bitfab 0.36.7 → 0.36.8

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: f18189ba0b9b7a2c8cd23ee6672f927af7f9f6400457d71d86b90c13a61698e9
4
- data.tar.gz: 61f38c9eb03166eeef759aca2914a015e92fe89247c163541c786331ed8fa26d
3
+ metadata.gz: 87c160ba40f51aca6fded283a601f2e917b948403436879e7dcca1793673a4aa
4
+ data.tar.gz: 730c4a41a9f26ee51e2e25490d8dd89aa1205a0acb091f0ce9e2560b40583622
5
5
  SHA512:
6
- metadata.gz: 8940e1f95426c3fa3343d48a340b3b4b2d2adec10c714b9e204ac7fdcfa348fba28b6bf4ed1820c1acd67536883a21701ccf401bc547cf2c3bd4c9b929169f6f
7
- data.tar.gz: 88935462030c7997ab153444e327666cc0040a0baed397c19322bfd1cedff2c9ef6624b64ce227aba02cad471d6ad05c639b1cdfbdb6a69f3a6e2f1346dca395
6
+ metadata.gz: 58ca8e468386578b6792e8aef86a74ab7f601b432e7333391931a028b8dd0a7e1006a317be2aeccc58fcdef5a89ab550fe950c89527ecf0babe888ed5cb32d1e
7
+ data.tar.gz: 56aa1281a7583d0c106b64044def3494221e33e9e1b45f25b2ee0113c43a72f6fc76c55ca2c0230b0430f92c2e55e7a0a7174ca980ac53c972b72a632d912155
@@ -13,10 +13,12 @@ require_relative "warn_once"
13
13
 
14
14
  module Bitfab
15
15
  # What a caller learns about one tracked trace once it takes it back.
16
- DeliveryReport = Struct.new(:span_count, :closed, :delivered, keyword_init: true)
16
+ # server_trace_id is the server's assigned traces.id, read back off the OTLP
17
+ # ingest response; nil when the server predates the field or nothing was acked.
18
+ DeliveryReport = Struct.new(:span_count, :closed, :delivered, :server_trace_id, keyword_init: true)
17
19
 
18
20
  TraceDelivery = Struct.new(:submitted_span_ids, :acked_span_ids, :closed, :closing_acked,
19
- keyword_init: true)
21
+ :server_trace_id, keyword_init: true)
20
22
 
21
23
  class HttpClient
22
24
  OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces"
@@ -413,6 +415,9 @@ module Bitfab
413
415
  )
414
416
  end
415
417
 
418
+ server_trace_ids = response.is_a?(Hash) ? response["traceIds"] : nil
419
+ record_server_trace_ids(server_trace_ids) if server_trace_ids.is_a?(Hash)
420
+
416
421
  partial_success = response.is_a?(Hash) ? response["partialSuccess"] : nil
417
422
  return unless partial_success.is_a?(Hash)
418
423
 
@@ -492,14 +497,40 @@ module Bitfab
492
497
  span_count: delivery.submitted_span_ids.size,
493
498
  closed: delivery.closed,
494
499
  delivered: delivery.closing_acked &&
495
- delivery.submitted_span_ids.subset?(delivery.acked_span_ids)
500
+ delivery.submitted_span_ids.subset?(delivery.acked_span_ids),
501
+ server_trace_id: delivery.server_trace_id
496
502
  )
497
503
  end
498
504
  end
499
505
  end
500
506
 
507
+ # The server's assigned traces.id for a tracked trace if it has already been
508
+ # read back off an ingest response, without stopping tracking. Lets a replay
509
+ # surface the id mid-run for items whose spans already landed.
510
+ def peek_server_trace_id(trace_id)
511
+ @delivery_mutex.synchronize { trace_deliveries[trace_id]&.server_trace_id }
512
+ end
513
+
501
514
  private
502
515
 
