bitfab 0.51.4 → 0.51.6
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 +3 -0
- data/lib/bitfab/git_command.rb +3 -2
- data/lib/bitfab/git_state.rb +72 -0
- data/lib/bitfab/http_client.rb +22 -1
- data/lib/bitfab/trace_completion.rb +85 -0
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +1 -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: 208aed8da4ed9d2c748135161048c595f30507c946f3bb4086a560c55c1a2044
|
|
4
|
+
data.tar.gz: cfd965992d9304db89d53760c7e8ef8ea237394902aa33f5f0862a7388bf4c00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b76db7c1e008f877c79d6ab25eaacd2733f3e61564eb2211ac77418d050634503b31a4dd43ecd80bf7ea098fa4fec1975b36ec4c4b3125badbbc0a3ef61dfbba
|
|
7
|
+
data.tar.gz: '07281744cb252b83655686936532d9d58297537c35938c2eea468a5359005ba9c8ae97149e3465f990d005f5a80a2890d04662add468602e4ca9ddaaacd704e4'
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -470,6 +470,7 @@ module Bitfab
|
|
|
470
470
|
span_contexts = nil
|
|
471
471
|
span_prompt = nil
|
|
472
472
|
completed = false
|
|
473
|
+
@http_client.trace_completion.start(trace_id, span_id)
|
|
473
474
|
|
|
474
475
|
complete = lambda do |final_result, final_error|
|
|
475
476
|
# Never crash the host app due to span building/sending. Idempotent:
|
|
@@ -549,6 +550,8 @@ module Bitfab
|
|
|
549
550
|
# Silently ignore: user's result/exception takes priority
|
|
550
551
|
# Catches Exception (not just StandardError) to handle SystemStackError
|
|
551
552
|
# from deeply nested serialization
|
|
553
|
+
ensure
|
|
554
|
+
@http_client.trace_completion.finish(trace_id, span_id)
|
|
552
555
|
end
|
|
553
556
|
end
|
|
554
557
|
|
data/lib/bitfab/git_command.rb
CHANGED
|
@@ -8,8 +8,9 @@ module Bitfab
|
|
|
8
8
|
|
|
9
9
|
module_function
|
|
10
10
|
|
|
11
|
-
def run(cwd, args, timeout:)
|
|
12
|
-
|
|
11
|
+
def run(cwd, args, timeout:, env: nil)
|
|
12
|
+
command = env ? [env, "git", *args] : ["git", *args]
|
|
13
|
+
Open3.popen3(*command, chdir: cwd, **spawn_options) do |stdin, stdout, stderr, wait_thread|
|
|
13
14
|
stdin.close
|
|
14
15
|
out = +""
|
|
15
16
|
reader = Thread.new { out = read_all(stdout) }
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
require_relative "git_command"
|
|
5
|
+
|
|
6
|
+
module Bitfab
|
|
7
|
+
module GitState
|
|
8
|
+
DISABLE_ENV = "BITFAB_DISABLE_GIT_STATE"
|
|
9
|
+
GIT_TIMEOUT_SECONDS = 30
|
|
10
|
+
SHA_PATTERN = /\A[0-9a-f]{40}\z/
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def git(cwd, *args, env: nil)
|
|
15
|
+
GitCommand.run(cwd, args, timeout: GIT_TIMEOUT_SECONDS, env: env)&.strip
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def text(value)
|
|
19
|
+
trimmed = value&.strip
|
|
20
|
+
(trimmed.nil? || trimmed.empty?) ? nil : trimmed
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sha(value)
|
|
24
|
+
trimmed = text(value)
|
|
25
|
+
(trimmed && SHA_PATTERN.match?(trimmed)) ? trimmed : nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def empty?(state)
|
|
29
|
+
state.values.all?(&:nil?)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def resolve_working_tree_sha(root)
|
|
33
|
+
Dir.mktmpdir("bitfab-git-") do |dir|
|
|
34
|
+
env = {"GIT_INDEX_FILE" => File.join(dir, "index")}
|
|
35
|
+
return nil if git(root, "read-tree", "HEAD", env: env).nil?
|
|
36
|
+
return nil if git(root, "add", "-A", "--", root, env: env).nil?
|
|
37
|
+
|
|
38
|
+
sha(git(root, "write-tree", env: env))
|
|
39
|
+
end
|
|
40
|
+
rescue
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def resolve(cwd)
|
|
45
|
+
refs = git(cwd, "rev-parse", "--show-toplevel", "HEAD", "HEAD^{tree}")
|
|
46
|
+
lines = refs.to_s.split("\n")
|
|
47
|
+
root = text(lines[0])
|
|
48
|
+
return nil if root.nil?
|
|
49
|
+
|
|
50
|
+
state = {
|
|
51
|
+
"githubEmail" => text(git(cwd, "config", "user.email")),
|
|
52
|
+
"branch" => text(git(cwd, "symbolic-ref", "--short", "-q", "HEAD")),
|
|
53
|
+
"commitSha" => sha(lines[1]),
|
|
54
|
+
"baseSha" => sha(lines[2]),
|
|
55
|
+
"experimentSha" => resolve_working_tree_sha(root)
|
|
56
|
+
}
|
|
57
|
+
empty?(state) ? nil : state
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resolved
|
|
61
|
+
return nil unless ENV[DISABLE_ENV].to_s.empty?
|
|
62
|
+
|
|
63
|
+
safe_resolve
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def safe_resolve
|
|
67
|
+
resolve(Dir.pwd)
|
|
68
|
+
rescue
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/bitfab/http_client.rb
CHANGED
|
@@ -4,10 +4,12 @@ require "net/http"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "uri"
|
|
6
6
|
|
|
7
|
+
require_relative "git_state"
|
|
7
8
|
require_relative "compress"
|
|
8
9
|
require_relative "constants"
|
|
9
10
|
require_relative "serialize"
|
|
10
11
|
require_relative "transport"
|
|
12
|
+
require_relative "trace_completion"
|
|
11
13
|
require_relative "version"
|
|
12
14
|
require_relative "warn_once"
|
|
13
15
|
|
|
@@ -26,7 +28,7 @@ module Bitfab
|
|
|
26
28
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS = 300
|
|
27
29
|
private_constant :REPLAY_DB_BRANCH_REQUEST_TIMEOUT_SECONDS
|
|
28
30
|
|
|
29
|
-
attr_reader :service_url
|
|
31
|
+
attr_reader :service_url, :trace_completion
|
|
30
32
|
|
|
31
33
|
def initialize(api_key:, service_url: nil, timeout: 120)
|
|
32
34
|
@api_key = api_key
|
|
@@ -37,6 +39,7 @@ module Bitfab
|
|
|
37
39
|
# the export threads recording acks would hand each a different mutex.
|
|
38
40
|
@delivery_mutex = Mutex.new
|
|
39
41
|
@trace_deliveries = {}
|
|
42
|
+
@trace_completion = TraceCompletion.new
|
|
40
43
|
@delivery_pid = Process.pid
|
|
41
44
|
@carrier_seq_mutex = Mutex.new
|
|
42
45
|
@carrier_seq = 0
|
|
@@ -125,6 +128,8 @@ module Bitfab
|
|
|
125
128
|
|
|
126
129
|
# Queue an external span on this client's trace transport (fire-and-forget).
|
|
127
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
|
|
128
133
|
trace_transport&.submit(
|
|
129
134
|
"external_span",
|
|
130
135
|
payload.merge("sdkVersion" => VERSION),
|
|
@@ -207,6 +212,8 @@ module Bitfab
|
|
|
207
212
|
end
|
|
208
213
|
payload["graderIds"] = grader_ids unless grader_ids.nil?
|
|
209
214
|
payload["dbBranchSettings"] = db_branch_settings unless db_branch_settings.nil?
|
|
215
|
+
git_state = GitState.resolved
|
|
216
|
+
payload["git"] = git_state if git_state
|
|
210
217
|
|
|
211
218
|
# When DB branching is on, the server resolves a Neon preview branch per
|
|
212
219
|
# item (snapshot + restore + poll), which can run several seconds each,
|
|
@@ -284,6 +291,18 @@ module Bitfab
|
|
|
284
291
|
|
|
285
292
|
# Queue an external trace on this client's trace transport (fire-and-forget).
|
|
286
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)
|
|
287
306
|
trace_transport&.submit(
|
|
288
307
|
"external_trace",
|
|
289
308
|
payload.merge("sdkVersion" => VERSION),
|
|
@@ -293,6 +312,8 @@ module Bitfab
|
|
|
293
312
|
(payload["completed"] == true) ? carrier_ref(payload) : nil
|
|
294
313
|
)
|
|
295
314
|
)
|
|
315
|
+
rescue => error
|
|
316
|
+
Bitfab.warn_once("trace-completion-submit", "trace completion could not be queued: #{error.message}")
|
|
296
317
|
end
|
|
297
318
|
|
|
298
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
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "bitfab/serialize"
|
|
|
10
10
|
require_relative "bitfab/db_snapshot"
|
|
11
11
|
require_relative "bitfab/git_command"
|
|
12
12
|
require_relative "bitfab/commit_ref"
|
|
13
|
+
require_relative "bitfab/git_state"
|
|
13
14
|
require_relative "bitfab/span_context"
|
|
14
15
|
require_relative "bitfab/http_client"
|
|
15
16
|
require_relative "bitfab/datasets"
|
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.
|
|
4
|
+
version: 0.51.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -146,6 +146,7 @@ files:
|
|
|
146
146
|
- lib/bitfab/datasets.rb
|
|
147
147
|
- lib/bitfab/db_snapshot.rb
|
|
148
148
|
- lib/bitfab/git_command.rb
|
|
149
|
+
- lib/bitfab/git_state.rb
|
|
149
150
|
- lib/bitfab/http_client.rb
|
|
150
151
|
- lib/bitfab/mock_override.rb
|
|
151
152
|
- lib/bitfab/otel.rb
|
|
@@ -158,6 +159,7 @@ files:
|
|
|
158
159
|
- lib/bitfab/span_context.rb
|
|
159
160
|
- lib/bitfab/subtree.rb
|
|
160
161
|
- lib/bitfab/timestamp.rb
|
|
162
|
+
- lib/bitfab/trace_completion.rb
|
|
161
163
|
- lib/bitfab/traceable.rb
|
|
162
164
|
- lib/bitfab/transport.rb
|
|
163
165
|
- lib/bitfab/version.rb
|