bitfab 0.51.5 → 0.51.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bf8a9d45207caf1c5a6ef62a0e9ad6af43e5ccd904a25c8b8cd1fefd73795b9
4
- data.tar.gz: 684d7a18b4dc4cde5df27bce58e9be1a7ed3dbd8a644638148d81746cd9e7683
3
+ metadata.gz: 64f734e03ef657b17437062d8827ba0ab1532cb244f0c2bc7ae46e1f599015a0
4
+ data.tar.gz: bef9e05e622daada74cf66907f6fe6f9cc3ecac4447af7b6590936c4dc85ba39
5
5
  SHA512:
6
- metadata.gz: f3ffe75284ce984a57b524746a640ad8b51a10b32d4835d35a1d692415a23cfd09a7730105825713919c519e5dc256b444ba329c4bd0783f24a07e933315cb87
7
- data.tar.gz: c9da6e278539c0fe49d5192d05624ce0e1ba0f36ce0e9e8bf23511fd336bc39a3b58427d1196fee0418a7569f2920aefa5c9f456c57d246d1d01ebabb0306b21
6
+ metadata.gz: 979725c68118f1bd070da152c1d4ec607957dc771d3c39d021c68b2bc5f06d1315c49d33c13eae1ce99a1f16e6e0f7b2486dd3051286d3d8016bb552c3f0d14c
7
+ data.tar.gz: 2a71770f9f72528093ed229bd2505dcc7a515fc5ffa9c682933fb5802e65ffa7cc46de7538f077e896f8ad01ea13820253fe781ed2b92477ad8ab8c9cadabeeb
data/lib/bitfab/client.rb CHANGED
@@ -34,6 +34,9 @@ module Bitfab
34
34
  # Dataset operations for the authenticated organization.
35
35
  attr_reader :datasets
36
36
 
37
+ # Trace lookup operations for the authenticated organization.
38
+ attr_reader :traces
39
+
37
40
  def initialize(api_key: nil, service_url: nil, enabled: true, strict: false)
38
41
  @api_key_config = api_key
39
42
  @service_url = service_url || DEFAULT_SERVICE_URL
@@ -48,6 +51,7 @@ module Bitfab
48
51
  # at send time, after any env loading has run.
49
52
  @http_client = HttpClient.new(api_key: -> { resolve_api_key }, service_url: @service_url)
50
53
  @datasets = Datasets.new(@http_client)
54
+ @traces = Traces.new(@http_client)
51
55
  # Mock overrides registered via register_mock_override, applied to every
52
56
  # replay on this client (after any per-call mock_override). Instance
53
57
  # state, no global; clear_mock_overrides resets it.
@@ -470,6 +474,7 @@ module Bitfab
470
474
  span_contexts = nil
471
475
  span_prompt = nil
472
476
  completed = false
477
+ @http_client.trace_completion.start(trace_id, span_id)
473
478
 
474
479
  complete = lambda do |final_result, final_error|
475
480
  # Never crash the host app due to span building/sending. Idempotent:
@@ -549,6 +554,8 @@ module Bitfab
549
554
  # Silently ignore: user's result/exception takes priority
550
555
  # Catches Exception (not just StandardError) to handle SystemStackError
551
556
  # from deeply nested serialization
557
+ ensure
558
+ @http_client.trace_completion.finish(trace_id, span_id)
552
559
  end
553
560
  end
554
561
 
@@ -9,6 +9,7 @@ require_relative "compress"
9
9
  require_relative "constants"
10
10
  require_relative "serialize"
11
11
  require_relative "transport"
12
+ require_relative "trace_completion"
12
13
  require_relative "version"
13
14
  require_relative "warn_once"
14
15
 
@@ -27,7 +28,7 @@ module Bitfab
27
28
  REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS = 300
28
29
  private_constant :REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS
29
30
 
30
- attr_reader :service_url
31
+ attr_reader :service_url, :trace_completion
31
32
 
32
33
  def initialize(api_key:, service_url: nil, timeout: 120)
33
34
  @api_key = api_key
@@ -38,6 +39,7 @@ module Bitfab
38
39
  # the export threads recording acks would hand each a different mutex.
39
40
  @delivery_mutex = Mutex.new
40
41
  @trace_deliveries = {}
42
+ @trace_completion = TraceCompletion.new
41
43
  @delivery_pid = Process.pid
42
44
  @carrier_seq_mutex = Mutex.new
43
45
  @carrier_seq = 0
@@ -126,6 +128,8 @@ module Bitfab
126
128
 
127
129
  # Queue an external span on this client's trace transport (fire-and-forget).
128
130
  def send_external_span(payload)
131
+ ref = carrier_ref(payload)
132
+ @trace_completion.record(ref.trace_id, ref.span_id) if ref&.span_id
129
133
  trace_transport&.submit(
130
134
  "external_span",
131
135
  payload.merge("sdkVersion" => VERSION),
@@ -287,6 +291,18 @@ module Bitfab
287
291
 
288
292
  # Queue an external trace on this client's trace transport (fire-and-forget).
289
293
  def send_external_trace(payload)
294
+ ref = carrier_ref(payload)
295
+ if payload["completed"] == true && ref
296
+ @trace_completion.close(ref.trace_id, dropped: payload["dropped"] == true) do |count|
297
+ submit_external_trace(payload.merge("expectedSpanCount" => count))
298
+ end
299
+ else
300
+ @trace_completion.open(ref.trace_id) if ref
301
+ submit_external_trace(payload)
302
+ end
303
+ end
304
+
305
+ def submit_external_trace(payload)
290
306
  trace_transport&.submit(
291
307
  "external_trace",
292
308
  payload.merge("sdkVersion" => VERSION),
@@ -296,6 +312,8 @@ module Bitfab
296
312
  (payload["completed"] == true) ? carrier_ref(payload) : nil
297
313
  )
298
314
  )
