bitfab 0.36.2 → 0.36.3
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/replay.rb +150 -43
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +6 -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: deea3052760a7b677216225f0cd030f4931995b1d10e6b7396c97a57a27ab9c1
|
|
4
|
+
data.tar.gz: 5f64a512db5c8c79f092e5bbf92e16adfc2e20a4a07c869eafab48fb42db5665
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9024ab4975b3963e4d2c91aa903b3e4bc7880791aa1f0a4132a9ee847e54e2132339246cbc1067beba8a2130e813738474fcc40a38a1d3b9a0e19ba92633834
|
|
7
|
+
data.tar.gz: b3c4b7c252a566192362e6d1a3e4d31bcad55f752a70527fce5de87ebc9ab75c4839a02fd5d67589823ee36003f3fcb49d5bdade0a315dc8a22971b9769ea9c3
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -12,6 +12,30 @@ require_relative "traceable"
|
|
|
12
12
|
require_relative "transport"
|
|
13
13
|
|
|
14
14
|
module Bitfab
|
|
15
|
+
# A requested replay database branch could not be resolved. The resolver code
|
|
16
|
+
# and original message remain structured for callers to inspect.
|
|
17
|
+
class DbBranchReplayError < StandardError
|
|
18
|
+
attr_reader :code, :original_trace_id
|
|
19
|
+
|
|
20
|
+
def initialize(code, message, original_trace_id)
|
|
21
|
+
super(message)
|
|
22
|
+
@code = code
|
|
23
|
+
@original_trace_id = original_trace_id
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Whole-run replay failure with every item collected before the run failed.
|
|
28
|
+
class ReplayError < StandardError
|
|
29
|
+
attr_reader :items, :test_run_id, :test_run_url
|
|
30
|
+
|
|
31
|
+
def initialize(message, items:, test_run_id:, test_run_url:)
|
|
32
|
+
super(message)
|
|
33
|
+
@items = items
|
|
34
|
+
@test_run_id = test_run_id
|
|
35
|
+
@test_run_url = test_run_url
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
15
39
|
# Replay mock strategies. Mirrors the Python and TypeScript SDKs.
|
|
16
40
|
#
|
|
17
41
|
# - "marked" : only spans declared with mock_on_replay: true return historical
|
|
@@ -85,6 +109,50 @@ module Bitfab
|
|
|
85
109
|
|
|
86
110
|
module_function
|
|
87
111
|
|
|
112
|
+
def json_safe(value)
|
|
113
|
+
case value
|
|
114
|
+
when Exception
|
|
115
|
+
serialized = {type: value.class.name, message: value.message, backtrace: value.backtrace}
|
|
116
|
+
if value.is_a?(DbBranchReplayError)
|
|
117
|
+
serialized[:code] = value.code
|
|
118
|
+
serialized[:original_trace_id] = value.original_trace_id
|
|
119
|
+
serialized[:cause] = json_safe(value.cause) if value.cause
|
|
120
|
+
end
|
|
121
|
+
serialized
|
|
122
|
+
when Hash
|
|
123
|
+
value.to_h { |key, child| [key, json_safe(child)] }
|
|
124
|
+
when Array
|
|
125
|
+
value.map { |child| json_safe(child) }
|
|
126
|
+
else
|
|
127
|
+
value
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def replay_item_error_message(error)
|
|
132
|
+
return error.message unless error.is_a?(DbBranchReplayError)
|
|
133
|
+
|
|
134
|
+
"Replay requested a database branch for trace #{error.original_trace_id} but it " \
|
|
135
|
+
"could not be resolved (#{error.code}): #{error.message}. The method was not run, " \
|
|
136
|
+
"because replaying it against the live database would produce a result that looks " \
|
|
137
|
+
"valid but did not use the historical data you asked for."
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def public_replay_items(items)
|
|
141
|
+
items.map { |item| item.except(:_sdk_trace_id) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def preserve_replay_failure(items, test_run_id, test_run_url)
|
|
145
|
+
yield
|
|
146
|
+
rescue => cause
|
|
147
|
+
error = ReplayError.new(
|
|
148
|
+
cause.message,
|
|
149
|
+
items: public_replay_items(items),
|
|
150
|
+
test_run_id:,
|
|
151
|
+
test_run_url:
|
|
152
|
+
)
|
|
153
|
+
raise error, cause:
|
|
154
|
+
end
|
|
155
|
+
|
|
88
156
|
# Replay historical traces through a method and create a test run.
|
|
89
157
|
#
|
|
90
158
|
# Fetches the last N traces for the given trace function key, re-runs each
|
|
@@ -223,6 +291,7 @@ module Bitfab
|
|
|
223
291
|
)
|
|
224
292
|
test_run_id = replay_data["testRunId"]
|
|
225
293
|
test_run_url = replay_data["testRunUrl"]
|
|
294
|
+
full_test_run_url = "#{client.service_url}#{test_run_url}"
|
|
226
295
|
server_items = replay_data["items"] || []
|
|
227
296
|
|
|
228
297
|
result_items = if server_items.any?
|
|
@@ -237,12 +306,16 @@ module Bitfab
|
|
|
237
306
|
# to finalize once the server confirms every replay trace it queued: the
|
|
238
307
|
# trace-ID mapping complete_replay builds would otherwise race the
|
|
239
308
|
# in-flight batches and hand back nil for every item.
|
|
240
|
-
|
|
309
|
+
preserve_replay_failure(result_items, test_run_id, full_test_run_url) do
|
|
310
|
+
wait_for_replay_persistence(http_client, test_run_id, result_items.map { |item| item[:_sdk_trace_id] })
|
|
311
|
+
end
|
|
241
312
|
|
|
242
313
|
# complete_replay finalizes the run and returns the token/diagnostic
|
|
243
314
|
# mapping; its failures propagate loudly because a run that never
|
|
244
315
|
# completed can't be finalized.
|
|
245
|
-
complete_response =
|
|
316
|
+
complete_response = preserve_replay_failure(result_items, test_run_id, full_test_run_url) do
|
|
317
|
+
http_client.complete_replay(test_run_id)
|
|
318
|
+
end
|
|
246
319
|
trace_id_map = complete_response&.dig("traceIds")
|
|
247
320
|
# Per-replay-trace token usage keyed by server trace id: the REPLAYED
|
|
248
321
|
# run's tokens (span-aggregated server-side), used below to fill each
|
|
@@ -278,11 +351,18 @@ module Bitfab
|
|
|
278
351
|
if completed_count.positive? && missing.length == completed_count
|
|
279
352
|
trace_count = complete_response["traceCount"]
|
|
280
353
|
server_count = trace_count.nil? ? "" : " The server persisted #{trace_count} trace(s) for this run."
|
|
281
|
-
|
|
354
|
+
cause = RuntimeError.new("Replay completed but the server has no persisted trace for " \
|
|
282
355
|
"any of the #{completed_count} completed item(s) " \
|
|
283
356
|
"(test_run_id #{test_run_id}).#{server_count} Trace uploads were " \
|
|
284
357
|
"flushed, so either the uploads failed or the replayed method is " \
|
|
285
|
-
"not traced (no root span was emitted)."
|
|
358
|
+
"not traced (no root span was emitted).")
|
|
359
|
+
error = ReplayError.new(
|
|
360
|
+
cause.message,
|
|
361
|
+
items: public_replay_items(result_items),
|
|
362
|
+
test_run_id:,
|
|
363
|
+
test_run_url: full_test_run_url
|
|
364
|
+
)
|
|
365
|
+
raise error, cause:
|
|
286
366
|
end
|
|
287
367
|
# SOME completed items missing: per-item upload failure. Warn, but
|
|
288
368
|
# return the run; the items that landed can still be labeled.
|
|
@@ -301,7 +381,7 @@ module Bitfab
|
|
|
301
381
|
result = {
|
|
302
382
|
items: result_items,
|
|
303
383
|
test_run_id:,
|
|
304
|
-
test_run_url:
|
|
384
|
+
test_run_url: full_test_run_url
|
|
305
385
|
}
|
|
306
386
|
# Persist the enriched result two ways so the Bitfab plugin never has to
|
|
307
387
|
# parse the replay's stdout (which a dependency's logging can corrupt):
|
|
@@ -543,7 +623,7 @@ module Bitfab
|
|
|
543
623
|
begin
|
|
544
624
|
dir = File.dirname(result_path)
|
|
545
625
|
FileUtils.mkdir_p(dir) unless dir.nil? || dir.empty? || dir == "."
|
|
546
|
-
File.write(result_path, "#{
|
|
626
|
+
File.write(result_path, "#{Bitfab.serialize_replay_result(result)}\n")
|
|
547
627
|
rescue => e
|
|
548
628
|
warn "Bitfab: failed to write replay result to BITFAB_REPLAY_RESULT_PATH (#{result_path}): #{e.message}"
|
|
549
629
|
end
|
|
@@ -593,6 +673,8 @@ module Bitfab
|
|
|
593
673
|
result: result[:result],
|
|
594
674
|
original_output: result[:original_output],
|
|
595
675
|
error:,
|
|
676
|
+
trace_error: result[:trace_error],
|
|
677
|
+
replay_error: result[:replay_error],
|
|
596
678
|
duration_ms: result[:duration_ms],
|
|
597
679
|
tokens: result[:tokens],
|
|
598
680
|
model: result[:model],
|
|
@@ -664,16 +746,26 @@ module Bitfab
|
|
|
664
746
|
lease_error = include_db_branch_lease ? server_item["dbBranchLeaseError"] : nil
|
|
665
747
|
db_snapshot_ref = server_item["dbSnapshotRef"]
|
|
666
748
|
if include_db_branch_lease && lease.nil? && lease_error.nil?
|
|
667
|
-
|
|
749
|
+
begin
|
|
750
|
+
resolved = http_client.resolve_db_branch_lease(test_run_id, original_trace_id, db_branch_settings)
|
|
751
|
+
rescue => cause
|
|
752
|
+
error = DbBranchReplayError.new(
|
|
753
|
+
"lease_request_failed",
|
|
754
|
+
"Bitfab could not request the database branch: #{cause.message}",
|
|
755
|
+
original_trace_id
|
|
756
|
+
)
|
|
757
|
+
raise error, cause:
|
|
758
|
+
end
|
|
668
759
|
lease = resolved["lease"]
|
|
669
760
|
lease_error = resolved["leaseError"]
|
|
670
761
|
db_snapshot_ref = resolved["dbSnapshotRef"] || db_snapshot_ref
|
|
671
762
|
end
|
|
672
763
|
if lease_error
|
|
673
|
-
raise
|
|
674
|
-
|
|
675
|
-
"
|
|
676
|
-
|
|
764
|
+
raise DbBranchReplayError.new(
|
|
765
|
+
lease_error["code"].to_s,
|
|
766
|
+
lease_error["message"].to_s,
|
|
767
|
+
original_trace_id
|
|
768
|
+
)
|
|
677
769
|
end
|
|
678
770
|
|
|
679
771
|
span = http_client.get_external_span(original_span_id, replay_view: true)
|
|
@@ -752,7 +844,9 @@ module Bitfab
|
|
|
752
844
|
input: [],
|
|
753
845
|
result: nil,
|
|
754
846
|
original_output: nil,
|
|
755
|
-
error: e
|
|
847
|
+
error: replay_item_error_message(e),
|
|
848
|
+
trace_error: nil,
|
|
849
|
+
replay_error: e,
|
|
756
850
|
duration_ms: metrics&.dig(:duration_ms),
|
|
757
851
|
tokens: metrics&.dig(:tokens),
|
|
758
852
|
model: metrics&.dig(:model),
|
|
@@ -941,51 +1035,64 @@ module Bitfab
|
|
|
941
1035
|
|
|
942
1036
|
fn_result = nil
|
|
943
1037
|
fn_error = nil
|
|
1038
|
+
replay_error = nil
|
|
944
1039
|
# Client-side correlation id that tags this item's replay spans so the
|
|
945
1040
|
# server can echo back the row id it minted (resolved in run()'s
|
|
946
1041
|
# complete-replay loop). Carried on the item under :_sdk_trace_id, never
|
|
947
1042
|
# surfaced as the public :trace_id.
|
|
948
1043
|
sdk_trace_id = SecureRandom.uuid
|
|
949
1044
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1045
|
+
begin
|
|
1046
|
+
ReplayContext.with_context(
|
|
1047
|
+
test_run_id:,
|
|
1048
|
+
input_source_span_id:,
|
|
1049
|
+
input_source_trace_id:,
|
|
1050
|
+
trace_id: sdk_trace_id,
|
|
1051
|
+
mock_tree:,
|
|
1052
|
+
mock_strategy:,
|
|
1053
|
+
mock_overrides:,
|
|
1054
|
+
fetch_span_output:,
|
|
1055
|
+
db_branch_lease:,
|
|
1056
|
+
source_bitfab_trace_id:
|
|
1057
|
+
) do
|
|
1058
|
+
begin
|
|
1059
|
+
if adapt_inputs
|
|
1060
|
+
ctx = adapt_ctx || {
|
|
1061
|
+
original_trace_id: nil,
|
|
1062
|
+
original_span_id: input_source_span_id,
|
|
1063
|
+
# Deprecated aliases for original_trace_id/original_span_id.
|
|
1064
|
+
source_trace_id: nil,
|
|
1065
|
+
source_span_id: input_source_span_id
|
|
1066
|
+
}
|
|
1067
|
+
args, kwargs = adapt_inputs.call(args, kwargs, ctx)
|
|
1068
|
+
end
|
|
1069
|
+
rescue => e
|
|
1070
|
+
replay_error = e
|
|
1071
|
+
end
|
|
1072
|
+
if replay_error.nil?
|
|
1073
|
+
begin
|
|
1074
|
+
fn_result = if kwargs.empty?
|
|
1075
|
+
receiver.send(method_name, *args)
|
|
1076
|
+
else
|
|
1077
|
+
receiver.send(method_name, *args, **kwargs)
|
|
1078
|
+
end
|
|
1079
|
+
rescue => e
|
|
1080
|
+
fn_error = e
|
|
1081
|
+
end
|
|
1082
|
+
end
|
|
979
1083
|
end
|
|
980
1084
|
rescue => e
|
|
981
|
-
|
|
1085
|
+
replay_error = e
|
|
982
1086
|
end
|
|
983
1087
|
|
|
1088
|
+
item_error = fn_error || replay_error
|
|
984
1089
|
{
|
|
985
1090
|
input: args,
|
|
986
1091
|
result: fn_result,
|
|
987
1092
|
original_output: item["output"],
|
|
988
|
-
error:
|
|
1093
|
+
error: item_error&.message,
|
|
1094
|
+
trace_error: fn_error,
|
|
1095
|
+
replay_error:,
|
|
989
1096
|
duration_ms: metrics[:duration_ms],
|
|
990
1097
|
tokens: metrics[:tokens],
|
|
991
1098
|
model: metrics[:model],
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -159,9 +159,14 @@ module Bitfab
|
|
|
159
159
|
# succeeded, errored, and item (the object replay already passes to
|
|
160
160
|
# on_progress)
|
|
161
161
|
def report_replay_progress(progress)
|
|
162
|
-
warn "#{BITFAB_PROGRESS_PREFIX}#{progress.to_json}"
|
|
162
|
+
warn "#{BITFAB_PROGRESS_PREFIX}#{Replay.json_safe(progress).to_json}"
|
|
163
163
|
rescue
|
|
164
164
|
nil
|
|
165
165
|
end
|
|
166
|
+
|
|
167
|
+
# Serialize a replay result while retaining structured exception fields.
|
|
168
|
+
def serialize_replay_result(result)
|
|
169
|
+
JSON.pretty_generate(Replay.json_safe(result))
|
|
170
|
+
end
|
|
166
171
|
end
|
|
167
172
|
end
|