bitfab 0.16.0 → 0.17.0
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/client.rb +5 -2
- data/lib/bitfab/db_snapshot.rb +29 -0
- data/lib/bitfab/http_client.rb +16 -3
- data/lib/bitfab/replay.rb +55 -17
- data/lib/bitfab/replay_environment.rb +94 -0
- data/lib/bitfab/span_context.rb +14 -6
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60fa4998513b5fa9f67950bf4abbbc253b324955732e3726531ca47856544ddb
|
|
4
|
+
data.tar.gz: ad45f27ff4c50a4a75329f48e6ba06f252eb74e425b1d19686b55f6c780ae6a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1dd29ae11022f8cc0ad80b3d59e37d809843b85ab9cb48e0623afd01ee1a2d6b4e99e8ba91c996b7cad1acb589757ccf4a0291673a28d7a54eccffa098d5239e
|
|
7
|
+
data.tar.gz: efaf095342fee4995aa8d45c69773c7d268f5eb23e358606292c7dd3ba11a3958da7416606025f18c8c11b43c295b62ef0bcd80baf753c0070055ae0d3dcc0f3
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -60,11 +60,11 @@ module Bitfab
|
|
|
60
60
|
# @return [Hash] with :items, :test_run_id, :test_run_url
|
|
61
61
|
def replay(receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
|
|
62
62
|
code_change_description: nil, code_change_files: nil, experiment_group_id: nil, mock: "none",
|
|
63
|
-
adapt_inputs: nil)
|
|
63
|
+
adapt_inputs: nil, environment: nil)
|
|
64
64
|
Replay.run(
|
|
65
65
|
self, receiver, method_name,
|
|
66
66
|
trace_function_key:, limit:, trace_ids:, max_concurrency:,
|
|
67
|
-
code_change_description:, code_change_files:, experiment_group_id:, mock:, adapt_inputs:
|
|
67
|
+
code_change_description:, code_change_files:, experiment_group_id:, mock:, adapt_inputs:, environment:
|
|
68
68
|
)
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -315,6 +315,9 @@ module Bitfab
|
|
|
315
315
|
if trace_state&.dig(:input_source_trace_id)
|
|
316
316
|
raw_trace["input_source_trace_id"] = trace_state[:input_source_trace_id]
|
|
317
317
|
end
|
|
318
|
+
if trace_state&.dig(:db_snapshot_ref)
|
|
319
|
+
raw_trace["db_snapshot_ref"] = trace_state[:db_snapshot_ref]
|
|
320
|
+
end
|
|
318
321
|
|
|
319
322
|
payload = {
|
|
320
323
|
"type" => "sdk-function",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
# Per-trace database snapshot ref capture.
|
|
5
|
+
#
|
|
6
|
+
# Every root trace carries a snapshot ref that pins the DB state at trace
|
|
7
|
+
# open by wall-clock timestamp. Capturing the timestamp is free (no IO) and
|
|
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'
|
|
11
|
+
# +DbSnapshotRef+ wire shape (camelCase key, since the server speaks the same
|
|
12
|
+
# protocol for every SDK).
|
|
13
|
+
module DbSnapshot
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# Build a snapshot ref for one trace. Synchronous, no IO.
|
|
17
|
+
#
|
|
18
|
+
# Stores only the wall clock the SDK observed immediately before invoking
|
|
19
|
+
# the wrapped function; the server-side resolver uses that as the snapshot
|
|
20
|
+
# timestamp. No provider is captured (it is resolved at replay time).
|
|
21
|
+
#
|
|
22
|
+
# @param sdk_wall_clock_before_fn [String] ISO wall-clock timestamp the SDK
|
|
23
|
+
# observed immediately before invoking the wrapped function.
|
|
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}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/bitfab/http_client.rb
CHANGED
|
@@ -106,7 +106,7 @@ module Bitfab
|
|
|
106
106
|
# @param experiment_group_id [String, nil] optional UUID grouping multiple
|
|
107
107
|
# replay runs into a single experiment batch
|
|
108
108
|
def start_replay(trace_function_key, limit, trace_ids: nil, code_change_description: nil,
|
|
109
|
-
code_change_files: nil, experiment_group_id: nil)
|
|
109
|
+
code_change_files: nil, experiment_group_id: nil, include_db_branch_lease: false)
|
|
110
110
|
payload = {
|
|
111
111
|
"traceFunctionKey" => trace_function_key
|
|
112
112
|
}
|
|
@@ -117,8 +117,14 @@ module Bitfab
|
|
|
117
117
|
payload["codeChangeDescription"] = code_change_description unless code_change_description.nil?
|
|
118
118
|
payload["codeChangeFiles"] = normalize_code_change_files(code_change_files) unless code_change_files.nil?
|
|
119
119
|
payload["experimentGroupId"] = experiment_group_id unless experiment_group_id.nil?
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
payload["includeDbBranchLease"] = true if include_db_branch_lease
|
|
121
|
+
|
|
122
|
+
# When DB branching is on, the server resolves a Neon preview branch per
|
|
123
|
+
# item (snapshot + restore + poll), which can run several seconds each.
|
|
124
|
+
# Use a generous timeout so the SDK doesn't give up before a healthy
|
|
125
|
+
# server finishes.
|
|
126
|
+
timeout = include_db_branch_lease ? 180 : 30
|
|
127
|
+
request("/api/sdk/replay/start", payload, timeout:)
|
|
122
128
|
end
|
|
123
129
|
|
|
124
130
|
# Fetch an external span by ID. Blocking GET request.
|
|
@@ -142,6 +148,13 @@ module Bitfab
|
|
|
142
148
|
request("/api/sdk/replay/complete", {"testRunId" => test_run_id}, timeout: 30)
|
|
143
149
|
end
|
|
144
150
|
|
|
151
|
+
# Release a previously-resolved DB branch by deleting its Neon branch.
|
|
152
|
+
# Blocking call. Idempotent server-side (a missing branch is treated as
|
|
153
|
+
# already released).
|
|
154
|
+
def release_db_branch_lease(neon_branch_id)
|
|
155
|
+
request("/api/sdk/replay/releaseDbBranchLease", {"neonBranchId" => neon_branch_id}, timeout: 30)
|
|
156
|
+
end
|
|
157
|
+
|
|
145
158
|
# Send an external trace (fire-and-forget in background thread).
|
|
146
159
|
def send_external_trace(payload)
|
|
147
160
|
merged = payload.merge("sdkVersion" => VERSION)
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -27,7 +27,8 @@ module Bitfab
|
|
|
27
27
|
# threads (span uploads + trace completion) so the replay runner can join
|
|
28
28
|
# them before complete_replay builds the trace-ID mapping.
|
|
29
29
|
def with_context(test_run_id:, input_source_span_id: nil, input_source_trace_id: nil, trace_id: nil,
|
|
30
|
-
mock_tree: nil, mock_strategy: nil, pending_persistence: nil
|
|
30
|
+
mock_tree: nil, mock_strategy: nil, pending_persistence: nil, db_branch_lease: nil,
|
|
31
|
+
source_bitfab_trace_id: nil)
|
|
31
32
|
previous = Thread.current[REPLAY_CONTEXT_KEY]
|
|
32
33
|
ctx = {
|
|
33
34
|
test_run_id:,
|
|
@@ -41,6 +42,11 @@ module Bitfab
|
|
|
41
42
|
ctx[:mock_strategy] = mock_strategy || "none"
|
|
42
43
|
ctx[:call_counters] = {}
|
|
43
44
|
end
|
|
45
|
+
# The per-trace DB branch (resolved server-side) and the Bitfab trace ID
|
|
46
|
+
# it belongs to ride on the context so ReplayEnvironment can read them
|
|
47
|
+
# inside the replayed method.
|
|
48
|
+
ctx[:db_branch_lease] = db_branch_lease if db_branch_lease
|
|
49
|
+
ctx[:source_bitfab_trace_id] = source_bitfab_trace_id if source_bitfab_trace_id
|
|
44
50
|
Thread.current[REPLAY_CONTEXT_KEY] = ctx
|
|
45
51
|
yield
|
|
46
52
|
ensure
|
|
@@ -62,8 +68,8 @@ module Bitfab
|
|
|
62
68
|
# @param method_name [Symbol] the method to replay
|
|
63
69
|
# @param trace_function_key [String] the trace function key for this method
|
|
64
70
|
# @param limit [Integer, nil] maximum number of traces to replay (default: 5).
|
|
65
|
-
#
|
|
66
|
-
# determines how many traces replay
|
|
71
|
+
# Ignored when trace_ids is passed (with a warning): an explicit ID list
|
|
72
|
+
# already determines how many traces replay.
|
|
67
73
|
# @param trace_ids [Array<String>, nil] optional list of trace IDs to replay (max 100)
|
|
68
74
|
# @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
|
|
69
75
|
# @param code_change_description [String, nil] optional rationale for the
|
|
@@ -84,7 +90,7 @@ module Bitfab
|
|
|
84
90
|
# @return [Hash] with :items, :test_run_id, :test_run_url
|
|
85
91
|
def run(client, receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
|
|
86
92
|
code_change_description: nil, code_change_files: nil, experiment_group_id: nil, mock: "none",
|
|
87
|
-
adapt_inputs: nil)
|
|
93
|
+
adapt_inputs: nil, environment: nil)
|
|
88
94
|
unless MOCK_STRATEGIES.include?(mock.to_s)
|
|
89
95
|
raise ArgumentError, "Invalid mock strategy '#{mock}'. Must be one of: #{MOCK_STRATEGIES.join(", ")}"
|
|
90
96
|
end
|
|
@@ -95,8 +101,8 @@ module Bitfab
|
|
|
95
101
|
end
|
|
96
102
|
end
|
|
97
103
|
if limit && trace_ids
|
|
98
|
-
|
|
99
|
-
"
|
|
104
|
+
warn "Bitfab: limit is ignored when trace_ids is passed: the explicit trace ID list already " \
|
|
105
|
+
"determines how many traces replay."
|
|
100
106
|
end
|
|
101
107
|
|
|
102
108
|
http_client = client.instance_variable_get(:@http_client)
|
|
@@ -105,13 +111,16 @@ module Bitfab
|
|
|
105
111
|
# the count), so it's omitted from the request entirely.
|
|
106
112
|
effective_limit = trace_ids ? nil : (limit || 5)
|
|
107
113
|
|
|
114
|
+
include_db_branch_lease = !environment.nil?
|
|
115
|
+
|
|
108
116
|
replay_data = http_client.start_replay(
|
|
109
117
|
trace_function_key,
|
|
110
118
|
effective_limit,
|
|
111
119
|
trace_ids:,
|
|
112
120
|
code_change_description:,
|
|
113
121
|
code_change_files:,
|
|
114
|
-
experiment_group_id
|
|
122
|
+
experiment_group_id:,
|
|
123
|
+
include_db_branch_lease:
|
|
115
124
|
)
|
|
116
125
|
test_run_id = replay_data["testRunId"]
|
|
117
126
|
test_run_url = replay_data["testRunUrl"]
|
|
@@ -119,7 +128,7 @@ module Bitfab
|
|
|
119
128
|
|
|
120
129
|
result_items = if server_items.any?
|
|
121
130
|
process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock.to_s,
|
|
122
|
-
adapt_inputs)
|
|
131
|
+
adapt_inputs, include_db_branch_lease)
|
|
123
132
|
else
|
|
124
133
|
[]
|
|
125
134
|
end
|
|
@@ -192,12 +201,13 @@ module Bitfab
|
|
|
192
201
|
|
|
193
202
|
# Process all replay items, optionally in parallel using threads.
|
|
194
203
|
def process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency, mock_strategy,
|
|
195
|
-
adapt_inputs = nil)
|
|
204
|
+
adapt_inputs = nil, include_db_branch_lease = false)
|
|
196
205
|
concurrency = max_concurrency || server_items.length
|
|
197
206
|
|
|
198
207
|
if concurrency <= 1
|
|
199
208
|
server_items.map do |item|
|
|
200
|
-
process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy, adapt_inputs
|
|
209
|
+
process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy, adapt_inputs,
|
|
210
|
+
include_db_branch_lease)
|
|
201
211
|
end
|
|
202
212
|
else
|
|
203
213
|
results_mutex = Mutex.new
|
|
@@ -212,7 +222,7 @@ module Bitfab
|
|
|
212
222
|
break unless item
|
|
213
223
|
|
|
214
224
|
result = process_single_item(http_client, item, receiver, method_name, test_run_id, mock_strategy,
|
|
215
|
-
adapt_inputs)
|
|
225
|
+
adapt_inputs, include_db_branch_lease)
|
|
216
226
|
results_mutex.synchronize { results[idx] = result }
|
|
217
227
|
end
|
|
218
228
|
end
|
|
@@ -230,8 +240,15 @@ module Bitfab
|
|
|
230
240
|
# than propagated, so one bad trace never aborts the whole replay run
|
|
231
241
|
# (mirrors the TypeScript and Python SDKs' per-item rescue).
|
|
232
242
|
def process_single_item(http_client, server_item, receiver, method_name, test_run_id, mock_strategy,
|
|
233
|
-
adapt_inputs = nil)
|
|
243
|
+
adapt_inputs = nil, include_db_branch_lease = false)
|
|
234
244
|
metrics = extract_server_item_metrics(server_item)
|
|
245
|
+
# The server resolves a Neon preview branch per item during /replay/start
|
|
246
|
+
# (only when include_db_branch_lease was sent). Release it in the +ensure+
|
|
247
|
+
# below so any raise — span fetch, mock-tree build, or the replayed
|
|
248
|
+
# method — frees the Neon resource. Items whose source trace had no
|
|
249
|
+
# snapshot ref, or whose resolve failed server-side, arrive without a
|
|
250
|
+
# lease (env.active? is false for those).
|
|
251
|
+
lease = include_db_branch_lease ? server_item["dbBranchLease"] : nil
|
|
235
252
|
|
|
236
253
|
span = http_client.get_external_span(server_item["externalSpanId"])
|
|
237
254
|
item_data = extract_span_data(span)
|
|
@@ -255,7 +272,10 @@ module Bitfab
|
|
|
255
272
|
mock_strategy:,
|
|
256
273
|
mock_tree:,
|
|
257
274
|
adapt_inputs:,
|
|
258
|
-
adapt_ctx
|
|
275
|
+
adapt_ctx:,
|
|
276
|
+
db_branch_lease: lease,
|
|
277
|
+
source_bitfab_trace_id: server_item["traceId"],
|
|
278
|
+
db_snapshot_ref: server_item["dbSnapshotRef"]
|
|
259
279
|
)
|
|
260
280
|
rescue => e
|
|
261
281
|
warn "Bitfab: replay item for span #{server_item["externalSpanId"]} failed before execution: #{e.message}"
|
|
@@ -267,8 +287,22 @@ module Bitfab
|
|
|
267
287
|
duration_ms: metrics&.dig(:duration_ms),
|
|
268
288
|
tokens: metrics&.dig(:tokens),
|
|
269
289
|
model: metrics&.dig(:model),
|
|
270
|
-
trace_id: nil
|
|
290
|
+
trace_id: nil,
|
|
291
|
+
db_snapshot_ref: server_item["dbSnapshotRef"]
|
|
271
292
|
}
|
|
293
|
+
ensure
|
|
294
|
+
release_db_branch_lease(http_client, lease) if lease
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Delete the per-item Neon preview branch. Best-effort: a failure is warned
|
|
298
|
+
# but never raised — the server-side TTL janitor reaps orphans.
|
|
299
|
+
def release_db_branch_lease(http_client, lease)
|
|
300
|
+
neon_branch_id = lease["neonBranchId"]
|
|
301
|
+
return unless neon_branch_id
|
|
302
|
+
|
|
303
|
+
http_client.release_db_branch_lease(neon_branch_id)
|
|
304
|
+
rescue => e
|
|
305
|
+
warn "Bitfab: failed to release DB branch #{neon_branch_id} (TTL janitor will catch it): #{e.message}"
|
|
272
306
|
end
|
|
273
307
|
|
|
274
308
|
# Walk the children of a root span tree node depth-first and build a
|
|
@@ -346,7 +380,8 @@ module Bitfab
|
|
|
346
380
|
|
|
347
381
|
# Execute a single replay item: deserialize inputs, call method with replay context.
|
|
348
382
|
def execute_item(item, receiver, method_name, test_run_id, input_source_span_id = nil, metrics = {},
|
|
349
|
-
input_source_trace_id: nil, mock_strategy: "none", mock_tree: nil, adapt_inputs: nil, adapt_ctx: nil
|
|
383
|
+
input_source_trace_id: nil, mock_strategy: "none", mock_tree: nil, adapt_inputs: nil, adapt_ctx: nil,
|
|
384
|
+
db_branch_lease: nil, source_bitfab_trace_id: nil, db_snapshot_ref: nil)
|
|
350
385
|
args, kwargs = Serialize.deserialize_inputs(item)
|
|
351
386
|
|
|
352
387
|
fn_result = nil
|
|
@@ -365,7 +400,9 @@ module Bitfab
|
|
|
365
400
|
trace_id: sdk_trace_id,
|
|
366
401
|
mock_tree:,
|
|
367
402
|
mock_strategy:,
|
|
368
|
-
pending_persistence
|
|
403
|
+
pending_persistence:,
|
|
404
|
+
db_branch_lease:,
|
|
405
|
+
source_bitfab_trace_id:
|
|
369
406
|
) do
|
|
370
407
|
# Reshape recorded inputs onto the current signature when an adapter is
|
|
371
408
|
# supplied. Inside the rescue so a raising adapter surfaces on this
|
|
@@ -397,7 +434,8 @@ module Bitfab
|
|
|
397
434
|
duration_ms: metrics[:duration_ms],
|
|
398
435
|
tokens: metrics[:tokens],
|
|
399
436
|
model: metrics[:model],
|
|
400
|
-
trace_id: sdk_trace_id
|
|
437
|
+
trace_id: sdk_trace_id,
|
|
438
|
+
db_snapshot_ref:
|
|
401
439
|
}
|
|
402
440
|
end
|
|
403
441
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
# Per-trace environment exposed to customer code during replay.
|
|
5
|
+
#
|
|
6
|
+
# The customer instantiates one +ReplayEnvironment+ and passes it to
|
|
7
|
+
# +client.replay(environment: ...)+. Inside the replayed method they read
|
|
8
|
+
# +env.database_url+ (and friends) to pick up the per-trace branch URL the
|
|
9
|
+
# Bitfab service resolved from the source trace's snapshot reference.
|
|
10
|
+
#
|
|
11
|
+
# Outside replay, reading +env.database_url+ raises. Customer code uses the
|
|
12
|
+
# env only on the replay path; live request code keeps reading
|
|
13
|
+
# +ENV["DATABASE_URL"]+ the normal way.
|
|
14
|
+
#
|
|
15
|
+
# Concurrency-safe: the readers resolve through the thread-local replay
|
|
16
|
+
# context, so each in-flight replay item sees its own per-trace values even
|
|
17
|
+
# when the SDK runs items across worker threads.
|
|
18
|
+
#
|
|
19
|
+
# Internally the resolved per-item state is a DB branch lease (the SDK <->
|
|
20
|
+
# server protocol term). We expose its useful fields directly here so
|
|
21
|
+
# customer code never sees the word.
|
|
22
|
+
class ReplayEnvironment
|
|
23
|
+
# The per-trace branch URL for the item currently being replayed.
|
|
24
|
+
# Raises if read outside a replay item.
|
|
25
|
+
def database_url
|
|
26
|
+
require_snapshot.fetch(:database_url)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# When the per-trace branch URL stops being valid. ISO-8601.
|
|
30
|
+
def expires_at
|
|
31
|
+
require_snapshot.fetch(:expires_at)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Deep link to the branch in the provider console, if available.
|
|
35
|
+
def provider_console_url
|
|
36
|
+
require_snapshot[:provider_console_url]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# True if the branch is read-only. Customer code can use this to skip write
|
|
40
|
+
# operations during replay when the provider returned a read-only lease.
|
|
41
|
+
def read_only
|
|
42
|
+
require_snapshot[:read_only]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The historical trace ID that produced the input for this replay item.
|
|
46
|
+
def trace_id
|
|
47
|
+
require_snapshot.fetch(:trace_id)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# True when read inside a replay item that has a resolved branch.
|
|
51
|
+
def active?
|
|
52
|
+
!read_snapshot.nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Non-raising variant for callers that handle the inactive case. Returns a
|
|
56
|
+
# symbol-keyed hash or nil.
|
|
57
|
+
def snapshot
|
|
58
|
+
read_snapshot
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def read_snapshot
|
|
64
|
+
ctx = ReplayContext.current
|
|
65
|
+
return nil unless ctx
|
|
66
|
+
|
|
67
|
+
lease = ctx[:db_branch_lease]
|
|
68
|
+
return nil unless lease
|
|
69
|
+
|
|
70
|
+
# Surface the Bitfab trace ID (what the customer sees in the dashboard),
|
|
71
|
+
# falling back to the external trace ID only if the Bitfab ID is somehow
|
|
72
|
+
# absent — keeps replays from external sources working until the
|
|
73
|
+
# source-system path is fully wired.
|
|
74
|
+
trace_id = ctx[:source_bitfab_trace_id] || ctx[:input_source_trace_id]
|
|
75
|
+
return nil unless trace_id
|
|
76
|
+
|
|
77
|
+
{
|
|
78
|
+
database_url: lease["databaseUrl"],
|
|
79
|
+
expires_at: lease["expiresAt"],
|
|
80
|
+
provider_console_url: lease["providerConsoleUrl"],
|
|
81
|
+
read_only: lease["readOnly"],
|
|
82
|
+
trace_id:
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def require_snapshot
|
|
87
|
+
snap = read_snapshot
|
|
88
|
+
return snap if snap
|
|
89
|
+
|
|
90
|
+
raise "ReplayEnvironment accessed outside of a replay item. Pass it to " \
|
|
91
|
+
"client.replay(environment: ...) and only read it inside the replayed method."
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/lib/bitfab/span_context.rb
CHANGED
|
@@ -133,12 +133,20 @@ module Bitfab
|
|
|
133
133
|
|
|
134
134
|
def create(trace_id, test_run_id: nil, input_source_trace_id: nil)
|
|
135
135
|
@states_mutex.synchronize do
|
|
136
|
-
@states[trace_id] ||=
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
@states[trace_id] ||= begin
|
|
137
|
+
started_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
|
|
138
|
+
{
|
|
139
|
+
trace_id:,
|
|
140
|
+
started_at:,
|
|
141
|
+
test_run_id:,
|
|
142
|
+
input_source_trace_id:,
|
|
143
|
+
# Capture the wall clock now, before the wrapped function runs.
|
|
144
|
+
# Stored on every trace (no IO, harmless) so any trace can later be
|
|
145
|
+
# replayed against a historical branch; the provider is resolved at
|
|
146
|
+
# replay time.
|
|
147
|
+
db_snapshot_ref: DbSnapshot.build_snapshot_ref(started_at)
|
|
148
|
+
}.compact
|
|
149
|
+
end
|
|
142
150
|
end
|
|
143
151
|
end
|
|
144
152
|
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
require_relative "bitfab/version"
|
|
4
4
|
require_relative "bitfab/constants"
|
|
5
5
|
require_relative "bitfab/serialize"
|
|
6
|
+
require_relative "bitfab/db_snapshot"
|
|
6
7
|
require_relative "bitfab/span_context"
|
|
7
8
|
require_relative "bitfab/http_client"
|
|
8
9
|
require_relative "bitfab/replay"
|
|
10
|
+
require_relative "bitfab/replay_environment"
|
|
9
11
|
require_relative "bitfab/client"
|
|
10
12
|
require_relative "bitfab/traceable"
|
|
11
13
|
|
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.
|
|
4
|
+
version: 0.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -119,8 +119,10 @@ files:
|
|
|
119
119
|
- lib/bitfab.rb
|
|
120
120
|
- lib/bitfab/client.rb
|
|
121
121
|
- lib/bitfab/constants.rb
|
|
122
|
+
- lib/bitfab/db_snapshot.rb
|
|
122
123
|
- lib/bitfab/http_client.rb
|
|
123
124
|
- lib/bitfab/replay.rb
|
|
125
|
+
- lib/bitfab/replay_environment.rb
|
|
124
126
|
- lib/bitfab/serialize.rb
|
|
125
127
|
- lib/bitfab/span_context.rb
|
|
126
128
|
- lib/bitfab/traceable.rb
|