516
+ # Record the server's assigned traces.id for each tracked source trace, read
517
+ # back off the OTLP ingest response. Keyed by source trace id, the same key
518
+ # the delivery ledger uses. Untracked ids are ignored.
519
+ #
520
+ # Called on exporter threads, hence the mutex.
521
+ def record_server_trace_ids(mapping)
522
+ @delivery_mutex.synchronize do
523
+ mapping.each do |source_trace_id, server_trace_id|
524
+ next unless server_trace_id.is_a?(String)
525
+
526
+ delivery = trace_deliveries[source_trace_id]
527
+ next if delivery.nil?
528
+
529
+ delivery.server_trace_id = server_trace_id
530
+ end
531
+ end
532
+ end
533
+
503
534
  def record_submitted_carrier(ref)
504
535
  return if ref.nil?
505
536
 
data/lib/bitfab/replay.rb CHANGED
@@ -315,10 +315,21 @@ module Bitfab
315
315
  # to finalize once the server confirms every replay trace it queued: the
316
316
  # trace-ID mapping complete_replay builds would otherwise race the
317
317
  # in-flight batches and hand back nil for every item.
318
- preserve_replay_failure(result_items, test_run_id, full_test_run_url) do
318
+ delivered_trace_ids = preserve_replay_failure(result_items, test_run_id, full_test_run_url) do
319
319
  wait_for_replay_persistence(http_client, test_run_id, result_items.map { |item| item[:_sdk_trace_id] })
320
320
  end
321
321
 
322
+ # Primary source for item[:trace_id]: the server's assigned traces.id, read
323
+ # back per trace off the ingest response during the flush above (server-
324
+ # sourced, no polling, already in hand before complete_replay). The
325
+ # complete_replay map below remains the fallback for servers that don't
326
+ # return ids on ingest.
327
+ result_items.each do |item|
328
+ local_id = item[:_sdk_trace_id]
329
+ read_back = local_id && delivered_trace_ids[local_id]
330
+ item[:trace_id] = read_back if read_back
331
+ end
332
+
322
333
  # complete_replay finalizes the run and returns the token/diagnostic
323
334
  # mapping; its failures propagate loudly because a run that never
324
335
  # completed can't be finalized.
@@ -345,9 +356,10 @@ module Bitfab
345
356
  result_items.each do |item|
346
357
  local_id = item[:_sdk_trace_id]
347
358
  mapped = local_id && trace_id_map[local_id]
348
- # Write the real server replay trace id in as it comes back; the item
349
- # held nil until now (the client correlation id is never surfaced).
350
- item[:trace_id] = mapped
359
+ # Fallback fill for servers that did not return ids on the ingest
360
+ # response; the read-back loop above already set it when they did, so
361
+ # never overwrite a resolved id with nil here.
362
+ item[:trace_id] ||= mapped
351
363
  if item[:error].nil?
352
364
  completed_count += 1
353
365
  missing << local_id if mapped.nil?
@@ -653,11 +665,13 @@ module Bitfab
653
665
  errored = 0
654
666
  # Each event carries the single item that just finished so a progress UI
655
667
  # can render per-trace pass/fail as the run streams. The item's :trace_id
656
- # is nil at this stage: the server replay trace id isn't known until run()
657
- # writes it in after complete_replay, and the client correlation id is
658
- # never surfaced. original_trace_id (the historical trace being replayed,
659
- # taken from the server item) is what a UI keys on to identify what just
660
- # finished. source_trace_id/source_span_id are kept as deprecated aliases.
668
+ # is the server's assigned traces.id, read back off the ingest response and
669
+ # surfaced as this item finishes (its trace is flushed on finish); nil only
670
+ # if that flush could not confirm delivery in time (the end-of-run barrier
671
+ # then fills the returned item), and the client correlation id is never
672
+ # surfaced. original_trace_id (the historical trace being replayed, taken
673
+ # from the server item) is what a UI keys on to identify what just finished.
674
+ # source_trace_id/source_span_id are kept as deprecated aliases.
661
675
  report_start = lambda do |original_trace_id, original_span_id|
662
676
  progress_mutex.synchronize do
663
677
  started += 1
@@ -679,7 +693,27 @@ module Bitfab
679
693
  end