315
+ rescue => error
316
+ Bitfab.warn_once("trace-completion-submit", "trace completion could not be queued: #{error.message}")
299
317
  end
300
318
 
301
319
  # Read the replay traces the server has fully persisted so far.
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set" # standard:disable Lint/RedundantRequireStatement -- Required on Ruby 3.4.
4
+
5
+ module Bitfab
6
+ class TraceCompletion
7
+ def initialize
8
+ @pid = Process.pid
9
+ @mutex = Mutex.new
10
+ @traces = {}
11
+ @closed = {}
12
+ end
13
+
14
+ def start(trace_id, span_id)
15
+ synchronize do
16
+ next if @closed.key?(trace_id)
17
+
18
+ entry = state(trace_id)
19
+ entry[:span_ids].add(span_id)
20
+ entry[:active].add(span_id)
21
+ end
22
+ end
23
+
24
+ def open(trace_id)
25
+ synchronize { state(trace_id) unless @closed.key?(trace_id) }
26
+ end
27
+
28
+ def record(trace_id, span_id)
29
+ synchronize do
30
+ state(trace_id)[:span_ids].add(span_id) unless @closed.key?(trace_id)
31
+ end
32
+ end
33
+
34
+ def finish(trace_id, span_id)
35
+ ready = synchronize do
36
+ entry = @traces[trace_id]
37
+ next if entry.nil?
38
+
39
+ entry[:active].delete(span_id)
40
+ drain(trace_id, entry)
41
+ end
42
+ ready[0].call(ready[1]) unless ready.nil?
43
+ end
44
+
45
+ def close(trace_id, dropped: false, &complete)
46
+ ready = synchronize do
47
+ next [complete, @closed[trace_id]] if @closed.key?(trace_id)
48
+
49
+ entry = @traces[trace_id]
50
+ next if entry.nil?
51
+
52
+ entry[:complete] = complete
53
+ entry[:active].clear if dropped
54
+ drain(trace_id, entry)
55
+ end
56
+ ready[0].call(ready[1]) unless ready.nil?
57
+ end
58
+
59
+ private
60
+
61
+ def synchronize(&block)
62
+ if @pid != Process.pid
63
+ @pid = Process.pid
64
+ @mutex = Mutex.new
65
+ @traces = {}
66
+ @closed = {}
67
+ end
68
+ @mutex.synchronize(&block)
69
+ end
70
+
71
+ def state(trace_id)
72
+ @traces[trace_id] ||= {span_ids: Set.new, active: Set.new}
73
+ end
74
+
75
+ def drain(trace_id, entry)
76
+ return unless entry[:active].empty? && entry[:complete]
77
+
78
+ @traces.delete(trace_id)
79
+ count = entry[:span_ids].size
80
+ @closed[trace_id] = count
81
+ @closed.shift if @closed.size > 1024
82
+ [entry[:complete], count]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitfab
4
+ # Trace lookup operations for the authenticated organization.
5
+ class Traces
6
+ def initialize(http_client)
7
+ @http_client = http_client
8
+ end
9
+
10
+ # Search by up to 10 exact, case-insensitive caller metadata pairs. Every
11
+ # pair must match. Pass nextCursor from the result as cursor: for another page.
12
+ def search(caller_metadata:, trace_function_key: nil, cursor: nil, limit: nil)
13
+ payload = {"callerMetadata" => caller_metadata}
14
+ payload["traceFunctionKey"] = trace_function_key unless trace_function_key.nil?
15
+ payload["cursor"] = cursor unless cursor.nil?
16
+ payload["limit"] = limit unless limit.nil?
17
+ @http_client.request("/api/sdk/traces/search", payload)
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.51.5"
4
+ VERSION = "0.51.7"
5
5
  end
data/lib/bitfab.rb CHANGED
@@ -14,6 +14,7 @@ require_relative "bitfab/git_state"
14
14
  require_relative "bitfab/span_context"
15
15
  require_relative "bitfab/http_client"
16
16
  require_relative "bitfab/datasets"
17
+ require_relative "bitfab/traces"
17
18
  require_relative "bitfab/mock_override"
18
19
  require_relative "bitfab/replay"
19
20
  require_relative "bitfab/replay_branch"
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.5
4
+ version: 0.51.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team
@@ -159,7 +159,9 @@ files:
159
159
  - lib/bitfab/span_context.rb
160
160
  - lib/bitfab/subtree.rb
161
161
  - lib/bitfab/timestamp.rb
162
+ - lib/bitfab/trace_completion.rb
162
163
  - lib/bitfab/traceable.rb
164
+ - lib/bitfab/traces.rb
163
165
  - lib/bitfab/transport.rb
164
166
  - lib/bitfab/version.rb
165
167
  - lib/bitfab/warn_once.rb