Class: Aidp::Harness::UsageLimitTracker

Inherits:
Object
  • Object
show all
Includes:
RescueLogging
Defined in:
lib/aidp/harness/usage_limit_tracker.rb

Overview

Domain service for tracking usage metrics per provider and period Handles recording usage, querying current usage, and period resets

Constant Summary collapse

MAX_HISTORY_PERIODS =

Maximum number of historical periods to retain for trend analysis

12

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from RescueLogging

__log_rescue_impl, #log_rescue, log_rescue

Constructor Details

#initialize(provider_name:, project_dir:, metrics_repo: nil) ⇒ UsageLimitTracker

Returns a new instance of UsageLimitTracker.



20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 20

def initialize(provider_name:, project_dir:, metrics_repo: nil)
  @provider_name = provider_name.to_s
  @project_dir = project_dir
  @metrics_repo = metrics_repo
  @usage_file = File.join(project_dir, ".aidp", "usage_tracking", "#{@provider_name}.yml")
  @usage_data = nil
  @lock = Mutex.new if defined?(Mutex)

  Aidp.log_debug("usage_limit_tracker", "initialized",
    provider: @provider_name,
    project_dir: @project_dir)
end

Instance Attribute Details

#project_dirObject (readonly)

Returns the value of attribute project_dir.



18
19
20
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 18

def project_dir
  @project_dir
end

#provider_nameObject (readonly)

Returns the value of attribute provider_name.



18
19
20
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 18

def provider_name
  @provider_name
end

Instance Method Details

#clear_all_usageObject

Clear all usage data (for testing or user request)



201
202
203
204
205
206
207
208
209
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 201

def clear_all_usage
  Aidp.log_warn("usage_limit_tracker", "clearing_all_usage",
    provider: @provider_name)

  with_lock do
    File.delete(@usage_file) if File.exist?(@usage_file)
    @usage_data = nil
  end
end

#current_usage(period_type: "monthly", reset_day: 1) ⇒ Hash

Get current usage for a period

Parameters:

  • period_type (String) (defaults to: "monthly")

    Period type (daily, weekly, monthly)

  • reset_day (Integer) (defaults to: 1)

    Reset day for monthly periods

Returns:

  • (Hash)

    Current usage data with keys:

    • :period_key [String] Period identifier (e.g., "2024-01")
    • :period_description [String] Human-readable period description
    • :total_tokens [Integer] Total tokens used in period
    • :total_cost [Float] Total cost incurred in period
    • :request_count [Integer] Number of requests made
    • :tier_usage [Hash] Usage breakdown by tier
    • :start_time [Time] Period start time
    • :end_time [Time] Period end time
    • :remaining_seconds [Integer] Seconds until period ends


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 97

def current_usage(period_type: "monthly", reset_day: 1)
  period = UsagePeriod.current(period_type: period_type, reset_day: reset_day)
  period_key = period.period_key

  data = load_usage_data
  period_data = data[:periods]&.[](period_key.to_sym)

  if period_data
    {
      period_key: period_key,
      period_description: period.description,
      total_tokens: period_data[:total_tokens] || 0,
      total_cost: period_data[:total_cost] || 0.0,
      request_count: period_data[:request_count] || 0,
      tier_usage: period_data[:tier_usage] || {},
      start_time: period.start_time,
      end_time: period.end_time,
      remaining_seconds: period.remaining_seconds
    }
  else
    {
      period_key: period_key,
      period_description: period.description,
      total_tokens: 0,
      total_cost: 0.0,
      request_count: 0,
      tier_usage: {},
      start_time: period.start_time,
      end_time: period.end_time,
      remaining_seconds: period.remaining_seconds
    }
  end
end

#record_usage(tokens:, cost:, tier: "standard", period_type: "monthly", reset_day: 1) ⇒ Object

Record usage for a request

Parameters:

  • tokens (Integer)

    Number of tokens used

  • cost (Float)

    Cost incurred

  • tier (String) (defaults to: "standard")

    Tier used (mini, standard, advanced, etc.)

  • period_type (String) (defaults to: "monthly")

    Period type (daily, weekly, monthly)

  • reset_day (Integer) (defaults to: 1)

    Reset day for monthly periods



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 40

