Class: Exa::Instrumentation::CostTracker

Inherits:
BaseSubscriber show all
Defined in:
lib/exa/instrumentation/cost_tracker.rb

Overview

Built-in subscriber for tracking API costs from actual response data. Thread-safe and works with both sync and async requests.

Examples:

Basic usage

tracker = Exa::Instrumentation::CostTracker.new
tracker.subscribe

client.search.search(query: "AI papers", num_results: 10)
client.search.contents(urls: ["https://example.com"], text: true)

puts tracker.total_cost  # => 0.006
puts tracker.summary     # => { Search => 0.005, Contents => 0.001 }

tracker.unsubscribe  # Clean up when done

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from BaseSubscriber

#unsubscribe

Constructor Details

#initializeCostTracker

Returns a new instance of CostTracker.



29
30
31
32
33
34
35
# File 'lib/exa/instrumentation/cost_tracker.rb', line 29

def initialize
  @total_cost = 0.0
  @request_count = 0
  @requests = []
  @mutex = Mutex.new
  super()
end

Instance Attribute Details

#request_countInteger (readonly)

Returns Number of requests tracked.

Returns:

  • (Integer)

    Number of requests tracked



24
25
26
# File 'lib/exa/instrumentation/cost_tracker.rb', line 24

def request_count
  @request_count
end

#requestsArray<Hash> (readonly)

Returns All tracked requests with their costs.

Returns:

  • (Array<Hash>)

    All tracked requests with their costs



27
28
29
# File 'lib/exa/instrumentation/cost_tracker.rb', line 27

def requests
  @requests
end

#total_costFloat (readonly)

Returns Total accumulated cost in dollars.

Returns:

  • (Float)

    Total accumulated cost in dollars



21
22
23
# File 'lib/exa/instrumentation/cost_tracker.rb', line 21

def total_cost
  @total_cost
end

Instance Method Details

#average_costFloat

Returns the average cost per request.

Returns:

  • (Float)

    Average cost or 0.0 if no requests



68
69
70
71
72
73
# File 'lib/exa/instrumentation/cost_tracker.rb', line 68

def average_cost
  @mutex.synchronize do
    return 0.0 if @request_count.zero?
    @total_cost / @request_count
  end
end

#reportString

Returns a formatted report of costs.

Returns:

  • (String)

    Human-readable cost report



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/exa/instrumentation/cost_tracker.rb', line 86

def report
  @mutex.synchronize do
    lines = ["Exa API Cost Report"]
    lines << "-" * 40
    lines << "Total Cost: $#{format('%.6f', @total_cost)}"
    lines << "Request Count: #{@request_count}"
    lines << "Average Cost: $#{format('%.6f', @request_count.zero? ? 0.0 : @total_cost / @request_count)}"
    lines << ""
    lines << "By Endpoint:"

    @requests
      .group_by { |req| req[:endpoint] }
      .transform_values { |reqs| reqs.sum { |r| r[:cost] } }
      .sort_by { |_endpoint, cost| -cost }
      .each do |endpoint, cost|
        lines << "  #{endpoint.serialize}: $#{format('%.6f', cost)}"
      end

    lines.join("\n")
  end
end

#reset!Object

Reset all tracked data.



76
77
78
79
80
81
82
# File 'lib/exa/instrumentation/cost_tracker.rb', line 76

def reset!
  @mutex.synchronize do
    @total_cost = 0.0
    @request_count = 0
    @requests.clear
  end
end

#subscribeObject

Subscribe to request completion events. Call this after initialization to start tracking.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/exa/instrumentation/cost_tracker.rb', line 39

def subscribe
  add_subscription("exa.request.complete") do |_event_name, payload|
    next unless payload.cost_dollars

    @mutex.synchronize do
      @total_cost += payload.cost_dollars
      @request_count += 1
      @requests << {
        endpoint: payload.endpoint,
        cost: payload.cost_dollars,
        request_id: payload.request_id,
        timestamp: payload.timestamp
      }
    end
  end
end

#summaryHash<Endpoint, Float>

Returns a summary of costs grouped by endpoint.

Returns:

  • (Hash<Endpoint, Float>)

    Cost per endpoint



58
59
60
61
62
63
64
# File 'lib/exa/instrumentation/cost_tracker.rb', line 58

def summary
  @mutex.synchronize do
    @requests
      .group_by { |req| req[:endpoint] }
      .transform_values { |reqs| reqs.sum { |r| r[:cost] } }
  end
end