bitfab 0.51.8 → 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 +7 -7
- 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 +107 -33
- data/lib/bitfab/constants.rb +1 -0
- data/lib/bitfab/db_snapshot.rb +18 -5
- data/lib/bitfab/detached_trace.rb +46 -0
- data/lib/bitfab/env.rb +27 -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 +33 -1
data/lib/bitfab/subtree.rb
CHANGED
|
@@ -6,6 +6,8 @@ require "time"
|
|
|
6
6
|
|
|
7
7
|
module Bitfab
|
|
8
8
|
# Captures Ruby method calls beneath one traced root via TracePoint.
|
|
9
|
+
class MixedTracingError < StandardError; end
|
|
10
|
+
|
|
9
11
|
module Subtree
|
|
10
12
|
DEFAULT_MAX_DEPTH = 30
|
|
11
13
|
DEFAULT_MAX_SPANS = 500
|
|
@@ -46,11 +48,115 @@ module Bitfab
|
|
|
46
48
|
:kwargs,
|
|
47
49
|
:started_at,
|
|
48
50
|
:error,
|
|
51
|
+
:span_type,
|
|
52
|
+
:test_run_id,
|
|
53
|
+
:span_data,
|
|
49
54
|
keyword_init: true
|
|
50
55
|
)
|
|
51
56
|
|
|
52
57
|
module_function
|
|
53
58
|
|
|
59
|
+
def assert_surface!(surface)
|
|
60
|
+
enclosing = current_session ? :trace : SpanContext.current&.dig(:surface)
|
|
61
|
+
return unless enclosing && enclosing != surface
|
|
62
|
+
|
|
63
|
+
raise MixedTracingError,
|
|
64
|
+
"opt-in and opt-out tracing cannot be mixed: #{surface} entered inside #{enclosing}. " \
|
|
65
|
+
"Use bitfab_node inside bitfab_trace, or use bitfab_span throughout the workflow."
|
|
66
|
+
rescue MixedTracingError
|
|
67
|
+
raise
|
|
68
|
+
rescue
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def nested_root_suppressed?
|
|
73
|
+
current_session && (ReplayContext.current || (defined?(SeedContext) && SeedContext.current))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def qualified_name(receiver, method_name)
|
|
77
|
+
owner = receiver.is_a?(Module) ? receiver.name : receiver.class.name
|
|
78
|
+
(owner && !owner.empty? && owner != "Object") ? "#{owner}.#{method_name}" : method_name.to_s
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class ConfiguredCall
|
|
82
|
+
attr_reader :span_data, :owner
|
|
83
|
+
|
|
84
|
+
def initialize(receiver:, method_name:, name:, type:, args:, kwargs:, test_run_id: nil,
|
|
85
|
+
nested_trace_key: nil)
|
|
86
|
+
@receiver = receiver
|
|
87
|
+
@method_name = method_name
|
|
88
|
+
@nested_trace_key = nested_trace_key
|
|
89
|
+
@span_data = {}
|
|
90
|
+
@completed = false
|
|
91
|
+
@records = Subtree.active_sessions.map do |session|
|
|
92
|
+
omitted = session.excluded?(method_name)
|
|
93
|
+
entry = unless omitted
|
|
94
|
+
session.configured_entry(name:, type:, args:, kwargs:, test_run_id:)
|
|
95
|
+
end
|
|
96
|
+
[session, entry, !omitted]
|
|
97
|
+
end
|
|
98
|
+
@owner = @records.find { |_, entry, _| entry }&.first unless nested_trace_key
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def start(trace_id:, span_id:)
|
|
102
|
+
@records.each do |session, entry, _|
|
|
103
|
+
next unless entry
|
|
104
|
+
|
|
105
|
+
if session.equal?(@owner)
|
|
106
|
+
entry.span_id = span_id
|
|
107
|
+
session.discard_configured(entry)
|
|
108
|
+
elsif @nested_trace_key
|
|
109
|
+
entry.span_data = {
|
|
110
|
+
"nested_trace_id" => trace_id,
|
|
111
|
+
"nested_trace_function_key" => @nested_trace_key,
|
|
112
|
+
"nested_root_span_id" => span_id
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
if @nested_trace_key && (enclosing = @records.find { |_, entry, _| entry })
|
|
117
|
+
session, entry = enclosing
|
|
118
|
+
@span_data.merge!(
|
|
119
|
+
"enclosing_trace_id" => session.trace_id,
|
|
120
|
+
"enclosing_trace_function_key" => session.trace_function_key,
|
|
121
|
+
"enclosing_span_id" => entry.span_id
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def run
|
|
127
|
+
boundaries = @records.filter_map do |session, entry, counts_toward_depth|
|
|
128
|
+
next if session.equal?(@owner)
|
|
129
|
+
|
|
130
|
+
[session, session.push_boundary(
|
|
131
|
+
receiver: @receiver, method_name: @method_name,
|
|
132
|
+
span_id: entry&.span_id, counts_toward_depth:
|
|
133
|
+
)]
|
|
134
|
+
end
|
|
135
|
+
yield
|
|
136
|
+
ensure
|
|
137
|
+
boundaries&.reverse_each { |session, boundary| session.pop_boundary(boundary) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def with_owner_parent(&body)
|
|
141
|
+
return yield unless @owner
|
|
142
|
+
|
|
143
|
+
SpanContext.with_span(trace_id: @owner.trace_id,
|
|
144
|
+
span_id: @owner.current_parent_span_id, surface: :trace, &body)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def complete(result:, error:, ended_at:)
|
|
148
|
+
return if @completed
|
|
149
|
+
|
|
150
|
+
@completed = true
|
|
151
|
+
@records.each do |session, entry, _|
|
|
152
|
+
next if !entry || session.equal?(@owner)
|
|
153
|
+
|
|
154
|
+
entry.error = error
|
|
155
|
+
session.emit_configured(entry, result:)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
54
160
|
def prepare(root_source:, exclude:)
|
|
55
161
|
path = root_source[0]
|
|
56
162
|
root = first_party_root(path)
|
|
@@ -64,7 +170,7 @@ module Bitfab
|
|
|
64
170
|
end
|
|
65
171
|
|
|
66
172
|
def capture(client:, trace_function_key:, config:, max_depth:, max_spans:, include_wrappers:,
|
|
67
|
-
root_receiver:, root_method_name:)
|
|
173
|
+
root_receiver:, root_method_name:, mock_on_replay_default: false)
|
|
68
174
|
return yield unless config.root
|
|
69
175
|
|
|
70
176
|
span = SpanContext.current || ReplayContext.current_span
|
|
@@ -83,6 +189,7 @@ module Bitfab
|
|
|
83
189
|
max_depth:,
|
|
84
190
|
max_spans:,
|
|
85
191
|
include_wrappers:,
|
|
192
|
+
mock_on_replay_default:,
|
|
86
193
|
root_receiver:,
|
|
87
194
|
root_method_name:
|
|
88
195
|
)
|
|
@@ -294,7 +401,7 @@ module Bitfab
|
|
|
294
401
|
end
|
|
295
402
|
|
|
296
403
|
class Session
|
|
297
|
-
attr_reader :client, :parent_session, :trace_function_key, :trace_id, :deferred_frames
|
|
404
|
+
attr_reader :client, :parent_session, :trace_function_key, :trace_id, :deferred_frames, :mock_on_replay_default
|
|
298
405
|
|
|
299
406
|
def active? = @active
|
|
300
407
|
def deferred? = @deferred
|
|
@@ -305,7 +412,8 @@ module Bitfab
|
|
|
305
412
|
|
|
306
413
|
def initialize(client:, trace_function_key:, trace_id:, root_span_id:, test_run_id:,
|
|
307
414
|
input_source_span_id:, config:, max_depth:, max_spans:, include_wrappers:,
|
|
308
|
-
root_receiver:, root_method_name:)
|
|
415
|
+
root_receiver:, root_method_name:, mock_on_replay_default: false)
|
|
416
|
+
@mock_on_replay_default = mock_on_replay_default
|
|
309
417
|
@client = client
|
|
310
418
|
@trace_function_key = trace_function_key
|
|
311
419
|
@trace_id = trace_id
|
|
@@ -322,6 +430,7 @@ module Bitfab
|
|
|
322
430
|
@exclude = config.exclude
|
|
323
431
|
@include_wrappers = include_wrappers
|
|
324
432
|
@stacks = {}
|
|
433
|
+
@configured_entries = {}
|
|
325
434
|
@fiber_parents = {}
|
|
326
435
|
@suppressed_depths = {}
|
|
327
436
|
@owned_paths = {}
|
|
@@ -419,6 +528,29 @@ module Bitfab
|
|
|
419
528
|
true
|
|
420
529
|
end
|
|
421
530
|
|
|
531
|
+
def configured_entry(name:, type:, args:, kwargs:, test_run_id:)
|
|
532
|
+
return unless reserve_node
|
|
533
|
+
|
|
534
|
+
entry = OpenSpan.new(
|
|
535
|
+
span_id: SecureRandom.uuid,
|
|
536
|
+
parent_span_id: current_parent_span_id,
|
|
537
|
+
name:, span_type: type, args:, kwargs:, test_run_id:, started_at: now,
|
|
538
|
+
span_data: {}
|
|
539
|
+
)
|
|
540
|
+
@configured_entries[entry.object_id] = entry
|
|
541
|
+
entry
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def discard_configured(entry)
|
|
545
|
+
@configured_entries.delete(entry.object_id)
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def emit_configured(entry, result:)
|
|
549
|
+
return unless discard_configured(entry)
|
|
550
|
+
|
|
551
|
+
emit(entry, result:)
|
|
552
|
+
end
|
|
553
|
+
|
|
422
554
|
def current_parent_span_id
|
|
423
555
|
stack = @stacks[Fiber.current.object_id] || []
|
|
424
556
|
stack.reverse_each.find { |entry| entry.span_id }&.span_id ||
|
|
@@ -465,6 +597,11 @@ module Bitfab
|
|
|
465
597
|
private
|
|
466
598
|
|
|
467
599
|
def finish_open_spans
|
|
600
|
+
@configured_entries.each_value do |entry|
|
|
601
|
+
entry.error ||= "Subtree capture ended before the call returned"
|
|
602
|
+
emit(entry, result: nil)
|
|
603
|
+
end
|
|
604
|
+
@configured_entries.clear
|
|
468
605
|
@stacks.each_value do |stack|
|
|
469
606
|
stack.reverse_each do |entry|
|
|
470
607
|
next unless entry.is_a?(OpenSpan)
|
|
@@ -590,7 +727,9 @@ module Bitfab
|
|
|
590
727
|
span_id: entry.span_id,
|
|
591
728
|
parent_span_id: entry.parent_span_id,
|
|
592
729
|
span_name: entry.name,
|
|
593
|
-
span_type: "function",
|
|
730
|
+
span_type: entry.span_type || "function",
|
|
731
|
+
instrumentation: "trace",
|
|
732
|
+
span_data: entry.span_data || {},
|
|
594
733
|
function_name: entry.name,
|
|
595
734
|
contexts: nil,
|
|
596
735
|
prompt: nil,
|
|
@@ -600,7 +739,7 @@ module Bitfab
|
|
|
600
739
|
error: entry.error,
|
|
601
740
|
started_at: entry.started_at,
|
|
602
741
|
ended_at: now,
|
|
603
|
-
test_run_id: @test_run_id,
|
|
742
|
+
test_run_id: entry.test_run_id || @test_run_id,
|
|
604
743
|
input_source_span_id: @input_source_span_id
|
|
605
744
|
)
|
|
606
745
|
rescue Exception # rubocop:disable Lint/RescueException
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
module ThreadPropagation
|
|
5
|
+
ORIGIN_KEY = :__bitfab_submit_origin
|
|
6
|
+
SDK_PATH = File.expand_path(__dir__) + File::SEPARATOR
|
|
7
|
+
@installed = false
|
|
8
|
+
@mutex = Mutex.new
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def install
|
|
13
|
+
@mutex.synchronize do
|
|
14
|
+
Thread.singleton_class.prepend(ThreadMethods) unless Thread.singleton_class.ancestors.include?(ThreadMethods)
|
|
15
|
+
@installed = true
|
|
16
|
+
end
|
|
17
|
+
begin
|
|
18
|
+
require "concurrent-ruby"
|
|
19
|
+
Concurrent::ThreadPoolExecutor.prepend(PoolMethods) unless Concurrent::ThreadPoolExecutor.ancestors.include?(PoolMethods)
|
|
20
|
+
rescue LoadError
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def installed?
|
|
26
|
+
@installed
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def uninstall
|
|
30
|
+
@installed = false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def runtime
|
|
34
|
+
current = Thread.current
|
|
35
|
+
data = {"thread_id" => current.object_id, "thread_name" => current.name || ((current == Thread.main) ? "main" : "thread-#{current.object_id}")}
|
|
36
|
+
if (origin = current.thread_variable_get(ORIGIN_KEY))
|
|
37
|
+
data["parent_thread_id"] = origin["thread_id"]
|
|
38
|
+
data["submit_thread_id"] = origin["thread_id"]
|
|
39
|
+
data["submit_thread_name"] = origin["thread_name"]
|
|
40
|
+
end
|
|
41
|
+
data
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def bind(block)
|
|
45
|
+
stack = SpanContext.stack.dup
|
|
46
|
+
stack << SpanContext.current if stack.empty? && SpanContext.current
|
|
47
|
+
replay = ReplayContext.current
|
|
48
|
+
seed = SeedContext.current
|
|
49
|
+
return block if stack.empty? && replay.nil? && seed.nil?
|
|
50
|
+
|
|
51
|
+
origin = runtime.slice("thread_id", "thread_name")
|
|
52
|
+
lambda do |*args, **kwargs|
|
|
53
|
+
current = Thread.current
|
|
54
|
+
previous_stack = current[SpanContext::STACK_KEY]
|
|
55
|
+
previous_bridge = current.thread_variable_get(SpanContext::FIBER_BRIDGE_STACK_KEY)
|
|
56
|
+
previous_replay = current.thread_variable_get(REPLAY_CONTEXT_KEY)
|
|
57
|
+
previous_seed = current.thread_variable_get(SeedContext::KEY)
|
|
58
|
+
previous_origin = current.thread_variable_get(ORIGIN_KEY)
|
|
59
|
+
local_replay = replay&.merge(span_stack: replay.fetch(:span_stack, []).dup)
|
|
60
|
+
current[SpanContext::STACK_KEY] = stack.dup
|
|
61
|
+
current.thread_variable_set(SpanContext::FIBER_BRIDGE_STACK_KEY, [])
|
|
62
|
+
current.thread_variable_set(REPLAY_CONTEXT_KEY, local_replay)
|
|
63
|
+
current.thread_variable_set(SeedContext::KEY, seed)
|
|
64
|
+
current.thread_variable_set(ORIGIN_KEY, origin)
|
|
65
|
+
begin
|
|
66
|
+
block.call(*args, **kwargs)
|
|
67
|
+
ensure
|
|
68
|
+
replay[:db_snapshot_accessed] = true if replay && local_replay[:db_snapshot_accessed]
|
|
69
|
+
current[SpanContext::STACK_KEY] = previous_stack
|
|
70
|
+
current.thread_variable_set(SpanContext::FIBER_BRIDGE_STACK_KEY, previous_bridge)
|
|
71
|
+
current.thread_variable_set(REPLAY_CONTEXT_KEY, previous_replay)
|
|
72
|
+
current.thread_variable_set(SeedContext::KEY, previous_seed)
|
|
73
|
+
current.thread_variable_set(ORIGIN_KEY, previous_origin)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def internal?(block)
|
|
79
|
+
path = block.source_location&.first
|
|
80
|
+
path && (path.start_with?(SDK_PATH) || path.include?("/opentelemetry") || path.include?("/concurrent-ruby"))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module ThreadMethods
|
|
84
|
+
%i[new start fork].each do |method_name|
|
|
85
|
+
define_method(method_name) do |*args, &block|
|
|
86
|
+
return super(*args, &block) if !ThreadPropagation.installed? || block.nil? || ThreadPropagation.internal?(block)
|
|
87
|
+
|
|
88
|
+
bound = begin
|
|
89
|
+
ThreadPropagation.bind(block)
|
|
90
|
+
rescue
|
|
91
|
+
block
|
|
92
|
+
end
|
|
93
|
+
super(*args, &bound)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
module PoolMethods
|
|
99
|
+
def post(*args, &block)
|
|
100
|
+
return super unless ThreadPropagation.installed? && block
|
|
101
|
+
|
|
102
|
+
bound = begin
|
|
103
|
+
ThreadPropagation.bind(block)
|
|
104
|
+
rescue
|
|
105
|
+
block
|
|
106
|
+
end
|
|
107
|
+
super(*args, &bound)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/bitfab/traceable.rb
CHANGED
|
@@ -53,32 +53,28 @@ module Bitfab
|
|
|
53
53
|
# preserve the bound client through the fluent wrapper, matching Python's
|
|
54
54
|
# `BitfabFunction.span()` and TypeScript's `BitfabFunction.withSpan()`.
|
|
55
55
|
def self.wrap(klass, method_name, trace_function_key:, name: nil, type: "custom",
|
|
56
|
-
capture_when: "always", mock_on_replay: false, client: nil)
|
|
57
|
-
span_name = name || method_name.to_s
|
|
56
|
+
capture_when: "always", mock_on_replay: false, finalize: nil, test_run_id: nil, client: nil)
|
|
58
57
|
method_name_str = method_name.to_s
|
|
59
58
|
bound_client = client
|
|
60
|
-
|
|
61
59
|
wrapper = Module.new do
|
|
62
60
|
define_method(method_name) do |*args, **kwargs, &block|
|
|
63
|
-
target_client = bound_client || Bitfab.
|
|
61
|
+
target_client = bound_client || Bitfab.client_or_nil
|
|
62
|
+
next super(*args, **kwargs, &block) unless target_client
|
|
63
|
+
|
|
64
|
+
Subtree.assert_surface!(:span) if target_client.send(:should_record?)
|
|
64
65
|
target_client.send(:execute_span,
|
|
65
66
|
trace_function_key:,
|
|
66
|
-
span_name
|
|
67
|
-
span_type: type,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
kwargs:,
|
|
71
|
-
capture_when:,
|
|
72
|
-
mock_on_replay:,
|
|
73
|
-
explicit_span_receiver: self,
|
|
74
|
-
explicit_span_method_name: method_name) do
|
|
67
|
+
span_name: name || Subtree.qualified_name(self, method_name),
|
|
68
|
+
span_type: type, function_name: method_name_str, args:, kwargs:,
|
|
69
|
+
capture_when:, mock_on_replay:, finalize:, test_run_id:, surface: :span,
|
|
70
|
+
explicit_span_receiver: self, explicit_span_method_name: method_name) do
|
|
75
71
|
super(*args, **kwargs, &block)
|
|
76
72
|
end
|
|
77
73
|
end
|
|
78
74
|
end
|
|
79
|
-
|
|
80
75
|
wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
|
|
81
76
|
wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
|
|
77
|
+
preserve_visibility(klass, wrapper, method_name)
|
|
82
78
|
klass.prepend(wrapper)
|
|
83
79
|
end
|
|
84
80
|
|
|
@@ -87,44 +83,45 @@ module Bitfab
|
|
|
87
83
|
# beneath it record automatically as function spans.
|
|
88
84
|
def self.trace(klass, method_name, trace_function_key:, name: nil, type: "custom",
|
|
89
85
|
max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
|
|
90
|
-
exclude: [], include_wrappers: false, client: nil)
|
|
91
|
-
|
|
92
|
-
capture_config = Subtree.prepare(root_source: source, exclude:)
|
|
93
|
-
span_name = name || method_name.to_s
|
|
86
|
+
exclude: [], include_wrappers: false, mock_on_replay_default: false, client: nil)
|
|
87
|
+
capture_config = Subtree.prepare(root_source: source_of(klass, method_name), exclude:)
|
|
94
88
|
method_name_str = method_name.to_s
|
|
95
89
|
bound_client = client
|
|
96
|
-
|
|
97
90
|
wrapper = Module.new do
|
|
98
91
|
define_method(method_name) do |*args, **kwargs, &block|
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
target_client = bound_client || Bitfab.client_or_nil
|
|
93
|
+
next super(*args, **kwargs, &block) unless target_client&.send(:should_record?)
|
|
94
|
+
|
|
95
|
+
Subtree.assert_surface!(:trace)
|
|
96
|
+
next super(*args, **kwargs, &block) if Subtree.nested_root_suppressed?
|
|
97
|
+
|
|
98
|
+
span_name = name || Subtree.qualified_name(self, method_name)
|
|
99
|
+
managed = SpanContext.current&.dig(:recording_override)
|
|
100
|
+
if managed && managed.empty? && (ReplayContext.current || SeedContext.current)
|
|
101
|
+
managed.merge!(name: span_name, type:, function_name: method_name_str, instrumentation: "trace")
|
|
102
|
+
SpanContext.current[:surface] = :trace
|
|
103
|
+
next Subtree.capture(
|
|
104
|
+
client: target_client, trace_function_key:, config: capture_config,
|
|
105
|
+
max_depth:, max_spans:, include_wrappers:, mock_on_replay_default:,
|
|
106
|
+
root_receiver: self, root_method_name: method_name
|
|
107
|
+
) { super(*args, **kwargs, &block) }
|
|
101
108
|
end
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
target_client.send(
|
|
105
|
-
:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
args:,
|
|
111
|
-
kwargs:,
|
|
112
|
-
independent_root: true
|
|
113
|
-
) do
|
|
109
|
+
call = Subtree::ConfiguredCall.new(receiver: self, method_name:, name: span_name,
|
|
110
|
+
type:, args:, kwargs:, nested_trace_key: trace_function_key)
|
|
111
|
+
target_client.send(:execute_span,
|
|
112
|
+
trace_function_key:, span_name:, span_type: type,
|
|
113
|
+
function_name: method_name_str, args:, kwargs:, independent_root: true,
|
|
114
|
+
surface: :trace, span_data: call.span_data,
|
|
115
|
+
on_start: call.method(:start), on_complete: call.method(:complete),
|
|
116
|
+
around_body: call.method(:run)) do
|
|
114
117
|
Subtree.capture(
|
|
115
|
-
client: target_client,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
max_depth:,
|
|
119
|
-
max_spans:,
|
|
120
|
-
include_wrappers:,
|
|
121
|
-
root_receiver: self,
|
|
122
|
-
root_method_name: method_name
|
|
118
|
+
client: target_client, trace_function_key:, config: capture_config,
|
|
119
|
+
max_depth:, max_spans:, include_wrappers:, mock_on_replay_default:,
|
|
120
|
+
root_receiver: self, root_method_name: method_name
|
|
123
121
|
) { super(*args, **kwargs, &block) }
|
|
124
122
|
end
|
|
125
123
|
end
|
|
126
124
|
end
|
|
127
|
-
|
|
128
125
|
wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
|
|
129
126
|
wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
|
|
130
127
|
preserve_visibility(klass, wrapper, method_name)
|
|
@@ -134,55 +131,42 @@ module Bitfab
|
|
|
134
131
|
# Configure an existing method only when it is discovered beneath a
|
|
135
132
|
# bitfab_trace root. Outside subtree capture the method runs unchanged.
|
|
136
133
|
def self.node(klass, method_name, name: nil, type: "custom", capture: true,
|
|
137
|
-
test_run_id: nil, mock_on_replay:
|
|
134
|
+
test_run_id: nil, mock_on_replay: nil, finalize: nil, client: nil)
|
|
138
135
|
validate_node_options!(capture:, mock_on_replay:)
|
|
139
|
-
span_name = name || method_name.to_s
|
|
140
136
|
method_name_str = method_name.to_s
|
|
141
137
|
bound_client = client
|
|
142
|
-
|
|
143
138
|
wrapper = Module.new do
|
|
144
139
|
define_method(method_name) do |*args, **kwargs, &block|
|
|
140
|
+
Subtree.assert_surface!(:trace)
|
|
145
141
|
session = Subtree.current_session
|
|
146
142
|
if !session || (bound_client && !session.client.equal?(bound_client))
|
|
147
143
|
next super(*args, **kwargs, &block)
|
|
148
144
|
end
|
|
149
|
-
|
|
150
145
|
unless capture
|
|
151
146
|
next Subtree.with_suppressed_node(receiver: self, method_name:) do
|
|
152
147
|
super(*args, **kwargs, &block)
|
|
153
148
|
end
|
|
154
149
|
end
|
|
155
150
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
span_name = name || Subtree.qualified_name(self, method_name)
|
|
152
|
+
call = Subtree::ConfiguredCall.new(receiver: self, method_name:, name: span_name,
|
|
153
|
+
type:, args:, kwargs:, test_run_id:)
|
|
154
|
+
unless call.owner
|
|
155
|
+
next call.run { super(*args, **kwargs, &block) }
|
|
160
156
|
end
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
policy = mock_on_replay.nil? ? call.owner.mock_on_replay_default : mock_on_replay
|
|
158
|
+
call.with_owner_parent do
|
|
159
|
+
call.owner.client.send(:execute_span,
|
|
160
|
+
trace_function_key: call.owner.trace_function_key,
|
|
161
|
+
span_name:, span_type: type, function_name: method_name_str,
|
|
162
|
+
args:, kwargs:, capture_when: "nested", mock_on_replay: policy,
|
|
163
|
+
explicit_span_receiver: self, explicit_span_method_name: method_name,
|
|
164
|
+
test_run_id:, finalize:, surface: :trace,
|
|
165
|
+
on_start: call.method(:start), on_complete: call.method(:complete),
|
|
166
|
+
around_body: call.method(:run)) { super(*args, **kwargs, &block) }
|
|
166
167
|
end
|
|
167
|
-
|
|
168
|
-
session.client.send(
|
|
169
|
-
:execute_span,
|
|
170
|
-
trace_function_key: session.trace_function_key,
|
|
171
|
-
span_name:,
|
|
172
|
-
span_type: type,
|
|
173
|
-
function_name: method_name_str,
|
|
174
|
-
args:,
|
|
175
|
-
kwargs:,
|
|
176
|
-
capture_when: "nested",
|
|
177
|
-
mock_on_replay:,
|
|
178
|
-
explicit_span_receiver: self,
|
|
179
|
-
explicit_span_method_name: method_name,
|
|
180
|
-
test_run_id:,
|
|
181
|
-
finalize:
|
|
182
|
-
) { super(*args, **kwargs, &block) }
|
|
183
168
|
end
|
|
184
169
|
end
|
|
185
|
-
|
|
186
170
|
preserve_visibility(klass, wrapper, method_name)
|
|
187
171
|
klass.prepend(wrapper)
|
|
188
172
|
end
|
|
@@ -278,7 +262,7 @@ module Bitfab
|
|
|
278
262
|
# When true, `client.replay(...)` returns this span's
|
|
279
263
|
# historical output instead of executing the wrapped method.
|
|
280
264
|
def bitfab_span(method_name, trace_function_key: nil, name: nil, type: "custom",
|
|
281
|
-
capture_when: "always", mock_on_replay: false)
|
|
265
|
+
capture_when: "always", mock_on_replay: false, finalize: nil, test_run_id: nil)
|
|
282
266
|
trace_function_key ||= @bitfab_function_key
|
|
283
267
|
unless trace_function_key
|
|
284
268
|
raise "No trace function key provided. Pass `trace_function_key:` to `bitfab_span` " \
|
|
@@ -288,7 +272,7 @@ module Bitfab
|
|
|
288
272
|
# If the method already exists (inline or after-method style), wrap it immediately
|
|
289
273
|
if method_defined?(method_name) || private_method_defined?(method_name)
|
|
290
274
|
_bitfab_wrap_method(
|
|
291
|
-
method_name, trace_function_key:, name:, type:, capture_when:, mock_on_replay:
|
|
275
|
+
method_name, trace_function_key:, name:, type:, capture_when:, mock_on_replay:, finalize:, test_run_id:
|
|
292
276
|
)
|
|
293
277
|
else
|
|
294
278
|
# Method doesn't exist yet (before-method style) - register for method_added hook
|
|
@@ -298,7 +282,9 @@ module Bitfab
|
|
|
298
282
|
name:,
|
|
299
283
|
type:,
|
|
300
284
|
capture_when:,
|
|
301
|
-
mock_on_replay
|
|
285
|
+
mock_on_replay:,
|
|
286
|
+
finalize:,
|
|
287
|
+
test_run_id:
|
|
302
288
|
}
|
|
303
289
|
end
|
|
304
290
|
end
|
|
@@ -307,7 +293,7 @@ module Bitfab
|
|
|
307
293
|
# Descendants require no Bitfab declarations and record as function spans.
|
|
308
294
|
def bitfab_trace(method_name, trace_function_key: nil, name: nil, type: "custom",
|
|
309
295
|
max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
|
|
310
|
-
exclude: [], include_wrappers: false)
|
|
296
|
+
exclude: [], include_wrappers: false, mock_on_replay_default: false)
|
|
311
297
|
trace_function_key ||= @bitfab_function_key
|
|
312
298
|
unless trace_function_key
|
|
313
299
|
raise "No trace function key provided. Pass `trace_function_key:` to `bitfab_trace` " \
|
|
@@ -321,7 +307,8 @@ module Bitfab
|
|
|
321
307
|
max_depth:,
|
|
322
308
|
max_spans:,
|
|
323
309
|
exclude:,
|
|
324
|
-
include_wrappers
|
|
310
|
+
include_wrappers:,
|
|
311
|
+
mock_on_replay_default:
|
|
325
312
|
}
|
|
326
313
|
if method_defined?(method_name) || private_method_defined?(method_name)
|
|
327
314
|
_bitfab_trace_method(method_name, **config)
|
|
@@ -334,7 +321,7 @@ module Bitfab
|
|
|
334
321
|
# EXPERIMENTAL. Configure one method discovered beneath bitfab_trace.
|
|
335
322
|
# The node inherits the active trace key and never traces by itself.
|
|
336
323
|
def bitfab_node(method_name, name: nil, type: "custom", capture: true,
|
|
337
|
-
test_run_id: nil, mock_on_replay:
|
|
324
|
+
test_run_id: nil, mock_on_replay: nil, finalize: nil)
|
|
338
325
|
Traceable.send(:validate_node_options!, capture:, mock_on_replay:)
|
|
339
326
|
config = {name:, type:, capture:, test_run_id:, mock_on_replay:, finalize:}
|
|
340
327
|
if method_defined?(method_name) || private_method_defined?(method_name)
|
|
@@ -361,88 +348,16 @@ module Bitfab
|
|
|
361
348
|
end
|
|
362
349
|
end
|
|
363
350
|
|
|
364
|
-
def _bitfab_wrap_method(method_name,
|
|
365
|
-
|
|
366
|
-
span_name = name || method_name.to_s
|
|
367
|
-
method_name_str = method_name.to_s
|
|
368
|
-
|
|
369
|
-
wrapper = Module.new do
|
|
370
|
-
define_method(method_name) do |*args, **kwargs, &block|
|
|
371
|
-
# If Bitfab was never configured, run the real method untraced
|
|
372
|
-
# rather than raising a "not configured" error into the host app.
|
|
373
|
-
client = Bitfab.client_or_nil
|
|
374
|
-
return super(*args, **kwargs, &block) unless client
|
|
375
|
-
|
|
376
|
-
client.send(:execute_span,
|
|
377
|
-
trace_function_key:,
|
|
378
|
-
span_name:,
|
|
379
|
-
span_type: type,
|
|
380
|
-
function_name: method_name_str,
|
|
381
|
-
args:,
|
|
382
|
-
kwargs:,
|
|
383
|
-
capture_when:,
|
|
384
|
-
mock_on_replay:,
|
|
385
|
-
explicit_span_receiver: self,
|
|
386
|
-
explicit_span_method_name: method_name) do
|
|
387
|
-
super(*args, **kwargs, &block)
|
|
388
|
-
end
|
|
389
|
-
end
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
|
|
393
|
-
wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
|
|
394
|
-
prepend(wrapper)
|
|
351
|
+
def _bitfab_wrap_method(method_name, **options)
|
|
352
|
+
Traceable.wrap(self, method_name, **options)
|
|
395
353
|
end
|
|
396
354
|
|
|
397
|
-
def _bitfab_trace_method(method_name,
|
|
398
|
-
|
|
399
|
-
exclude: [], include_wrappers: false)
|
|
400
|
-
source = Traceable.send(:source_of, self, method_name)
|
|
401
|
-
capture_config = Subtree.prepare(root_source: source, exclude:)
|
|
402
|
-
span_name = name || method_name.to_s
|
|
403
|
-
method_name_str = method_name.to_s
|
|
404
|
-
|
|
405
|
-
wrapper = Module.new do
|
|
406
|
-
define_method(method_name) do |*args, **kwargs, &block|
|
|
407
|
-
if Subtree.active_trace?(trace_function_key)
|
|
408
|
-
next super(*args, **kwargs, &block)
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
client = Bitfab.client_or_nil
|
|
412
|
-
return super(*args, **kwargs, &block) unless client
|
|
413
|
-
|
|
414
|
-
client.send(
|
|
415
|
-
:execute_span,
|
|
416
|
-
trace_function_key:,
|
|
417
|
-
span_name:,
|
|
418
|
-
span_type: type,
|
|
419
|
-
function_name: method_name_str,
|
|
420
|
-
args:,
|
|
421
|
-
kwargs:,
|
|
422
|
-
independent_root: true
|
|
423
|
-
) do
|
|
424
|
-
Subtree.capture(
|
|
425
|
-
client:,
|
|
426
|
-
trace_function_key:,
|
|
427
|
-
config: capture_config,
|
|
428
|
-
max_depth:,
|
|
429
|
-
max_spans:,
|
|
430
|
-
include_wrappers:,
|
|
431
|
-
root_receiver: self,
|
|
432
|
-
root_method_name: method_name
|
|
433
|
-
) { super(*args, **kwargs, &block) }
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
|
|
439
|
-
wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
|
|
440
|
-
Traceable.send(:preserve_visibility, self, wrapper, method_name)
|
|
441
|
-
prepend(wrapper)
|
|
355
|
+
def _bitfab_trace_method(method_name, **options)
|
|
356
|
+
Traceable.trace(self, method_name, **options)
|
|
442
357
|
end
|
|
443
358
|
|
|
444
359
|
def _bitfab_node_method(method_name, name: nil, type: "custom", capture: true,
|
|
445
|
-
test_run_id: nil, mock_on_replay:
|
|
360
|
+
test_run_id: nil, mock_on_replay: nil, finalize: nil)
|
|
446
361
|
Traceable.node(
|
|
447
362
|
self,
|
|
448
363
|
method_name,
|
data/lib/bitfab/version.rb
CHANGED