bitfab 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd571926bd40dc39f337e87f9977bfb0fe3219c21532dc1cf7be382c175a4559
4
- data.tar.gz: 17ff724ae1a42f97fa6eae82c97404de8dc9cef738a18674d76da48ba9ea762d
3
+ metadata.gz: 7536f774b491c131b04a135a41c5f753c7c507615e54aa675c314cff785b08df
4
+ data.tar.gz: 47e7804161bc40563f5aca99147ef43d2af1af6d73d22f6ad3ae3402ea4b51f4
5
5
  SHA512:
6
- metadata.gz: 012d3433d2d59200c812557a95b4f2b7cb8d93d7885d9495f59b7e21d89a892ffcc3c0bb83200f4c9f324320d5a1153f4c59bd2facac2dc1924174a7f184d81f
7
- data.tar.gz: 69aaffc897238560ebecf6fedbd81509eda58fd082126107585a913cc74a025456a32111356902251a0c5b3439ff2b84ef7ea5aac4325a10e531c93a5d460334
6
+ metadata.gz: 4aed7d077e6aa0ca89e8130b526578276591e60b41ae10670dc14db4eaca6221c30481dcad84acb8d7dfd7e549b2b92aa2c5a750efa36c4df85224b01360fe19
7
+ data.tar.gz: d73a3933eceba9c705fdab9f8b38f9f46766e026e59cdf61ee0ca2ea49a01ec4e5029fcbc87161a0a16602a2365ef3eeddba1acff27e659bf8fcf69b08d85138
data/lib/bitfab/client.rb CHANGED
@@ -59,14 +59,21 @@ module Bitfab
59
59
  # onto the method's current signature when its shape changed after the
60
60
  # traces were captured. Receives (args, kwargs, ctx) where ctx is
61
61
  # { trace_id:, source_span_id: }, and returns [new_args, new_kwargs].
62
+ # @param on_progress [#call, nil] optional callback invoked once per item as
63
+ # it finishes, with a running-totals hash { completed:, total:, succeeded:,
64
+ # errored: }. Use it to render replay progress (e.g. a terminal progress
65
+ # bar). Replay does not know pass/fail yet, so the totals only distinguish
66
+ # items whose method ran (:succeeded) from items that raised (:errored).
67
+ # A raising callback never crashes the run.
62
68
  # @return [Hash] with :items, :test_run_id, :test_run_url
63
69
  def replay(receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
64
70
  code_change_description: nil, code_change_files: nil, experiment_group_id: nil, dataset_id: nil, mock: "none",
65
- adapt_inputs: nil, environment: nil)
71
+ adapt_inputs: nil, environment: nil, on_progress: nil)
66
72
  Replay.run(
67
73
  self, receiver, method_name,
68
74
  trace_function_key:, limit:, trace_ids:, max_concurrency:,
69
- code_change_description:, code_change_files:, experiment_group_id:, dataset_id:, mock:, adapt_inputs:, environment:
75
+ code_change_description:, code_change_files:, experiment_group_id:, dataset_id:, mock:, adapt_inputs:, environment:,
76
+ on_progress:
70
77
  )
71
78
  end
72
79
 
data/lib/bitfab/replay.rb CHANGED
@@ -100,10 +100,14 @@ module Bitfab
100
100
  # { trace_id:, source_span_id: }, and returns [new_args, new_kwargs]. Runs
101
101
  # per item inside the same rescue as the method, so a raising adapter sets
102
102
  # that item's :error rather than crashing the run.
103
+ # @param on_progress [#call, nil] optional callback invoked once per item as
104
+ # it finishes, with a running-totals hash { completed:, total:, succeeded:,
105
+ # errored: }. Use it to render replay progress (e.g. a terminal progress
106
+ # bar). A raising callback never crashes the run.
103
107
  # @return [Hash] with :items, :test_run_id, :test_run_url
104
108
  def run(client, receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
105
109
  code_change_description: nil, code_change_files: nil, experiment_group_id: nil, dataset_id: nil, mock: "none",
106
- adapt_inputs: nil, environment: nil)
110
+ adapt_inputs: nil, environment: nil, on_progress: nil)
107
111
  unless MOCK_STRATEGIES.include?(mock.to_s)
108
112
  raise ArgumentError, "Invalid mock strategy '#{mock}'. Must be one of: #{MOCK_STRATEGIES.join(", ")}"
109
113
  end
@@ -157,7 +161,7 @@ module Bitfab
157
161
 
158
162
  result_items = if server_items.any?
159
163
  process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock.to_s,
160
- adapt_inputs, include_db_branch_lease)
164
+ adapt_inputs, include_db_branch_lease, on_progress:)
161
165
  else
162
166
  []
163
167
  end
@@ -170,6 +174,10 @@ module Bitfab
170
174
  # loudly.
171
175
  complete_response = http_client.complete_replay(test_run_id)
172
176
  trace_id_map = complete_response&.dig("traceIds")
