bitfab 0.51.2 → 0.51.4

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.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "subtree"
4
+
3
5
  module Bitfab
4
6
  # Mixin for declarative span tracing on instance methods.
5
7
  #
@@ -67,7 +69,9 @@ module Bitfab
67
69
  args:,
68
70
  kwargs:,
69
71
  capture_when:,
70
- mock_on_replay:) do
72
+ mock_on_replay:,
73
+ explicit_span_receiver: self,
74
+ explicit_span_method_name: method_name) do
71
75
  super(*args, **kwargs, &block)
72
76
  end
73
77
  end
@@ -78,6 +82,137 @@ module Bitfab
78
82
  klass.prepend(wrapper)
79
83
  end
80
84
 
85
+ # Wrap an existing method with experimental whole-subtree tracing.
86
+ # The root records with the requested type; first-party Ruby methods called
87
+ # beneath it record automatically as function spans.
88
+ def self.trace(klass, method_name, trace_function_key:, name: nil, type: "custom",
89
+ max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
90
+ exclude: [], include_wrappers: false, client: nil)
91
+ source = source_of(klass, method_name)
92
+ capture_config = Subtree.prepare(root_source: source, exclude:)
93
+ span_name = name || method_name.to_s
94
+ method_name_str = method_name.to_s
95
+ bound_client = client
96
+
97
+ wrapper = Module.new do
98
+ define_method(method_name) do |*args, **kwargs, &block|
99
+ if Subtree.active_trace?(trace_function_key)
100
+ next super(*args, **kwargs, &block)
101
+ end
102
+
103
+ target_client = bound_client || Bitfab.client
104
+ target_client.send(
105
+ :execute_span,
106
+ trace_function_key:,
107
+ span_name:,
108
+ span_type: type,
109
+ function_name: method_name_str,
110
+ args:,
111
+ kwargs:,
112
+ independent_root: true
113
+ ) do
114
+ Subtree.capture(
115
+ client: target_client,
116
+ trace_function_key:,
117
+ config: capture_config,
118
+ max_depth:,
119
+ max_spans:,
120
+ include_wrappers:,
121
+ root_receiver: self,
122
+ root_method_name: method_name
123
+ ) { super(*args, **kwargs, &block) }
124
+ end
125
+ end
126
+ end
127
+
128
+ wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
129
+ wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
130
+ preserve_visibility(klass, wrapper, method_name)
131
+ klass.prepend(wrapper)
132
+ end
133
+
134
+ # Configure an existing method only when it is discovered beneath a
135
+ # bitfab_trace root. Outside subtree capture the method runs unchanged.
136
+ def self.node(klass, method_name, name: nil, type: "custom", capture: true,
137
+ test_run_id: nil, mock_on_replay: false, finalize: nil, client: nil)
138
+ validate_node_options!(capture:, mock_on_replay:)
139
+ span_name = name || method_name.to_s
140
+ method_name_str = method_name.to_s
141
+ bound_client = client
142
+
143
+ wrapper = Module.new do
144
+ define_method(method_name) do |*args, **kwargs, &block|
145
+ session = Subtree.current_session
146
+ if !session || (bound_client && !session.client.equal?(bound_client))
147
+ next super(*args, **kwargs, &block)
148
+ end
149
+
150
+ unless capture
151
+ next Subtree.with_suppressed_node(receiver: self, method_name:) do
152
+ super(*args, **kwargs, &block)
153
+ end
154
+ end
155
+
156
+ if session.excluded?(method_name)
157
+ next Subtree.with_suppressed_node(receiver: self, method_name:, sessions: [session]) do
158
+ super(*args, **kwargs, &block)
159
+ end
160
+ end
161
+
162
+ unless session.reserve_node
163
+ next Subtree.with_suppressed_node(receiver: self, method_name:, sessions: [session]) do
164
+ super(*args, **kwargs, &block)
165
+ end
166
+ 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
+ end
184
+ end
185
+
186
+ preserve_visibility(klass, wrapper, method_name)
187
+ klass.prepend(wrapper)
188
+ end
189
+
190
+ def self.source_of(klass, method_name)
191
+ path, line = klass.instance_method(method_name).source_location || [nil, nil]
192
+ [path, line, method_name]
193
+ end
194
+ private_class_method :source_of
195
+
196
+ def self.preserve_visibility(klass, wrapper, method_name)
197
+ visibility = if klass.private_method_defined?(method_name)
198
+ :private
199
+ elsif klass.protected_method_defined?(method_name)
200
+ :protected
201
+ else
202
+ :public
203
+ end
204
+ wrapper.send(visibility, method_name)
205
+ end
206
+ private_class_method :preserve_visibility
207
+
208
+ def self.validate_node_options!(capture:, mock_on_replay:)
209
+ return unless !capture && mock_on_replay
210
+
211
+ raise ArgumentError,
212
+ "bitfab_node(capture: false) cannot use mock_on_replay: true because an uncaptured node has no recorded output."
213
+ end
214
+ private_class_method :validate_node_options!
215
+
81
216
  # Resolve the trace function key a traced method records spans under, or
