bitfab 0.57.6 → 0.57.7
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/baml.rb +3 -2
- data/lib/bitfab/client.rb +24 -13
- data/lib/bitfab/connection_pool.rb +136 -0
- data/lib/bitfab/http_client.rb +10 -18
- data/lib/bitfab/simulation_plan.rb +8 -6
- data/lib/bitfab/span_context.rb +2 -1
- data/lib/bitfab/subtree.rb +44 -9
- data/lib/bitfab/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b92a57be2a0c05885f0f84f757ede302ab14aed53ad422c85ee2781591a39786
|
|
4
|
+
data.tar.gz: ddb9b050e2f66e9591c17be161c2ebd25d395b625704baee80e396801f5a9e25
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0542a3b4dcb64f183ce35f7c9f16ead26e858fa1fefb7889115842e552b4855842ba175bea19893912ec37547708403d115259a201b2e02a1e6cb5ffa84f79f6
|
|
7
|
+
data.tar.gz: a530cd08bc1cdbc42467ea640c8f64e3035569c6723032626284bd6397744a21f5149ef0bc2d32af50aab1b1bbb856e8ba7510d1eb3640824de4f6f4e4472c43
|
data/lib/bitfab/baml.rb
CHANGED
|
@@ -170,7 +170,6 @@ module Bitfab
|
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
class Client
|
|
173
|
-
# Execute a stored BAML prompt locally and return its parsed output.
|
|
174
173
|
def call(method_name, **inputs)
|
|
175
174
|
version = @http_client.request("/api/sdk/functions/lookup", {"name" => method_name})
|
|
176
175
|
raise ArgumentError, "Function #{method_name.inspect} not found. Create it at #{@service_url}/functions" unless version["id"]
|
|
@@ -182,7 +181,9 @@ module Bitfab
|
|
|
182
181
|
rescue
|
|
183
182
|
result.to_s
|
|
184
183
|
end
|
|
185
|
-
|
|
184
|
+
if should_record? && !active_span_content_withheld?
|
|
185
|
+
@http_client.send_internal_trace(version["id"], {"result" => output, "inputs" => inputs, "rawCollector" => collector, "source" => "ruby-sdk"})
|
|
186
|
+
end
|
|
186
187
|
result
|
|
187
188
|
end
|
|
188
189
|
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -49,14 +49,10 @@ module Bitfab
|
|
|
49
49
|
@enabled_in_code = capture_enabled.nil? ? enabled : capture_enabled
|
|
50
50
|
@capture_configured_from_env = nil
|
|
51
51
|
@strict = strict
|
|
52
|
-
# Cached only once a non-empty key is found, so an early resolve (before
|
|
53
|
-
# env loaded) can't poison a later one.
|
|
54
52
|
@resolved_api_key = nil
|
|
55
53
|
@api_key_warned = false
|
|
56
|
-
# The key is NOT read here. HttpClient gets a proc so the key is resolved
|
|
57
|
-
# at send time, after any env loading has run.
|
|
58
54
|
@http_client = HttpClient.new(api_key: -> { resolve_api_key }, service_url: @service_url, timeout:)
|
|
59
|
-
@http_client.configure_simulation_plan(enabled: simulation_plan)
|
|
55
|
+
@http_client.configure_simulation_plan(enabled: simulation_plan, start: !api_key_resolved_lazily?)
|
|
60
56
|
@datasets = Datasets.new(@http_client)
|
|
61
57
|
@assertion_categories = AssertionCategories.new(@http_client)
|
|
62
58
|
@traces = Traces.new(@http_client)
|
|
@@ -381,11 +377,10 @@ module Bitfab
|
|
|
381
377
|
explicit_span_receiver: nil, explicit_span_method_name: nil, test_run_id: nil, finalize: nil,
|
|
382
378
|
surface: nil, on_start: nil, on_complete: nil, around_body: nil, span_data: {}, managed_root: false,
|
|
383
379
|
result_recorded_elsewhere: false)
|
|
384
|
-
# Decide at CALL time, not construction. The key may be set after the
|
|
385
|
-
# client is built (env loaded later), so re-checking per call lets a
|
|
386
|
-
# late-resolved key take effect.
|
|
387
380
|
return yield unless should_record?
|
|
388
381
|
|
|
382
|
+
@http_client.refresh_simulation_plan
|
|
383
|
+
|
|
389
384
|
resolved_capture_when = capture_when.to_s
|
|
390
385
|
unless ["always", "nested"].include?(resolved_capture_when)
|
|
391
386
|
Bitfab.warn_once(
|
|
@@ -513,6 +508,12 @@ module Bitfab
|
|
|
513
508
|
completed = false
|
|
514
509
|
@http_client.trace_completion.start(trace_id, span_id)
|
|
515
510
|
recording_override = managed_root ? {} : nil
|
|
511
|
+
content_withheld = lambda do
|
|
512
|
+
instrumentation = recording_override&.dig(:instrumentation) || ((surface == :trace) ? "trace" : "span")
|
|
513
|
+
content_off_by_simulation_plan?(
|
|
514
|
+
trace_id:, trace_function_key:, span_name: recording_override&.dig(:name) || span_name, instrumentation:
|
|
515
|
+
) || (!parent_span_id.nil? && simulation_plan_unavailable?(instrumentation:))
|
|
516
|
+
end
|
|
516
517
|
|
|
517
518
|
complete = lambda do |final_result, final_error|
|
|
518
519
|
# Never crash the host app due to span building/sending. Idempotent:
|
|
@@ -528,9 +529,7 @@ module Bitfab
|
|
|
528
529
|
recorded_error = final_error
|
|
529
530
|
recorded_name = recording_override&.dig(:name) || span_name
|
|
530
531
|
recorded_instrumentation = recording_override&.dig(:instrumentation) || ((surface == :trace) ? "trace" : "span")
|
|
531
|
-
content_off =
|
|
532
|
-
trace_id:, trace_function_key:, span_name: recorded_name, instrumentation: recorded_instrumentation
|
|
533
|
-
) || (!parent_span_id.nil? && simulation_plan_unavailable?(instrumentation: recorded_instrumentation))
|
|
532
|
+
content_off = content_withheld.call
|
|
534
533
|
if finalize && final_error.nil? && !(content_off && !result_recorded_elsewhere)
|
|
535
534
|
begin
|
|
536
535
|
recorded_result = finalize.call(final_result)
|
|
@@ -613,7 +612,7 @@ module Bitfab
|
|
|
613
612
|
span_id:,
|
|
614
613
|
explicit_span_receiver:,
|
|
615
614
|
explicit_span_method_name: explicit_span_method_name&.to_sym,
|
|
616
|
-
surface:, recording_override:
|
|
615
|
+
surface:, recording_override:, content_withheld:
|
|
617
616
|
) do
|
|
618
617
|
invoke = -> { around_body ? around_body.call { yield } : yield }
|
|
619
618
|
result = if explicit_span_receiver
|
|
@@ -704,6 +703,18 @@ module Bitfab
|
|
|
704
703
|
nil
|
|
705
704
|
end
|
|
706
705
|
|
|
706
|
+
def active_span_content_withheld?
|
|
707
|
+
return true if Subtree.current_session&.current_span_content_withheld?
|
|
708
|
+
|
|
709
|
+
SpanContext.current&.dig(:content_withheld)&.call == true
|
|
710
|
+
rescue
|
|
711
|
+
true
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
def api_key_resolved_lazily?
|
|
715
|
+
@api_key_config.respond_to?(:call) || @api_key_config.to_s.strip.empty?
|
|
716
|
+
end
|
|
717
|
+
|
|
707
718
|
# Resolve the API key lazily, the first time a span actually needs it.
|
|
708
719
|
#
|
|
709
720
|
# The key is intentionally NOT read at construction: a client built when a
|
|
@@ -955,7 +966,7 @@ module Bitfab
|
|
|
955
966
|
span_data["error_source"] = "code"
|
|
956
967
|
end
|
|
957
968
|
span_data["contexts"] = contexts if contexts&.any?
|
|
958
|
-
span_data["prompt"] = prompt if prompt
|
|
969
|
+
span_data["prompt"] = prompt if prompt && !content_off
|
|
959
970
|
|
|
960
971
|
raw_span = {
|
|
961
972
|
"id" => span_id,
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
|
|
5
|
+
module Bitfab
|
|
6
|
+
class ConnectionPool
|
|
7
|
+
KEEP_ALIVE_TIMEOUT_SECONDS = 30
|
|
8
|
+
MAX_IDLE_CONNECTIONS = 64
|
|
9
|
+
|
|
10
|
+
IdleConnection = Struct.new(:http, :released_at)
|
|
11
|
+
private_constant :IdleConnection
|
|
12
|
+
|
|
13
|
+
@registry = ObjectSpace::WeakMap.new
|
|
14
|
+
@registry_mutex = Mutex.new
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def register(pool)
|
|
18
|
+
@registry_mutex.synchronize { @registry[pool] = true }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def close_all
|
|
22
|
+
pools = @registry_mutex.synchronize { @registry.keys }
|
|
23
|
+
pools.each(&:close)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize
|
|
28
|
+
@pid = Process.pid
|
|
29
|
+
@mutex = Mutex.new
|
|
30
|
+
@idle = {}
|
|
31
|
+
self.class.register(self)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def with_connection(uri, timeout)
|
|
35
|
+
key = [uri.scheme, uri.host, uri.port]
|
|
36
|
+
owner_pid = Process.pid
|
|
37
|
+
reusable = false
|
|
38
|
+
http = checkout(key, uri, timeout)
|
|
39
|
+
response = yield http
|
|
40
|
+
reusable = true
|
|
41
|
+
response
|
|
42
|
+
ensure
|
|
43
|
+
release(key, http, owner_pid, reusable) if http
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def close
|
|
47
|
+
drain.each { |http| finish(http) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def idle_count
|
|
51
|
+
reset_after_fork
|
|
52
|
+
@mutex.synchronize { @idle.sum { |_key, connections| connections.size } }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def checkout(key, uri, timeout)
|
|
58
|
+
http = take_idle(key) || connect(uri, timeout)
|
|
59
|
+
http.read_timeout = timeout
|
|
60
|
+
http.write_timeout = timeout
|
|
61
|
+
http
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def connect(uri, timeout)
|
|
65
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
66
|
+
http.use_ssl = uri.scheme == "https"
|
|
67
|
+
http.open_timeout = timeout
|
|
68
|
+
http.read_timeout = timeout
|
|
69
|
+
http.write_timeout = timeout
|
|
70
|
+
http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT_SECONDS
|
|
71
|
+
http.start
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def take_idle(key)
|
|
75
|
+
reset_after_fork
|
|
76
|
+
now = monotonic_now
|
|
77
|
+
expired = []
|
|
78
|
+
http = @mutex.synchronize do
|
|
79
|
+
connections = @idle[key] || []
|
|
80
|
+
stale, fresh = connections.partition { |idle| now - idle.released_at >= KEEP_ALIVE_TIMEOUT_SECONDS }
|
|
81
|
+
expired.concat(stale.map(&:http))
|
|
82
|
+
@idle[key] = fresh
|
|
83
|
+
fresh.pop&.http
|
|
84
|
+
end
|
|
85
|
+
expired.each { |stale_http| finish(stale_http) }
|
|
86
|
+
http
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def release(key, http, owner_pid, reusable)
|
|
90
|
+
return unless owner_pid == Process.pid
|
|
91
|
+
|
|
92
|
+
unless reusable && http.started?
|
|
93
|
+
finish(http)
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
reset_after_fork
|
|
98
|
+
pooled = @mutex.synchronize do
|
|
99
|
+
next false if @idle.sum { |_key, connections| connections.size } >= MAX_IDLE_CONNECTIONS
|
|
100
|
+
|
|
101
|
+
(@idle[key] ||= []) << IdleConnection.new(http, monotonic_now)
|
|
102
|
+
true
|
|
103
|
+
end
|
|
104
|
+
finish(http) unless pooled
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def drain
|
|
108
|
+
return [] if reset_after_fork
|
|
109
|
+
|
|
110
|
+
@mutex.synchronize do
|
|
111
|
+
connections = @idle.values.flatten.map(&:http)
|
|
112
|
+
@idle = {}
|
|
113
|
+
connections
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def reset_after_fork
|
|
118
|
+
return false if @pid == Process.pid
|
|
119
|
+
|
|
120
|
+
@pid = Process.pid
|
|
121
|
+
@mutex = Mutex.new
|
|
122
|
+
@idle = {}
|
|
123
|
+
true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def finish(http)
|
|
127
|
+
http.finish if http.started?
|
|
128
|
+
rescue
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def monotonic_now
|
|
133
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
data/lib/bitfab/http_client.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "uri"
|
|
|
6
6
|
|
|
7
7
|
require_relative "git_state"
|
|
8
8
|
require_relative "compress"
|
|
9
|
+
require_relative "connection_pool"
|
|
9
10
|
require_relative "constants"
|
|
10
11
|
require_relative "serialize"
|
|
11
12
|
require_relative "transport"
|
|
@@ -37,6 +38,7 @@ module Bitfab
|
|
|
37
38
|
@api_key = api_key
|
|
38
39
|
@service_url = (service_url || DEFAULT_SERVICE_URL).chomp("/")
|
|
39
40
|
@timeout = timeout
|
|
41
|
+
@connections = ConnectionPool.new
|
|
40
42
|
@transport_mutex = Mutex.new
|
|
41
43
|
# Eager for the same reason the transport's mutex is: `||=` racing between
|
|
42
44
|
# the export threads recording acks would hand each a different mutex.
|
|
@@ -53,9 +55,9 @@ module Bitfab
|
|
|
53
55
|
@close_condition = ConditionVariable.new
|
|
54
56
|
end
|
|
55
57
|
|
|
56
|
-
def configure_simulation_plan(enabled: true)
|
|
58
|
+
def configure_simulation_plan(enabled: true, start: true)
|
|
57
59
|
@simulation_plan = SimulationPlan.new(self, enabled:)
|
|
58
|
-
@simulation_plan.refresh
|
|
60
|
+
@simulation_plan.refresh if start
|
|
59
61
|
@simulation_plan
|
|
60
62
|
end
|
|
61
63
|
|
|
@@ -80,7 +82,7 @@ module Bitfab
|
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def get_simulation_plan
|
|
83
|
-
get("/api/sdk/sim-plan", timeout: SimulationPlan::READ_TIMEOUT
|
|
85
|
+
get("/api/sdk/sim-plan", timeout: SimulationPlan::READ_TIMEOUT)
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
# Flush and permanently close this client's tracing transport.
|
|
@@ -112,6 +114,7 @@ module Bitfab
|
|
|
112
114
|
shutdown = transport.nil? || transport.shutdown(remaining)
|
|
113
115
|
result = released && shutdown
|
|
114
116
|
ensure
|
|
117
|
+
@connections.close
|
|
115
118
|
@transport_mutex.synchronize do
|
|
116
119
|
@close_result = result
|
|
117
120
|
@close_condition.broadcast
|
|
@@ -157,16 +160,11 @@ module Bitfab
|
|
|
157
160
|
last_error = nil
|
|
158
161
|
|
|
159
162
|
max_retries.times do |attempt|
|
|
160
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
161
|
-
http.use_ssl = uri.scheme == "https"
|
|
162
|
-
http.open_timeout = request_timeout
|
|
163
|
-
http.read_timeout = request_timeout
|
|
164
|
-
|
|
165
163
|
req = Net::HTTPGenericRequest.new(method, true, true, uri.request_uri, headers)
|
|
166
164
|
req["Content-Encoding"] = prepared.content_encoding if prepared.content_encoding
|
|
167
165
|
req.body = prepared.body
|
|
168
166
|
|
|
169
|
-
response = http.request(req)
|
|
167
|
+
response = @connections.with_connection(uri, request_timeout) { |http| http.request(req) }
|
|
170
168
|
|
|
171
169
|
unless response.is_a?(Net::HTTPSuccess)
|
|
172
170
|
raise Net::HTTPError.new("HTTP #{response.code}: #{response.body}", response)
|
|
@@ -216,19 +214,12 @@ module Bitfab
|
|
|
216
214
|
|
|
217
215
|
# Make a GET request to the Bitfab API.
|
|
218
216
|
# Returns parsed JSON response hash.
|
|
219
|
-
def get(endpoint, timeout: nil
|
|
217
|
+
def get(endpoint, timeout: nil)
|
|
220
218
|
uri = URI("#{@service_url}#{endpoint}")
|
|
221
219
|
request_timeout = timeout || @timeout
|
|
222
220
|
|
|
223
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
224
|
-
http.use_ssl = uri.scheme == "https"
|
|
225
|
-
http.open_timeout = request_timeout
|
|
226
|
-
http.read_timeout = request_timeout
|
|
227
|
-
|
|
228
|
-
# request_uri (not path) so any query string on the endpoint survives.
|
|
229
221
|
req = Net::HTTP::Get.new(uri.request_uri, headers)
|
|
230
|
-
|
|
231
|
-
response = http.request(req)
|
|
222
|
+
response = @connections.with_connection(uri, request_timeout) { |http| http.request(req) }
|
|
232
223
|
|
|
233
224
|
unless response.is_a?(Net::HTTPSuccess)
|
|
234
225
|
raise Net::HTTPError.new("HTTP #{response.code}: #{response.body}", response)
|
|
@@ -742,5 +733,6 @@ module Bitfab
|
|
|
742
733
|
at_exit do
|
|
743
734
|
SimulationPlan.release_all(2, stop: true)
|
|
744
735
|
Transport.shutdown_trace_transports(2)
|
|
736
|
+
ConnectionPool.close_all
|
|
745
737
|
end
|
|
746
738
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Bitfab
|
|
4
4
|
class SimulationPlan
|
|
5
|
-
CONTENT_KEYS = %w[input input_meta output output_meta input_serialized output_serialized].freeze
|
|
5
|
+
CONTENT_KEYS = %w[input input_meta output output_meta input_serialized output_serialized prompt].freeze
|
|
6
6
|
FRAMEWORKS = %w[openai-agents langgraph claude-agent-sdk vercel-ai].freeze
|
|
7
7
|
READ_TIMEOUT = 5.0
|
|
8
8
|
REFRESH_SECONDS = 60.0
|
|
@@ -54,6 +54,7 @@ module Bitfab
|
|
|
54
54
|
@draining = Set.new
|
|
55
55
|
@stopped = false
|
|
56
56
|
@first_read_finished = false
|
|
57
|
+
@first_read_deadline = nil
|
|
57
58
|
self.class.register(self)
|
|
58
59
|
end
|
|
59
60
|
|
|
@@ -144,14 +145,13 @@ module Bitfab
|
|
|
144
145
|
@mutex.synchronize { @content_off.nil? && !reading? }
|
|
145
146
|
end
|
|
146
147
|
|
|
147
|
-
def wait_for_first_read
|
|
148
|
-
|
|
148
|
+
def wait_for_first_read
|
|
149
|
+
refresh
|
|
149
150
|
return if disabled?
|
|
150
151
|
|
|
151
|
-
deadline = now + [timeout, 0].max
|
|
152
152
|
@mutex.synchronize do
|
|
153
|
-
while reading? && @thread
|
|
154
|
-
remaining =
|
|
153
|
+
while reading? && @thread && @first_read_deadline
|
|
154
|
+
remaining = @first_read_deadline - now
|
|
155
155
|
break unless remaining.positive?
|
|
156
156
|
@condition.wait(@mutex, remaining)
|
|
157
157
|
end
|
|
@@ -229,6 +229,7 @@ module Bitfab
|
|
|
229
229
|
end
|
|
230
230
|
|
|
231
231
|
def start_reader(delay)
|
|
232
|
+
@first_read_deadline ||= now + delay + READ_TIMEOUT
|
|
232
233
|
@thread = Thread.new { read_until_loaded(delay) }
|
|
233
234
|
@thread.name = "bitfab-sim-plan"
|
|
234
235
|
rescue ThreadError
|
|
@@ -302,6 +303,7 @@ module Bitfab
|
|
|
302
303
|
@thread = nil
|
|
303
304
|
@held = []
|
|
304
305
|
@draining = Set.new
|
|
306
|
+
@first_read_deadline = nil unless @first_read_finished
|
|
305
307
|
end
|
|
306
308
|
end
|
|
307
309
|
end
|
data/lib/bitfab/span_context.rb
CHANGED
|
@@ -150,10 +150,11 @@ module Bitfab
|
|
|
150
150
|
|
|
151
151
|
# Execute a block with a new span pushed onto the stack.
|
|
152
152
|
# The span is automatically popped when the block completes.
|
|
153
|
-
def with_span(trace_id:, span_id:, explicit_span_receiver: nil, explicit_span_method_name: nil, surface: nil, recording_override: nil)
|
|
153
|
+
def with_span(trace_id:, span_id:, explicit_span_receiver: nil, explicit_span_method_name: nil, surface: nil, recording_override: nil, content_withheld: nil)
|
|
154
154
|
entry = {trace_id:, span_id:}
|
|
155
155
|
entry[:surface] = surface if surface
|
|
156
156
|
entry[:recording_override] = recording_override if recording_override
|
|
157
|
+
entry[:content_withheld] = content_withheld if content_withheld
|
|
157
158
|
if explicit_span_receiver
|
|
158
159
|
entry[:explicit_span_receiver] = explicit_span_receiver
|
|
159
160
|
entry[:explicit_span_method_name] = explicit_span_method_name
|
data/lib/bitfab/subtree.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Bitfab
|
|
|
12
12
|
DEFAULT_MAX_DEPTH = 30
|
|
13
13
|
DEFAULT_MAX_SPANS = 2048
|
|
14
14
|
DEFAULT_MAX_CAPTURED_SUBTREE_SPANS = 512
|
|
15
|
+
LIMIT_ORDER = %i[max_depth max_spans max_captured_subtree_spans].freeze
|
|
15
16
|
SESSION_KEY = :bitfab_subtree_session
|
|
16
17
|
DISPATCHER_KEY = :bitfab_subtree_dispatcher
|
|
17
18
|
FIBER_CONTEXT_KEY = :bitfab_subtree_fiber_context
|
|
@@ -444,7 +445,8 @@ module Bitfab
|
|
|
444
445
|
@method_metadata = Hash.new { |owners, owner| owners[owner] = {} }
|
|
445
446
|
@spans_used = 1
|
|
446
447
|
@captured_subtree_count = 0
|
|
447
|
-
@
|
|
448
|
+
@limits_fired = Set.new
|
|
449
|
+
@dropped_spans = 0
|
|
448
450
|
@exhausted = false
|
|
449
451
|
@drained = false
|
|
450
452
|
@deferred_frames = []
|
|
@@ -478,7 +480,7 @@ module Bitfab
|
|
|
478
480
|
unless @deferred
|
|
479
481
|
@fiber_parents.clear
|
|
480
482
|
finish_open_spans
|
|
481
|
-
|
|
483
|
+
record_truncation
|
|
482
484
|
end
|
|
483
485
|
end
|
|
484
486
|
|
|
@@ -491,7 +493,7 @@ module Bitfab
|
|
|
491
493
|
end
|
|
492
494
|
|
|
493
495
|
def handle(event)
|
|
494
|
-
return if @drained
|
|
496
|
+
return count_drained_call(event) if @drained
|
|
495
497
|
|
|
496
498
|
if event.event == :raise
|
|
497
499
|
record_error(event)
|
|
@@ -523,16 +525,21 @@ module Bitfab
|
|
|
523
525
|
def reserve_node
|
|
524
526
|
stack = current_stack(Fiber.current.object_id)
|
|
525
527
|
if captured_depth(stack) >= @max_depth
|
|
526
|
-
|
|
528
|
+
drop(:max_depth)
|
|
527
529
|
return false
|
|
528
530
|
end
|
|
529
531
|
return true if take_slot(discovered: false)
|
|
530
532
|
|
|
531
|
-
|
|
533
|
+
drop(:max_spans)
|
|
532
534
|
@exhausted = true
|
|
533
535
|
false
|
|
534
536
|
end
|
|
535
537
|
|
|
538
|
+
def current_span_content_withheld?
|
|
539
|
+
entry = @stacks[Fiber.current.object_id]&.reverse_each&.find { |candidate| candidate.is_a?(OpenSpan) }
|
|
540
|
+
entry&.span_data&.dig("content_off_by_simulation_plan") == true
|
|
541
|
+
end
|
|
542
|
+
|
|
536
543
|
def configured_entry(name:, type:, args:, kwargs:, test_run_id:)
|
|
537
544
|
return unless reserve_node
|
|
538
545
|
|
|
@@ -624,11 +631,13 @@ module Bitfab
|
|
|
624
631
|
fiber_id = Fiber.current.object_id
|
|
625
632
|
if @suppressed_depths[fiber_id].to_i.positive?
|
|
626
633
|
@suppressed_depths[fiber_id] += 1
|
|
634
|
+
count_dropped_call(event, resolved_path)
|
|
627
635
|
return
|
|
628
636
|
end
|
|
629
637
|
|
|
630
638
|
if @exhausted
|
|
631
639
|
suppress(fiber_id)
|
|
640
|
+
count_dropped_call(event, resolved_path)
|
|
632
641
|
return
|
|
633
642
|
end
|
|
634
643
|
|
|
@@ -638,13 +647,13 @@ module Bitfab
|
|
|
638
647
|
|
|
639
648
|
stack = current_stack(fiber_id)
|
|
640
649
|
if captured_depth(stack) >= @max_depth
|
|
641
|
-
|
|
650
|
+
drop(:max_depth)
|
|
642
651
|
suppress(fiber_id)
|
|
643
652
|
return
|
|
644
653
|
end
|
|
645
654
|
content_off = content_off?(metadata.name)
|
|
646
655
|
if (limit = limit_reached(discovered: true, content_off:))
|
|
647
|
-
|
|
656
|
+
drop(limit)
|
|
648
657
|
stop_discovery(fiber_id) unless limit == :max_captured_subtree_spans && content_off_names?
|
|
649
658
|
return
|
|
650
659
|
end
|
|
@@ -927,9 +936,35 @@ module Bitfab
|
|
|
927
936
|
false
|
|
928
937
|
end
|
|
929
938
|
|
|
930
|
-
def
|
|
931
|
-
|
|
939
|
+
def drop(limit)
|
|
940
|
+
@limits_fired << limit
|
|
941
|
+
@dropped_spans += 1
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
def count_dropped_call(event, resolved_path)
|
|
945
|
+
metadata = metadata_for(event, resolved_path)
|
|
946
|
+
return if metadata.excluded || (!@include_wrappers && metadata.passthrough)
|
|
947
|
+
|
|
948
|
+
@dropped_spans += 1
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
def count_drained_call(event)
|
|
952
|
+
return unless event.event == :call
|
|
953
|
+
return if skip_boundary_call?(event)
|
|
932
954
|
|
|
955
|
+
resolved_path = owned_path(event.path)
|
|
956
|
+
count_dropped_call(event, resolved_path) if resolved_path
|
|
957
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
958
|
+
nil
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
def record_truncation
|
|
962
|
+
return if @limits_fired.empty?
|
|
963
|
+
|
|
964
|
+
CurrentTrace.new(@trace_id).set_metadata(
|
|
965
|
+
"bitfab.truncated_by" => LIMIT_ORDER.select { |limit| @limits_fired.include?(limit) }.join(","),
|
|
966
|
+
"bitfab.dropped_spans" => @dropped_spans
|
|
967
|
+
)
|
|
933
968
|
Bitfab.warn_once(
|
|
934
969
|
"subtree-truncated:#{@trace_function_key}",
|
|
935
970
|
"'#{@trace_function_key}' hit a subtree capture limit (max_depth=#{@max_depth}, " \
|
data/lib/bitfab/version.rb
CHANGED
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.57.
|
|
4
|
+
version: 0.57.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -161,6 +161,7 @@ files:
|
|
|
161
161
|
- lib/bitfab/client.rb
|
|
162
162
|
- lib/bitfab/commit_ref.rb
|
|
163
163
|
- lib/bitfab/compress.rb
|
|
164
|
+
- lib/bitfab/connection_pool.rb
|
|
164
165
|
- lib/bitfab/constants.rb
|
|
165
166
|
- lib/bitfab/datasets.rb
|
|
166
167
|
- lib/bitfab/db_snapshot.rb
|