177
+ # Per-replay-trace token usage keyed by server trace id: the REPLAYED
178
+ # run's tokens (span-aggregated server-side), used below to fill each
179
+ # item's :tokens.
180
+ replay_tokens = complete_response&.dig("tokens") || {}
173
181
 
174
182
  if trace_id_map.nil?
175
183
  # Older servers don't return the mapping. Preserve the legacy
@@ -202,6 +210,9 @@ module Bitfab
202
210
  completed_count += 1
203
211
  missing << item[:trace_id] if mapped.nil?
204
212
  end
213
+ # Pull this item's replayed-run tokens by its server trace id, before
214
+ # :trace_id is overwritten with that id below.
215
+ item[:tokens] = normalize_tokens(replay_tokens[mapped]) if mapped
205
216
  item[:trace_id] = mapped
206
217
  end
207
218
  if missing.any?
@@ -230,13 +241,39 @@ module Bitfab
230
241
 
231
242
  # Process all replay items, optionally in parallel using threads.
232
243
  def process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock_strategy,
233
- adapt_inputs = nil, include_db_branch_lease = false)
244
+ adapt_inputs = nil, include_db_branch_lease = false, on_progress: nil)
234
245
  concurrency = max_concurrency || server_items.length
235
246
 
247
+ # Reports running totals once per item as it settles. In the parallel
248
+ # path it runs from worker threads, so the mutex both makes the counter
249
+ # updates safe and serializes the user's callback (never called
250
+ # concurrently). A raising callback is swallowed: progress UI must never
251
+ # crash the run.
252
+ total = server_items.length
253
+ progress_mutex = Mutex.new
254
+ completed = 0
255
+ succeeded = 0
256
+ errored = 0
257
+ report = lambda do |result|
258
+ return unless on_progress
259
+
260
+ progress_mutex.synchronize do
261
+ completed += 1
262
+ result[:error].nil? ? (succeeded += 1) : (errored += 1)
263
+ begin
264
+ on_progress.call({completed:, total:, succeeded:, errored:})
265
+ rescue => e
266
+ warn "Bitfab: replay on_progress callback raised: #{e.message}"
267
+ end
268
+ end
269
+ end
270
+
236
271
  if concurrency <= 1
237
272
  server_items.map do |item|
238
- process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy, adapt_inputs,
239
- include_db_branch_lease)
273
+ result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy,
274
+ adapt_inputs, include_db_branch_lease)
275
+ report.call(result)
276
+ result
240
277
  end
241
278
  else
242
279
  results_mutex = Mutex.new
@@ -253,6 +290,7 @@ module Bitfab
253
290
  result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy,
254
291
  adapt_inputs, include_db_branch_lease)
255
292
  results_mutex.synchronize { results[idx] = result }
293
+ report.call(result)
256
294
  end
257
295
  end
258
296
  end
@@ -386,27 +424,34 @@ module Bitfab
386
424
  }
387
425
  end
388
426
 
389
- # Pull durationMs / tokens / model from the start-replay server item.
390
- # Normalizes to symbol-keyed tokens hash and nil-safe defaults so older
391
- # servers without these fields still produce a consistent shape.
427
+ # Pull durationMs / model from the start-replay server item. Nil-safe
428
+ # defaults so older servers without these fields still produce a consistent
429
+ # shape. Tokens are intentionally NOT read from the start item (it carries
430
+ # the ORIGINAL trace's tokens); the replayed run's tokens are filled in by
431
+ # run() from the complete-replay response once spans are aggregated
432
+ # server-side, and stay nil here and on older servers.
392
433
  def extract_server_item_metrics(server_item)
393
- raw_tokens = server_item["tokens"]
394
- tokens = if raw_tokens.is_a?(Hash)
395
- {
396
- input: raw_tokens["input"],
397
- output: raw_tokens["output"],
398
- cached: raw_tokens["cached"],
399
- total: raw_tokens["total"]
400
- }
401
- end
402
-
403
434
  {
404
435
  duration_ms: server_item["durationMs"],
405
- tokens:,
436
+ tokens: nil,
406
437
  model: server_item["model"]
407
438
  }
408
439
  end
409
440
 
441
+ # Normalize a complete-replay tokens hash (string-keyed JSON) into the
442
+ # symbol-keyed shape the replay item exposes. Nil when the server reported
443
+ # no token data for this trace.
444
+ def normalize_tokens(raw_tokens)
445
+ return nil unless raw_tokens.is_a?(Hash)
446
+
447
+ {
448
+ input: raw_tokens["input"],
449
+ output: raw_tokens["output"],
450
+ cached: raw_tokens["cached"],
451
+ total: raw_tokens["total"]
452
+ }
453
+ end
454
+
410
455
  # Execute a single replay item: deserialize inputs, call method with replay context.
411
456
  def execute_item(item, receiver, method_name, test_run_id, input_source_span_id = nil, metrics = {},
412
457
  input_source_trace_id: nil, mock_strategy: "none", mock_tree: nil, adapt_inputs: nil, adapt_ctx: nil,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.20.1"
4
+ VERSION = "0.21.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitfab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.1
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team