def record_usage(tokens:, cost:, tier: "standard", period_type: "monthly", reset_day: 1)
  Aidp.log_debug("usage_limit_tracker", "recording_usage",
    provider: @provider_name,
    tokens: tokens,
    cost: cost,
    tier: tier)

  return unless tokens.positive? || cost.positive?

  with_lock do
    data = load_usage_data
    period = UsagePeriod.current(period_type: period_type, reset_day: reset_day)
    period_key = period.period_key

    # Initialize period data if needed
    data[:periods] ||= {}
    period_sym = period_key.to_sym
    data[:periods][period_sym] ||= new_period_data(period)

    # Update usage metrics
    period_data = data[:periods][period_sym]
    period_data[:total_tokens] = (period_data[:total_tokens] || 0) + tokens
    period_data[:total_cost] = (period_data[:total_cost] || 0.0) + cost
    period_data[:request_count] = (period_data[:request_count] || 0) + 1
    period_data[:last_updated] = Time.now.iso8601

    # Update tier-specific usage
    tier_key = tier.to_s.downcase
    period_data[:tier_usage] ||= {}
    period_data[:tier_usage][tier_key] ||= {tokens: 0, cost: 0.0, requests: 0}
    period_data[:tier_usage][tier_key][:tokens] += tokens
    period_data[:tier_usage][tier_key][:cost] += cost
    period_data[:tier_usage][tier_key][:requests] += 1

    # Prune old periods
    prune_old_periods(data)

    # Save updated data
    save_usage_data(data)
    @usage_data = data
  end
end

#reset_current_period(period_type: "monthly", reset_day: 1) ⇒ Object

Reset usage for the current period (for testing or manual reset)

Parameters:

  • period_type (String) (defaults to: "monthly")

    Period type

  • reset_day (Integer) (defaults to: 1)

    Reset day



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 183

def reset_current_period(period_type: "monthly", reset_day: 1)
  Aidp.log_warn("usage_limit_tracker", "resetting_current_period",
    provider: @provider_name,
    period_type: period_type)

  with_lock do
    period = UsagePeriod.current(period_type: period_type, reset_day: reset_day)
    period_key = period.period_key

    data = load_usage_data
    data[:periods]&.delete(period_key.to_sym)

    save_usage_data(data)
    @usage_data = data
  end
end

#tier_usage(tier:, period_type: "monthly", reset_day: 1) ⇒ Hash

Get usage for a specific tier in the current period

Parameters:

  • tier (String)

    Tier name

  • period_type (String) (defaults to: "monthly")

    Period type

  • reset_day (Integer) (defaults to: 1)

    Reset day

Returns:

  • (Hash)

    Tier-specific usage data



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 137

def tier_usage(tier:, period_type: "monthly", reset_day: 1)
  usage = current_usage(period_type: period_type, reset_day: reset_day)
  tier_key = tier.to_s.downcase

  tier_data = usage[:tier_usage][tier_key] || usage[:tier_usage][tier_key.to_sym]

  if tier_data
    {
      tokens: tier_data[:tokens] || tier_data["tokens"] || 0,
      cost: tier_data[:cost] || tier_data["cost"] || 0.0,
      requests: tier_data[:requests] || tier_data["requests"] || 0
    }
  else
    {tokens: 0, cost: 0.0, requests: 0}
  end
end

#usage_history(limit: MAX_HISTORY_PERIODS) ⇒ Array<Hash>

Note:

Sorting relies on lexicographic ordering of period keys (e.g., "2024-01", "2024-W01") which produces correct chronological order for ISO-formatted date strings

Get usage history across multiple periods

Parameters:

  • limit (Integer) (defaults to: MAX_HISTORY_PERIODS)

    Maximum number of periods to return

Returns:

  • (Array<Hash>)

    Array of period usage data, newest first



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/aidp/harness/usage_limit_tracker.rb', line 160

def usage_history(limit: MAX_HISTORY_PERIODS)
  data = load_usage_data
  return [] unless data[:periods].is_a?(Hash)

  data[:periods]
    .sort_by { |key, _| key }
    .reverse
    .take(limit)
    .map do |key, period_data|
      {
        period_key: key,
        total_tokens: period_data[:total_tokens] || 0,
        total_cost: period_data[:total_cost] || 0.0,
        request_count: period_data[:request_count] || 0,
        tier_usage: period_data[:tier_usage] || {}
      }
    end
end