680
694
  end
681
695
 
696
+ # Flush an item's trace the moment it finishes, so its server traces.id is
697
+ # read back off the ingest response and surfaced on the item as it settles,
698
+ # instead of waiting for the end-of-run barrier. Waiting until the end is
699
+ # worst at low concurrency: each trace is closed and ready when its item
700
+ # finishes, but nothing fills a batch to force an export, so ready ids sit
701
+ # in the queue for the whole run. Serializing on a mutex keeps concurrent
702
+ # finishers from storming the server: a flush drains the whole queue, so
703
+ # the first finisher exports the batch and the rest find nothing to send.
704
+ flush_mutex = Mutex.new
705
+ flush_finished_item_trace = lambda do
706
+ flush_mutex.synchronize { Bitfab.flush_traces(timeout: PERSISTENCE_TIMEOUT_SECONDS) }
707
+ rescue => e
708
+ warn "Bitfab: replay per-item flush failed: #{e.message}"
709
+ end
710
+
682
711
  report = lambda do |result, original_trace_id, original_span_id, test_run_id|
712
+ # Deliver this item's trace now, then read its server id back off the
713
+ # ingest response. A flush failure never crashes the run: the id stays
714
+ # nil and the end-of-run barrier remains the authority on persistence.
715
+ flush_finished_item_trace.call
716
+ result[:trace_id] = http_client.peek_server_trace_id(result[:_sdk_trace_id]) || result[:trace_id]
683
717
  progress_mutex.synchronize do
684
718
  completed += 1
685
719
  error = result[:error]
@@ -1023,7 +1057,7 @@ module Bitfab
1023
1057
  # nothing to wait on, and must not emit an export request to discover it.
1024
1058
  unless http_client.closed_deliveries?(trace_ids)
1025
1059
  http_client.take_trace_deliveries(trace_ids)
1026
- return
1060
+ return {}
1027
1061
  end
1028
1062
 
1029
1063
  # A failed flush is a hint, not a verdict. It means delivery was not
@@ -1033,9 +1067,15 @@ module Bitfab
1033
1067
  # fully landed.
1034
1068
  flushed = Bitfab.flush_traces(timeout: PERSISTENCE_TIMEOUT_SECONDS)
1035
1069
 
1036
- deliveries = http_client.take_trace_deliveries(trace_ids).select { |_id, report| report.closed }
1070
+ raw_deliveries = http_client.take_trace_deliveries(trace_ids)
1071
+ # Server-assigned traces.id per replay trace, read back off the ingest
1072
+ # response during the flush and keyed by the client-side replay id.
1073
+ read_back_trace_ids = raw_deliveries.each_with_object({}) do |(id, report), acc|
1074
+ acc[id] = report.server_trace_id unless report.server_trace_id.nil?
1075
+ end
1076
+ deliveries = raw_deliveries.select { |_id, report| report.closed }
1037
1077
  expected_span_counts = deliveries.transform_values(&:span_count)
1038
- return if expected_span_counts.empty? || deliveries.each_value.all?(&:delivered)
1078
+ return read_back_trace_ids if expected_span_counts.empty? || deliveries.each_value.all?(&:delivered)
1039
1079
 
1040
1080
  deadline = Otel.monotonic_now + PERSISTENCE_TIMEOUT_SECONDS
1041
1081
  missing = expected_span_counts.keys
@@ -1045,7 +1085,7 @@ module Bitfab
1045
1085
  ready = status["traceIds"]
1046
1086
  ready = {} unless ready.is_a?(Hash)
1047
1087
  missing = expected_span_counts.keys - ready.keys
1048
- return if missing.empty?
1088
+ return read_back_trace_ids if missing.empty?
1049
1089
  break if Otel.monotonic_now >= deadline
1050
1090
 
1051
1091
  sleep((deadline - Otel.monotonic_now).clamp(0, PERSISTENCE_POLL_SECONDS))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.36.7"
4
+ VERSION = "0.36.8"
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.36.7
4
+ version: 0.36.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team