bitfab 0.36.7 → 0.36.9
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 +4 -4
- data/lib/bitfab/http_client.rb +34 -3
- data/lib/bitfab/replay.rb +66 -19
- data/lib/bitfab/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23d0d3e96160502ecf10da7fe952d08fec34439595e4c61e50ce9ea7c086a11c
|
|
4
|
+
data.tar.gz: 7bb58eab2f4749bd073593ec4c359d8460d1a1012eab60727999ad765a3e6d1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7360217eac4dfaa7ae66c645415598a9b794e2433434b425de54a1922243a93012270d9146309aca8d3ee65e9084da047cd6c6a5751cade83e1eb7b7d7458ab7
|
|
7
|
+
data.tar.gz: ee2575e01da6352effba7a1943621aa44280aabd5ead4aed76ce85561db6ccd8251c28c369de846fce1879e8ff4a08da9a6211b924d40f0a78430bec98302e76
|
data/lib/bitfab/http_client.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
349
|
-
#
|
|
350
|
-
|
|
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?
|
|
@@ -558,28 +570,28 @@ module Bitfab
|
|
|
558
570
|
|
|
559
571
|
base, from_trunk = resolved
|
|
560
572
|
|
|
561
|
-
tracked = git(root, ["diff", "--name-status", "--
|
|
573
|
+
tracked = git(root, ["diff", "--name-status", "--find-renames", "-z", base, "--", ":!.bitfab"]) || ""
|
|
562
574
|
untracked = git(root, ["ls-files", "--others", "--exclude-standard", "-z", "--", ":!.bitfab"]) || ""
|
|
563
575
|
|
|
564
576
|
entries = cc_parse_name_status_z(tracked)
|
|
565
|
-
untracked.split("\0").reject(&:empty?).each { |p| entries << ["A", p] }
|
|
577
|
+
untracked.split("\0").reject(&:empty?).each { |p| entries << ["A", p, p] }
|
|
566
578
|
return nil if entries.empty?
|
|
567
579
|
|
|
568
580
|
files = []
|
|
569
581
|
total = 0
|
|
570
|
-
entries.each do |status, path|
|
|
582
|
+
entries.each do |status, before_path, path|
|
|
571
583
|
break if files.length >= CC_MAX_FILES
|
|
572
584
|
|
|
573
585
|
# Skip oversized files by size BEFORE reading their full contents, so a
|
|
574
586
|
# huge changed file can't OOM/stall replay just to be discarded.
|
|
575
|
-
before_bytes = (status == "A") ? 0 : cc_blob_bytes(root, base,
|
|
587
|
+
before_bytes = (status == "A") ? 0 : cc_blob_bytes(root, base, before_path)
|
|
576
588
|
after_bytes = (status == "D") ? 0 : cc_working_bytes(root, path)
|
|
577
589
|
next if before_bytes > CC_MAX_FILE_BYTES || after_bytes > CC_MAX_FILE_BYTES
|
|
578
590
|
|
|
579
591
|
# Normalize CRLF -> LF on both sides: git's blob is already LF-normalized
|
|
580
592
|
# (autocrlf clean), so a raw CRLF working file would otherwise skew every
|
|
581
593
|
# line as changed. Comparing/storing LF keeps the diff meaningful.
|
|
582
|
-
before = (status == "A") ? "" : (git(root, ["show", "#{base}:#{
|
|
594
|
+
before = (status == "A") ? "" : (git(root, ["show", "#{base}:#{before_path}"]) || "").gsub("\r\n", "\n")
|
|
583
595
|
after = (status == "D") ? "" : cc_read_working_file(root, path).gsub("\r\n", "\n")
|
|
584
596
|
next if before == after
|
|
585
597
|
|
|
@@ -607,8 +619,15 @@ module Bitfab
|
|
|
607
619
|
out = []
|
|
608
620
|
i = 0
|
|
609
621
|
while i + 1 < parts.length
|
|
610
|
-
|
|
622
|
+
status = parts[i][0]
|
|
623
|
+
before_path = parts[i + 1]
|
|
611
624
|
i += 2
|
|
625
|
+
if ["R", "C"].include?(status) && i < parts.length
|
|
626
|
+
out << [status, before_path, parts[i]]
|
|
627
|
+
i += 1
|
|
628
|
+
else
|
|
629
|
+
out << [status, before_path, before_path]
|
|
630
|
+
end
|
|
612
631
|
end
|
|
613
632
|
out
|
|
614
633
|
end
|
|
@@ -653,11 +672,13 @@ module Bitfab
|
|
|
653
672
|
errored = 0
|
|
654
673
|
# Each event carries the single item that just finished so a progress UI
|
|
655
674
|
# can render per-trace pass/fail as the run streams. The item's :trace_id
|
|
656
|
-
# is
|
|
657
|
-
#
|
|
658
|
-
#
|
|
659
|
-
#
|
|
660
|
-
#
|
|
675
|
+
# is the server's assigned traces.id, read back off the ingest response and
|
|
676
|
+
# surfaced as this item finishes (its trace is flushed on finish); nil only
|
|
677
|
+
# if that flush could not confirm delivery in time (the end-of-run barrier
|
|
678
|
+
# then fills the returned item), and the client correlation id is never
|
|
679
|
+
# surfaced. original_trace_id (the historical trace being replayed, taken
|
|
680
|
+
# from the server item) is what a UI keys on to identify what just finished.
|
|
681
|
+
# source_trace_id/source_span_id are kept as deprecated aliases.
|
|
661
682
|
report_start = lambda do |original_trace_id, original_span_id|
|
|
662
683
|
progress_mutex.synchronize do
|
|
663
684
|
started += 1
|
|
@@ -679,7 +700,27 @@ module Bitfab
|
|
|
679
700
|
end
|
|
680
701
|
end
|
|
681
702
|
|
|
703
|
+
# Flush an item's trace the moment it finishes, so its server traces.id is
|
|
704
|
+
# read back off the ingest response and surfaced on the item as it settles,
|
|
705
|
+
# instead of waiting for the end-of-run barrier. Waiting until the end is
|
|
706
|
+
# worst at low concurrency: each trace is closed and ready when its item
|
|
707
|
+
# finishes, but nothing fills a batch to force an export, so ready ids sit
|
|
708
|
+
# in the queue for the whole run. Serializing on a mutex keeps concurrent
|
|
709
|
+
# finishers from storming the server: a flush drains the whole queue, so
|
|
710
|
+
# the first finisher exports the batch and the rest find nothing to send.
|
|
711
|
+
flush_mutex = Mutex.new
|
|
712
|
+
flush_finished_item_trace = lambda do
|
|
713
|
+
flush_mutex.synchronize { Bitfab.flush_traces(timeout: PERSISTENCE_TIMEOUT_SECONDS) }
|
|
714
|
+
rescue => e
|
|
715
|
+
warn "Bitfab: replay per-item flush failed: #{e.message}"
|
|
716
|
+
end
|
|
717
|
+
|
|
682
718
|
report = lambda do |result, original_trace_id, original_span_id, test_run_id|
|
|
719
|
+
# Deliver this item's trace now, then read its server id back off the
|
|
720
|
+
# ingest response. A flush failure never crashes the run: the id stays
|
|
721
|
+
# nil and the end-of-run barrier remains the authority on persistence.
|
|
722
|
+
flush_finished_item_trace.call
|
|
723
|
+
result[:trace_id] = http_client.peek_server_trace_id(result[:_sdk_trace_id]) || result[:trace_id]
|
|
683
724
|
progress_mutex.synchronize do
|
|
684
725
|
completed += 1
|
|
685
726
|
error = result[:error]
|
|
@@ -1023,7 +1064,7 @@ module Bitfab
|
|
|
1023
1064
|
# nothing to wait on, and must not emit an export request to discover it.
|
|
1024
1065
|
unless http_client.closed_deliveries?(trace_ids)
|
|
1025
1066
|
http_client.take_trace_deliveries(trace_ids)
|
|
1026
|
-
return
|
|
1067
|
+
return {}
|
|
1027
1068
|
end
|
|
1028
1069
|
|
|
1029
1070
|
# A failed flush is a hint, not a verdict. It means delivery was not
|
|
@@ -1033,9 +1074,15 @@ module Bitfab
|
|
|
1033
1074
|
# fully landed.
|
|
1034
1075
|
flushed = Bitfab.flush_traces(timeout: PERSISTENCE_TIMEOUT_SECONDS)
|
|
1035
1076
|
|
|
1036
|
-
|
|
1077
|
+
raw_deliveries = http_client.take_trace_deliveries(trace_ids)
|
|
1078
|
+
# Server-assigned traces.id per replay trace, read back off the ingest
|
|
1079
|
+
# response during the flush and keyed by the client-side replay id.
|
|
1080
|
+
read_back_trace_ids = raw_deliveries.each_with_object({}) do |(id, report), acc|
|
|
1081
|
+
acc[id] = report.server_trace_id unless report.server_trace_id.nil?
|
|
1082
|
+
end
|
|
1083
|
+
deliveries = raw_deliveries.select { |_id, report| report.closed }
|
|
1037
1084
|
expected_span_counts = deliveries.transform_values(&:span_count)
|
|
1038
|
-
return if expected_span_counts.empty? || deliveries.each_value.all?(&:delivered)
|
|
1085
|
+
return read_back_trace_ids if expected_span_counts.empty? || deliveries.each_value.all?(&:delivered)
|
|
1039
1086
|
|
|
1040
1087
|
deadline = Otel.monotonic_now + PERSISTENCE_TIMEOUT_SECONDS
|
|
1041
1088
|
missing = expected_span_counts.keys
|
|
@@ -1045,7 +1092,7 @@ module Bitfab
|
|
|
1045
1092
|
ready = status["traceIds"]
|
|
1046
1093
|
ready = {} unless ready.is_a?(Hash)
|
|
1047
1094
|
missing = expected_span_counts.keys - ready.keys
|
|
1048
|
-
return if missing.empty?
|
|
1095
|
+
return read_back_trace_ids if missing.empty?
|
|
1049
1096
|
break if Otel.monotonic_now >= deadline
|
|
1050
1097
|
|
|
1051
1098
|
sleep((deadline - Otel.monotonic_now).clamp(0, PERSISTENCE_POLL_SECONDS))
|
data/lib/bitfab/version.rb
CHANGED