82
217
  # nil if the method carries no Bitfab span wrapper. Walks the receiver's
83
218
  # ancestor chain for the wrapper module that bitfab_span / Traceable.wrap
@@ -168,14 +303,62 @@ module Bitfab
168
303
  end
169
304
  end
170
305
 
306
+ # EXPERIMENTAL. Trace a method and every first-party Ruby method it calls.
307
+ # Descendants require no Bitfab declarations and record as function spans.
308
+ def bitfab_trace(method_name, trace_function_key: nil, name: nil, type: "custom",
309
+ max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
310
+ exclude: [], include_wrappers: false)
311
+ trace_function_key ||= @bitfab_function_key
312
+ unless trace_function_key
313
+ raise "No trace function key provided. Pass `trace_function_key:` to `bitfab_trace` " \
314
+ "or call `bitfab_function 'my-key'` before using `bitfab_trace`."
315
+ end
316
+
317
+ config = {
318
+ trace_function_key:,
319
+ name:,
320
+ type:,
321
+ max_depth:,
322
+ max_spans:,
323
+ exclude:,
324
+ include_wrappers:
325
+ }
326
+ if method_defined?(method_name) || private_method_defined?(method_name)
327
+ _bitfab_trace_method(method_name, **config)
328
+ else
329
+ @_bitfab_pending_traces ||= {}
330
+ @_bitfab_pending_traces[method_name] = config
331
+ end
332
+ end
333
+
334
+ # EXPERIMENTAL. Configure one method discovered beneath bitfab_trace.
335
+ # The node inherits the active trace key and never traces by itself.
336
+ def bitfab_node(method_name, name: nil, type: "custom", capture: true,
337
+ test_run_id: nil, mock_on_replay: false, finalize: nil)
338
+ Traceable.send(:validate_node_options!, capture:, mock_on_replay:)
339
+ config = {name:, type:, capture:, test_run_id:, mock_on_replay:, finalize:}
340
+ if method_defined?(method_name) || private_method_defined?(method_name)
341
+ _bitfab_node_method(method_name, **config)
342
+ else
343
+ @_bitfab_pending_nodes ||= {}
344
+ @_bitfab_pending_nodes[method_name] = config
345
+ end
346
+ end
347
+
171
348
  private
172
349
 
173
350
  def method_added(method_name)
174
351
  super
175
- return unless defined?(@_bitfab_pending_spans) && @_bitfab_pending_spans&.key?(method_name)
176
-
177
- config = @_bitfab_pending_spans.delete(method_name)
178
- _bitfab_wrap_method(method_name, **config)
352
+ if defined?(@_bitfab_pending_spans) && @_bitfab_pending_spans&.key?(method_name)
353
+ config = @_bitfab_pending_spans.delete(method_name)
354
+ _bitfab_wrap_method(method_name, **config)
355
+ elsif defined?(@_bitfab_pending_traces) && @_bitfab_pending_traces&.key?(method_name)
356
+ config = @_bitfab_pending_traces.delete(method_name)
357
+ _bitfab_trace_method(method_name, **config)
358
+ elsif defined?(@_bitfab_pending_nodes) && @_bitfab_pending_nodes&.key?(method_name)
359
+ config = @_bitfab_pending_nodes.delete(method_name)
360
+ _bitfab_node_method(method_name, **config)
361
+ end
179
362
  end
180
363
 
181
364
  def _bitfab_wrap_method(method_name, trace_function_key:, name: nil, type: "custom",
@@ -198,7 +381,9 @@ module Bitfab
198
381
  args:,
199
382
  kwargs:,
200
383
  capture_when:,
201
- mock_on_replay:) do
384
+ mock_on_replay:,
385
+ explicit_span_receiver: self,
386
+ explicit_span_method_name: method_name) do
202
387
  super(*args, **kwargs, &block)
203
388
  end
204
389
  end
@@ -208,6 +393,68 @@ module Bitfab
208
393
  wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
209
394
  prepend(wrapper)
210
395
  end
396
+
397
+ def _bitfab_trace_method(method_name, trace_function_key:, name: nil, type: "custom",
398
+ max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
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)
442
+ end
443
+
444
+ def _bitfab_node_method(method_name, name: nil, type: "custom", capture: true,
445
+ test_run_id: nil, mock_on_replay: false, finalize: nil)
446
+ Traceable.node(
447
+ self,
448
+ method_name,
449
+ name:,
450
+ type:,
451
+ capture:,
452
+ test_run_id:,
453
+ mock_on_replay:,
454
+ finalize:,
455
+ client: nil
456
+ )
457
+ end
211
458
  end
212
459
  end
213
460
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.51.2"
4
+ VERSION = "0.51.4"
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.51.2
4
+ version: 0.51.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team
@@ -156,6 +156,7 @@ files:
156
156
  - lib/bitfab/replay_registry.rb
157
157
  - lib/bitfab/serialize.rb
158
158
  - lib/bitfab/span_context.rb
159
+ - lib/bitfab/subtree.rb
159
160
  - lib/bitfab/timestamp.rb
160
161
  - lib/bitfab/traceable.rb
161
162
  - lib/bitfab/transport.rb