bitfab 0.51.9 → 0.51.10
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/README.md +3 -3
- data/exe/bitfab-seed +13 -0
- data/lib/bitfab/assertion_categories.rb +42 -0
- data/lib/bitfab/assertions.rb +35 -0
- data/lib/bitfab/baml.rb +208 -0
- data/lib/bitfab/client.rb +95 -31
- data/lib/bitfab/db_snapshot.rb +18 -5
- data/lib/bitfab/detached_trace.rb +46 -0
- data/lib/bitfab/graders.rb +23 -0
- data/lib/bitfab/http_client.rb +106 -35
- data/lib/bitfab/labels.rb +80 -0
- data/lib/bitfab/replay.rb +92 -23
- data/lib/bitfab/replay_cli.rb +7 -1
- data/lib/bitfab/replay_concurrency.rb +44 -0
- data/lib/bitfab/replay_memory.rb +111 -0
- data/lib/bitfab/replay_processes.rb +218 -0
- data/lib/bitfab/replay_registry.rb +100 -15
- data/lib/bitfab/seed.rb +128 -0
- data/lib/bitfab/seed_cli.rb +67 -0
- data/lib/bitfab/seed_context.rb +21 -0
- data/lib/bitfab/simulation_plan.rb +248 -0
- data/lib/bitfab/span_context.rb +9 -4
- data/lib/bitfab/span_origin.rb +13 -0
- data/lib/bitfab/subtree.rb +144 -5
- data/lib/bitfab/thread_propagation.rb +111 -0
- data/lib/bitfab/traceable.rb +70 -155
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +9 -2
- metadata +32 -1
data/lib/bitfab/client.rb
CHANGED
|
@@ -5,13 +5,18 @@ require "time"
|
|
|
5
5
|
|
|
6
6
|
require_relative "constants"
|
|
7
7
|
require_relative "env"
|
|
8
|
+
require_relative "seed_context"
|
|
9
|
+
require_relative "detached_trace"
|
|
8
10
|
require_relative "http_client"
|
|
11
|
+
require_relative "replay_concurrency"
|
|
12
|
+
require_relative "replay_processes"
|
|
9
13
|
require_relative "replay"
|
|
10
14
|
require_relative "span_context"
|
|
11
15
|
require_relative "subtree"
|
|
12
16
|
require_relative "serialize"
|
|
13
17
|
require_relative "timestamp"
|
|
14
18
|
require_relative "warn_once"
|
|
19
|
+
require_relative "thread_propagation"
|
|
15
20
|
|
|
16
21
|
module Bitfab
|
|
17
22
|
class Client
|
|
@@ -32,17 +37,16 @@ module Bitfab
|
|
|
32
37
|
|
|
33
38
|
attr_reader :service_url
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
attr_reader :datasets
|
|
40
|
+
attr_reader :datasets, :assertion_categories, :traces, :labels, :graders
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
def initialize(api_key: nil, service_url: nil, capture_enabled: nil, enabled: nil, strict: false, simulation_plan: true, timeout: 120, trace_across_threads: nil, env_vars: nil, baml_client: nil, db_snapshot: nil)
|
|
43
|
+
@db_snapshot = DbSnapshot.normalize_config(db_snapshot)
|
|
44
|
+
@env_vars = env_vars || ENV.to_h
|
|
45
|
+
@baml_client = baml_client
|
|
42
46
|
@api_key_config = api_key
|
|
43
47
|
@service_url = service_url || DEFAULT_SERVICE_URL
|
|
44
|
-
|
|
45
|
-
@enabled_in_code = enabled
|
|
48
|
+
Bitfab.warn_once("enabled-deprecated", "Bitfab(enabled:) is deprecated; use capture_enabled: instead.") unless enabled.nil?
|
|
49
|
+
@enabled_in_code = capture_enabled.nil? ? enabled : capture_enabled
|
|
46
50
|
@capture_configured_from_env = nil
|
|
47
51
|
@strict = strict
|
|
48
52
|
# Cached only once a non-empty key is found, so an early resolve (before
|
|
@@ -51,13 +55,19 @@ module Bitfab
|
|
|
51
55
|
@api_key_warned = false
|
|
52
56
|
# The key is NOT read here. HttpClient gets a proc so the key is resolved
|
|
53
57
|
# at send time, after any env loading has run.
|
|
54
|
-
@http_client = HttpClient.new(api_key: -> { resolve_api_key }, service_url: @service_url)
|
|
58
|
+
@http_client = HttpClient.new(api_key: -> { resolve_api_key }, service_url: @service_url, timeout:)
|
|
59
|
+
@http_client.configure_simulation_plan(enabled: simulation_plan)
|
|
55
60
|
@datasets = Datasets.new(@http_client)
|
|
61
|
+
@assertion_categories = AssertionCategories.new(@http_client)
|
|
56
62
|
@traces = Traces.new(@http_client)
|
|
63
|
+
@labels = Labels.new(@http_client)
|
|
64
|
+
@graders = Graders.new(@http_client)
|
|
57
65
|
# Mock overrides registered via register_mock_override, applied to every
|
|
58
66
|
# replay on this client (after any per-call mock_override). Instance
|
|
59
67
|
# state, no global; clear_mock_overrides resets it.
|
|
60
68
|
@mock_overrides = []
|
|
69
|
+
resolved_thread_propagation = trace_across_threads.nil? ? %w[1 true yes].include?(ENV.fetch("BITFAB_TRACE_ACROSS_THREADS", "").strip.downcase) : trace_across_threads
|
|
70
|
+
ThreadPropagation.install if resolved_thread_propagation
|
|
61
71
|
CommitRef.start_resolution
|
|
62
72
|
end
|
|
63
73
|
|
|
@@ -95,6 +105,10 @@ module Bitfab
|
|
|
95
105
|
tracing_enabled?
|
|
96
106
|
end
|
|
97
107
|
|
|
108
|
+
def capture_enabled
|
|
109
|
+
tracing_enabled?
|
|
110
|
+
end
|
|
111
|
+
|
|
98
112
|
# Replay historical traces through a method and create a test run.
|
|
99
113
|
#
|
|
100
114
|
# @param receiver [Object, Class] an instance for instance methods, or a Class for class methods
|
|
@@ -173,8 +187,8 @@ module Bitfab
|
|
|
173
187
|
# begins processing each item. Pair it with on_item_finish to distinguish
|
|
174
188
|
# queued work from in-flight work. A raising callback never crashes the run.
|
|
175
189
|
# @return [Hash] with :items, :test_run_id, :test_run_url
|
|
176
|
-
def replay(receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency:
|
|
177
|
-
name: nil, code_change_description: Replay::CODE_CHANGE_UNSET, code_change_files: Replay::CODE_CHANGE_UNSET, experiment_group_id: nil, dataset_id: nil, dataset_ids: nil, grader_ids: nil, mock: "marked",
|
|
190
|
+
def replay(receiver, method_name = nil, trace_function_key:, limit: nil, trace_ids: nil, concurrency: nil, max_concurrency: ReplayConcurrency::UNSET,
|
|
191
|
+
name: nil, code_change_description: Replay::CODE_CHANGE_UNSET, code_change_files: Replay::CODE_CHANGE_UNSET, experiment_group_id: nil, dataset_id: nil, dataset_ids: nil, grader_ids: nil, mock: "marked", attempts: ReplayConcurrency::UNSET, only_with_assertions: false, dry_run: false,
|
|
178
192
|
adapt_inputs: nil, mock_override: nil, db_branch: nil, on_item_start: nil, on_item_finish: nil, on_progress: nil)
|
|
179
193
|
Replay.run(
|
|
180
194
|
self,
|
|
@@ -184,7 +198,11 @@ module Bitfab
|
|
|
184
198
|
limit:,
|
|
185
199
|
trace_ids:,
|
|
186
200
|
name:,
|
|
201
|
+
concurrency:,
|
|
187
202
|
max_concurrency:,
|
|
203
|
+
attempts:,
|
|
204
|
+
only_with_assertions:,
|
|
205
|
+
dry_run:,
|
|
188
206
|
code_change_description:,
|
|
189
207
|
code_change_files:,
|
|
190
208
|
experiment_group_id:,
|
|
@@ -307,13 +325,14 @@ module Bitfab
|
|
|
307
325
|
# @param trace_function_key [String]
|
|
308
326
|
# @return [BitfabFunction]
|
|
309
327
|
def get_function(trace_function_key)
|
|
328
|
+
@http_client.refresh_simulation_plan
|
|
310
329
|
BitfabFunction.new(self, trace_function_key)
|
|
311
330
|
end
|
|
312
331
|
|
|
313
332
|
# Configure an existing method only when it is discovered beneath a
|
|
314
333
|
# bitfab_trace root owned by this client.
|
|
315
334
|
def node(klass, method_name, name: nil, type: "custom", capture: true,
|
|
316
|
-
test_run_id: nil, mock_on_replay:
|
|
335
|
+
test_run_id: nil, mock_on_replay: nil, finalize: nil)
|
|
317
336
|
Bitfab::Traceable.node(
|
|
318
337
|
klass,
|
|
319
338
|
method_name,
|
|
@@ -327,6 +346,13 @@ module Bitfab
|
|
|
327
346
|
)
|
|
328
347
|
end
|
|
329
348
|
|
|
349
|
+
# Return a handle that updates a persisted trace without an active span.
|
|
350
|
+
# Each handle operation waits for the server and raises on rejection.
|
|
351
|
+
def get_trace(trace_id)
|
|
352
|
+
validate_trace_id(trace_id)
|
|
353
|
+
DetachedTrace.new(self, @http_client, trace_id)
|
|
354
|
+
end
|
|
355
|
+
|
|
330
356
|
# Fetch one persisted span without loading the full trace.
|
|
331
357
|
# Exactly one of id or name is required. Name lookups return the last
|
|
332
358
|
# matching span by default; occurrence also accepts "first" or a zero-based
|
|
@@ -352,11 +378,12 @@ module Bitfab
|
|
|
352
378
|
# Called by Traceable, not intended for direct use.
|
|
353
379
|
def execute_span(trace_function_key:, span_name:, span_type:, function_name:, args:, kwargs:,
|
|
354
380
|
capture_when: "always", mock_on_replay: false, independent_root: false,
|
|
355
|
-
explicit_span_receiver: nil, explicit_span_method_name: nil, test_run_id: nil, finalize: nil
|
|
381
|
+
explicit_span_receiver: nil, explicit_span_method_name: nil, test_run_id: nil, finalize: nil,
|
|
382
|
+
surface: nil, on_start: nil, on_complete: nil, around_body: nil, span_data: {}, managed_root: false)
|
|
356
383
|
# Decide at CALL time, not construction. The key may be set after the
|
|
357
384
|
# client is built (env loaded later), so re-checking per call lets a
|
|
358
385
|
# late-resolved key take effect.
|
|
359
|
-
return yield unless
|
|
386
|
+
return yield unless should_record?
|
|
360
387
|
|
|
361
388
|
resolved_capture_when = capture_when.to_s
|
|
362
389
|
unless ["always", "nested"].include?(resolved_capture_when)
|
|
@@ -395,7 +422,7 @@ module Bitfab
|
|
|
395
422
|
trace_id = if independent_root && ambient_parent
|
|
396
423
|
SecureRandom.uuid
|
|
397
424
|
else
|
|
398
|
-
parent&.dig(:trace_id) || replay_ctx&.dig(:trace_id) || SecureRandom.uuid
|
|
425
|
+
parent&.dig(:trace_id) || SeedContext.current&.dig(:trace_id) || replay_ctx&.dig(:trace_id) || SecureRandom.uuid
|
|
399
426
|
end
|
|
400
427
|
span_id = SecureRandom.uuid
|
|
401
428
|
parent_span_id = parent&.dig(:span_id)
|
|
@@ -409,11 +436,16 @@ module Bitfab
|
|
|
409
436
|
if is_root_span && !TraceState.get(trace_id)
|
|
410
437
|
TraceState.create(
|
|
411
438
|
trace_id,
|
|
439
|
+
trace_function_key:,
|
|
412
440
|
test_run_id: resolved_test_run_id,
|
|
413
|
-
input_source_trace_id: resolved_input_source_trace_id
|
|
441
|
+
input_source_trace_id: resolved_input_source_trace_id,
|
|
442
|
+
replay_attempt: replay_ctx&.dig(:replay_attempt),
|
|
443
|
+
db_snapshot: @db_snapshot
|
|
414
444
|
)
|
|
415
445
|
end
|
|
416
446
|
|
|
447
|
+
on_start&.call(trace_id:, span_id:)
|
|
448
|
+
|
|
417
449
|
# Advance the per-(key, name) call counter for any non-root span under
|
|
418
450
|
# an active mock tree, even when this span won't itself be mocked.
|
|
419
451
|
# Unmarked spans must consume an index so subsequent marked siblings
|
|
@@ -448,8 +480,10 @@ module Bitfab
|
|
|
448
480
|
started_at:,
|
|
449
481
|
test_run_id: resolved_test_run_id,
|
|
450
482
|
input_source_span_id: resolved_input_source_span_id,
|
|
451
|
-
mock_source
|
|
483
|
+
mock_source:,
|
|
484
|
+
instrumentation: (surface == :trace) ? "trace" : "span"
|
|
452
485
|
)
|
|
486
|
+
on_complete&.call(result: mocked_output, error: nil, ended_at: Bitfab.now_iso_timestamp)
|
|
453
487
|
return mocked_output
|
|
454
488
|
end
|
|
455
489
|
end
|
|
@@ -477,6 +511,7 @@ module Bitfab
|
|
|
477
511
|
span_prompt = nil
|
|
478
512
|
completed = false
|
|
479
513
|
@http_client.trace_completion.start(trace_id, span_id)
|
|
514
|
+
recording_override = managed_root ? {} : nil
|
|
480
515
|
|
|
481
516
|
complete = lambda do |final_result, final_error|
|
|
482
517
|
# Never crash the host app due to span building/sending. Idempotent:
|
|
@@ -499,14 +534,18 @@ module Bitfab
|
|
|
499
534
|
end
|
|
500
535
|
end
|
|
501
536
|
|
|
537
|
+
on_complete&.call(result: recorded_result, error: recorded_error, ended_at:)
|
|
538
|
+
|
|
502
539
|
send_span(
|
|
540
|
+
span_data:,
|
|
541
|
+
instrumentation: recording_override&.dig(:instrumentation) || ((surface == :trace) ? "trace" : "span"),
|
|
503
542
|
trace_function_key:,
|
|
504
543
|
trace_id:,
|
|
505
544
|
span_id:,
|
|
506
545
|
parent_span_id:,
|
|
507
|
-
span_name
|
|
508
|
-
span_type
|
|
509
|
-
function_name
|
|
546
|
+
span_name: recording_override&.dig(:name) || span_name,
|
|
547
|
+
span_type: recording_override&.dig(:type) || span_type,
|
|
548
|
+
function_name: recording_override&.dig(:function_name) || function_name,
|
|
510
549
|
contexts: span_contexts,
|
|
511
550
|
prompt: span_prompt,
|
|
512
551
|
args:,
|
|
@@ -567,17 +606,19 @@ module Bitfab
|
|
|
567
606
|
trace_id:,
|
|
568
607
|
span_id:,
|
|
569
608
|
explicit_span_receiver:,
|
|
570
|
-
explicit_span_method_name: explicit_span_method_name&.to_sym
|
|
609
|
+
explicit_span_method_name: explicit_span_method_name&.to_sym,
|
|
610
|
+
surface:, recording_override:
|
|
571
611
|
) do
|
|
612
|
+
invoke = -> { around_body ? around_body.call { yield } : yield }
|
|
572
613
|
result = if explicit_span_receiver
|
|
573
614
|
Subtree.with_borrowed_span(
|
|
574
615
|
trace_id:,
|
|
575
616
|
span_id:,
|
|
576
617
|
receiver: explicit_span_receiver,
|
|
577
618
|
method_name: explicit_span_method_name
|
|
578
|
-
) {
|
|
619
|
+
) { invoke.call }
|
|
579
620
|
else
|
|
580
|
-
|
|
621
|
+
invoke.call
|
|
581
622
|
end
|
|
582
623
|
ensure
|
|
583
624
|
# Capture contexts before the span context is popped
|
|
@@ -610,6 +651,8 @@ module Bitfab
|
|
|
610
651
|
trace_id:,
|
|
611
652
|
span_id:,
|
|
612
653
|
complete:,
|
|
654
|
+
surface:,
|
|
655
|
+
around_body:,
|
|
613
656
|
explicit_span_receiver:,
|
|
614
657
|
explicit_span_method_name:
|
|
615
658
|
)
|
|
@@ -673,6 +716,13 @@ module Bitfab
|
|
|
673
716
|
|
|
674
717
|
# Whether tracing should run, decided lazily at call time. An explicit
|
|
675
718
|
# `enabled: false` short-circuits without ever touching the key.
|
|
719
|
+
def should_record?
|
|
720
|
+
active_run = ReplayContext.current || SeedContext.current
|
|
721
|
+
return false unless capture_configured? || active_run
|
|
722
|
+
|
|
723
|
+
!resolve_api_key.nil?
|
|
724
|
+
end
|
|
725
|
+
|
|
676
726
|
def tracing_enabled?
|
|
677
727
|
return false unless capture_configured?
|
|
678
728
|
|
|
@@ -685,8 +735,8 @@ module Bitfab
|
|
|
685
735
|
# span output. The span is sent exactly once: when iteration finishes,
|
|
686
736
|
# raises, or the wrapper is `.close`d.
|
|
687
737
|
def wrap_enumerator(source, trace_id:, span_id:, complete:, explicit_span_receiver: nil,
|
|
688
|
-
explicit_span_method_name: nil)
|
|
689
|
-
span_entry = {trace_id:, span_id:}
|
|
738
|
+
explicit_span_method_name: nil, surface: nil, around_body: nil)
|
|
739
|
+
span_entry = {trace_id:, span_id:, surface:}
|
|
690
740
|
if explicit_span_receiver
|
|
691
741
|
span_entry[:explicit_span_receiver] = explicit_span_receiver
|
|
692
742
|
span_entry[:explicit_span_method_name] = explicit_span_method_name&.to_sym
|
|
@@ -699,7 +749,7 @@ module Bitfab
|
|
|
699
749
|
# the right parent. The fiber bridge carries the same parent into
|
|
700
750
|
# Enumerator.new / enum_for source fibers on this thread.
|
|
701
751
|
ReplayContext.with_span(trace_id:, span_id:) do
|
|
702
|
-
SpanContext.with_fiber_bridge(trace_id:, span_id:) do
|
|
752
|
+
SpanContext.with_fiber_bridge(trace_id:, span_id:, surface:) do
|
|
703
753
|
SpanContext.stack.push(span_entry)
|
|
704
754
|
begin
|
|
705
755
|
iterate = lambda do
|
|
@@ -708,6 +758,8 @@ module Bitfab
|
|
|
708
758
|
yielder << value
|
|
709
759
|
end
|
|
710
760
|
end
|
|
761
|
+
raw_iterate = iterate
|
|
762
|
+
iterate = -> { around_body.call(&raw_iterate) } if around_body
|
|
711
763
|
if explicit_span_receiver
|
|
712
764
|
Subtree.with_borrowed_span(
|
|
713
765
|
trace_id:,
|
|
@@ -755,6 +807,7 @@ module Bitfab
|
|
|
755
807
|
"ended_at" => ended_at
|
|
756
808
|
}
|
|
757
809
|
|
|
810
|
+
raw_trace["ingestion_type"] = trace_state[:ingestion_type] if trace_state&.dig(:ingestion_type)
|
|
758
811
|
if trace_state&.dig(:name)
|
|
759
812
|
raw_trace["name"] = trace_state[:name]
|
|
760
813
|
end
|
|
@@ -764,6 +817,9 @@ module Bitfab
|
|
|
764
817
|
if trace_state&.dig(:contexts)
|
|
765
818
|
raw_trace["contexts"] = trace_state[:contexts]
|
|
766
819
|
end
|
|
820
|
+
unless trace_state&.dig(:replay_attempt).nil?
|
|
821
|
+
raw_trace["replay_attempt"] = trace_state[:replay_attempt]
|
|
822
|
+
end
|
|
767
823
|
if trace_state&.dig(:input_source_trace_id)
|
|
768
824
|
raw_trace["input_source_trace_id"] = trace_state[:input_source_trace_id]
|
|
769
825
|
end
|
|
@@ -822,7 +878,8 @@ module Bitfab
|
|
|
822
878
|
def send_span(trace_function_key:, trace_id:, span_id:, parent_span_id:,
|
|
823
879
|
span_name:, span_type:, function_name:, contexts:, prompt:, args:, kwargs:, result:, error:,
|
|
824
880
|
started_at:, ended_at:, test_run_id: nil, input_source_span_id: nil, mocked: false,
|
|
825
|
-
mock_target: nil, mock_source: nil)
|
|
881
|
+
mock_target: nil, mock_source: nil, span_data: {}, instrumentation: "span")
|
|
882
|
+
extra_span_data = span_data
|
|
826
883
|
# If drop() was called on this trace, suppress the span PAYLOAD upload for
|
|
827
884
|
# every span that completes after the flag was set. The trace completion
|
|
828
885
|
# still rides out with dropped: true, so the server scrubs any sibling
|
|
@@ -849,6 +906,7 @@ module Bitfab
|
|
|
849
906
|
"output" => human_output,
|
|
850
907
|
"function_name" => function_name
|
|
851
908
|
}
|
|
909
|
+
span_data.merge!(extra_span_data)
|
|
852
910
|
span_data["input_serialized"] = marshalled_input if marshalled_input
|
|
853
911
|
span_data["output_serialized"] = marshalled_output if marshalled_output
|
|
854
912
|
if error
|
|
@@ -863,7 +921,8 @@ module Bitfab
|
|
|
863
921
|
"trace_id" => trace_id,
|
|
864
922
|
"started_at" => started_at,
|
|
865
923
|
"ended_at" => ended_at,
|
|
866
|
-
"span_data" => span_data
|
|
924
|
+
"span_data" => span_data,
|
|
925
|
+
"span_origin" => Bitfab.make_span_origin(instrumentation)
|
|
867
926
|
}
|
|
868
927
|
raw_span["parent_id"] = parent_span_id if parent_span_id
|
|
869
928
|
raw_span["input_source_span_id"] = input_source_span_id if input_source_span_id
|
|
@@ -875,6 +934,7 @@ module Bitfab
|
|
|
875
934
|
"source" => "ruby-sdk-function",
|
|
876
935
|
"sourceTraceId" => trace_id,
|
|
877
936
|
"traceFunctionKey" => trace_function_key,
|
|
937
|
+
"rootTraceFunctionKey" => TraceState.get(trace_id)&.dig(:trace_function_key) || trace_function_key,
|
|
878
938
|
"rawSpan" => raw_span
|
|
879
939
|
}
|
|
880
940
|
payload["testRunId"] = test_run_id if test_run_id
|
|
@@ -1029,7 +1089,7 @@ module Bitfab
|
|
|
1029
1089
|
# output as the result and no error.
|
|
1030
1090
|
def send_mocked_span(trace_function_key:, trace_id:, span_id:, parent_span_id:,
|
|
1031
1091
|
span_name:, span_type:, function_name:, args:, kwargs:, mocked_output:,
|
|
1032
|
-
started_at:, test_run_id:, input_source_span_id:, mock_source:)
|
|
1092
|
+
started_at:, test_run_id:, input_source_span_id:, mock_source:, instrumentation: "span")
|
|
1033
1093
|
ended_at = Bitfab.now_iso_timestamp
|
|
1034
1094
|
send_span(
|
|
1035
1095
|
trace_function_key:,
|
|
@@ -1050,6 +1110,7 @@ module Bitfab
|
|
|
1050
1110
|
test_run_id:,
|
|
1051
1111
|
input_source_span_id:,
|
|
1052
1112
|
mocked: true,
|
|
1113
|
+
instrumentation:,
|
|
1053
1114
|
# Both paths short-circuit the call, so the target is always the
|
|
1054
1115
|
# output; only where the value came from differs.
|
|
1055
1116
|
mock_target: "output",
|
|
@@ -1093,7 +1154,7 @@ module Bitfab
|
|
|
1093
1154
|
# values warn once and default to "always"
|
|
1094
1155
|
# @param mock_on_replay [Boolean] mark this span for the default "marked"
|
|
1095
1156
|
# mock strategy. A missing selected occurrence fails closed.
|
|
1096
|
-
def wrap(klass, method_name, name: nil, type: "custom", capture_when: "always", mock_on_replay: false)
|
|
1157
|
+
def wrap(klass, method_name, name: nil, type: "custom", capture_when: "always", mock_on_replay: false, finalize: nil, test_run_id: nil)
|
|
1097
1158
|
Bitfab::Traceable.wrap(
|
|
1098
1159
|
klass,
|
|
1099
1160
|
method_name,
|
|
@@ -1102,6 +1163,8 @@ module Bitfab
|
|
|
1102
1163
|
type:,
|
|
1103
1164
|
capture_when:,
|
|
1104
1165
|
mock_on_replay:,
|
|
1166
|
+
finalize:,
|
|
1167
|
+
test_run_id:,
|
|
1105
1168
|
client: @client
|
|
1106
1169
|
)
|
|
1107
1170
|
end
|
|
@@ -1109,7 +1172,7 @@ module Bitfab
|
|
|
1109
1172
|
# Experimentally trace an existing method and its first-party call subtree.
|
|
1110
1173
|
def trace(klass, method_name, name: nil, type: "custom",
|
|
1111
1174
|
max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
|
|
1112
|
-
exclude: [], include_wrappers: false)
|
|
1175
|
+
exclude: [], include_wrappers: false, mock_on_replay_default: false)
|
|
1113
1176
|
Bitfab::Traceable.trace(
|
|
1114
1177
|
klass, method_name,
|
|
1115
1178
|
trace_function_key: @trace_function_key,
|
|
@@ -1119,6 +1182,7 @@ module Bitfab
|
|
|
1119
1182
|
max_spans:,
|
|
1120
1183
|
exclude:,
|
|
1121
1184
|
include_wrappers:,
|
|
1185
|
+
mock_on_replay_default:,
|
|
1122
1186
|
client: @client
|
|
1123
1187
|
)
|
|
1124
1188
|
end
|
data/lib/bitfab/db_snapshot.rb
CHANGED
|
@@ -6,8 +6,8 @@ module Bitfab
|
|
|
6
6
|
# Every root trace carries a snapshot ref that pins the DB state at trace
|
|
7
7
|
# open by wall-clock timestamp. Capturing the timestamp is free (no IO) and
|
|
8
8
|
# harmless, so it happens on every trace regardless of configuration: that
|
|
9
|
-
# lets any trace be replayed against a historical branch later. The provider
|
|
10
|
-
# is resolved at replay time. Mirrors the TypeScript and Python SDKs'
|
|
9
|
+
# lets any trace be replayed against a historical branch later. The optional configured provider
|
|
10
|
+
# is captured too; otherwise it is resolved at replay time. Mirrors the TypeScript and Python SDKs'
|
|
11
11
|
# +DbSnapshotRef+ wire shape (camelCase key, since the server speaks the same
|
|
12
12
|
# protocol for every SDK).
|
|
13
13
|
module DbSnapshot
|
|
@@ -17,13 +17,26 @@ module Bitfab
|
|
|
17
17
|
#
|
|
18
18
|
# Stores only the wall clock the SDK observed immediately before invoking
|
|
19
19
|
# the wrapped function; the server-side resolver uses that as the snapshot
|
|
20
|
-
# timestamp.
|
|
20
|
+
# timestamp. A configured provider is included; otherwise it is resolved at replay time.
|
|
21
21
|
#
|
|
22
22
|
# @param sdk_wall_clock_before_fn [String] ISO wall-clock timestamp the SDK
|
|
23
23
|
# observed immediately before invoking the wrapped function.
|
|
24
24
|
# @return [Hash] snapshot ref with a single camelCase +sdkWallClockBeforeFn+ key.
|
|
25
|
-
def build_snapshot_ref(sdk_wall_clock_before_fn)
|
|
26
|
-
{"sdkWallClockBeforeFn" => sdk_wall_clock_before_fn}
|
|
25
|
+
def build_snapshot_ref(sdk_wall_clock_before_fn, config: nil)
|
|
26
|
+
ref = {"sdkWallClockBeforeFn" => sdk_wall_clock_before_fn}
|
|
27
|
+
ref["provider"] = config.fetch("provider") if config
|
|
28
|
+
ref
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def normalize_config(config)
|
|
32
|
+
return nil if config.nil?
|
|
33
|
+
raise ArgumentError, "db_snapshot must be a Hash with provider: neon." unless config.is_a?(Hash)
|
|
34
|
+
|
|
35
|
+
provider = config[:provider] || config["provider"]
|
|
36
|
+
unless provider.to_s == "neon"
|
|
37
|
+
raise ArgumentError, "db_snapshot.provider #{provider.inspect} is not supported. Supported providers: neon."
|
|
38
|
+
end
|
|
39
|
+
{"provider" => "neon"}.freeze
|
|
27
40
|
end
|
|
28
41
|
end
|
|
29
42
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
class DetachedTrace
|
|
5
|
+
attr_reader :trace_id
|
|
6
|
+
|
|
7
|
+
def initialize(client, http_client, trace_id)
|
|
8
|
+
@client = client
|
|
9
|
+
@http_client = http_client
|
|
10
|
+
@trace_id = trace_id
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add_context(context)
|
|
14
|
+
return unless context.is_a?(Hash)
|
|
15
|
+
|
|
16
|
+
patch("appendContexts" => [context])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def set_metadata(metadata)
|
|
20
|
+
return unless metadata.is_a?(Hash)
|
|
21
|
+
|
|
22
|
+
patch("mergeMetadata" => metadata)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def set_session_id(session_id)
|
|
26
|
+
return unless session_id.is_a?(String) && !session_id.empty?
|
|
27
|
+
|
|
28
|
+
patch("setSessionId" => session_id)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def set_name(name)
|
|
32
|
+
return unless name.is_a?(String) && !name.empty?
|
|
33
|
+
|
|
34
|
+
patch("setName" => name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def patch(payload)
|
|
40
|
+
return unless @client.enabled
|
|
41
|
+
|
|
42
|
+
@http_client.request("/api/sdk/traces/#{@trace_id}", payload, method: "PATCH", timeout: 10)
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Bitfab
|
|
6
|
+
class Graders
|
|
7
|
+
def initialize(http_client)
|
|
8
|
+
@http_client = http_client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get_labels(trace_ids: nil, grader_id: nil, limit: nil)
|
|
12
|
+
if (trace_ids.nil? || trace_ids.empty?) && grader_id.nil?
|
|
13
|
+
raise ArgumentError, "Pass trace_ids, grader_id, or both: there is nothing to read otherwise."
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
params = {}
|
|
17
|
+
params["traceIds"] = trace_ids.join(",") unless trace_ids.nil? || trace_ids.empty?
|
|
18
|
+
params["graderId"] = grader_id unless grader_id.nil?
|
|
19
|
+
params["limit"] = limit unless limit.nil?
|
|
20
|
+
@http_client.get("/api/sdk/graderLabels?#{URI.encode_www_form(params)}")["labels"]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|