bitfab 0.38.0 → 0.38.1
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/exe/bitfab-replay +6 -0
- data/lib/bitfab/client.rb +4 -2
- data/lib/bitfab/replay.rb +4 -1
- data/lib/bitfab/replay_cli.rb +58 -0
- data/lib/bitfab/replay_registry.rb +290 -0
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +2 -0
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2816ce4e3bcd11bb1335ba7dac6b36376277d17075cf853d26c9befa231a758b
|
|
4
|
+
data.tar.gz: 7e923c2efb8dfd38edbdd592972a8f236a3fa81cdfabcbd46b8303ba4160b01e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15a2df82260f61733b90da3c110b195c92373c187c3eddce5faa041f44499bb008ec5a14cd27dab22b63e2973540010cc2e2f10fcc2dcd75481405d018a107f6
|
|
7
|
+
data.tar.gz: 13968f6a9f8d3c88ea2845d0d59e0cc1d31ffadc1054311addeedc1e4ba92d94aea504f0f5d11e9891fd0a233e92d285e635e5d8e85e46ebfdf553cdedc45005
|
data/exe/bitfab-replay
ADDED
data/lib/bitfab/client.rb
CHANGED
|
@@ -91,7 +91,8 @@ module Bitfab
|
|
|
91
91
|
# maximum: 5,000). Ignored when trace_ids or dataset_id is passed because
|
|
92
92
|
# either source already determines how many traces replay. Supplying
|
|
93
93
|
# trace_ids also emits a warning.
|
|
94
|
-
# @param trace_ids [Array<String>, nil] optional list of trace IDs to replay (max 100)
|
|
94
|
+
# @param trace_ids [Array<String>, nil] optional list of trace IDs to replay (max 100),
|
|
95
|
+
# mutually exclusive with dataset_id
|
|
95
96
|
# @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
|
|
96
97
|
# @param code_change_description [String, nil] optional rationale for the
|
|
97
98
|
# code change being tested in this replay (stored on the experiment).
|
|
@@ -105,7 +106,8 @@ module Bitfab
|
|
|
105
106
|
# @param experiment_group_id [String, nil] optional UUID grouping multiple
|
|
106
107
|
# replay runs into a single experiment batch
|
|
107
108
|
# @param dataset_id [String, nil] optional UUID of the dataset this replay
|
|
108
|
-
# runs against,
|
|
109
|
+
# runs against, mutually exclusive with trace_ids and stored on the
|
|
110
|
+
# resulting experiment for durable attribution
|
|
109
111
|
# @param grader_ids [Array<String>, nil] optional UUIDs of graders attached
|
|
110
112
|
# directly to this experiment, graded as the union with the dataset's
|
|
111
113
|
# runnable graders at completion; each must be an active/live grader in the
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -260,11 +260,14 @@ module Bitfab
|
|
|
260
260
|
raise ArgumentError, "trace_ids supports at most 100 trace IDs per replay (got #{trace_ids.length})."
|
|
261
261
|
end
|
|
262
262
|
end
|
|
263
|
+
if trace_ids && dataset_id
|
|
264
|
+
raise ArgumentError,
|
|
265
|
+
"trace_ids and dataset_id select different replay sources and cannot be used together."
|
|
266
|
+
end
|
|
263
267
|
if limit && trace_ids
|
|
264
268
|
warn "Bitfab: limit is ignored when trace_ids is passed: the explicit trace ID list already " \
|
|
265
269
|
"determines how many traces replay."
|
|
266
270
|
end
|
|
267
|
-
|
|
268
271
|
# Reject a trace_function_key that contradicts the method's declared key.
|
|
269
272
|
# replay() fetches historical traces by trace_function_key but records the
|
|
270
273
|
# replayed spans under the method's own bitfab_span key (via send below),
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module Bitfab
|
|
6
|
+
# Installed command that loads a project-owned registry module.
|
|
7
|
+
module ReplayCommand
|
|
8
|
+
USAGE = "Usage: bitfab-replay --registry <path> <pipeline> [replay options]"
|
|
9
|
+
HELP = "#{USAGE}\nRun bitfab-replay --registry <path> --help to list replay options."
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def run(argv: ARGV, stdout: $stdout, stderr: $stderr, registry_loader: nil)
|
|
14
|
+
registry_path, replay_argv = parse(argv)
|
|
15
|
+
loader = registry_loader || method(:load_registry)
|
|
16
|
+
registry = loader.call(registry_path)
|
|
17
|
+
ReplayCli.run(registry, argv: replay_argv, stdout:, stderr:)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def main(argv: ARGV, stdout: $stdout, stderr: $stderr)
|
|
21
|
+
if (argv.include?("--help") || argv.include?("-h")) && !argv.include?("--registry")
|
|
22
|
+
stdout.puts HELP
|
|
23
|
+
return 0
|
|
24
|
+
end
|
|
25
|
+
run(argv:, stdout:, stderr:)
|
|
26
|
+
0
|
|
27
|
+
rescue => error
|
|
28
|
+
stderr.puts error.message
|
|
29
|
+
1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse(argv)
|
|
33
|
+
values = argv.dup
|
|
34
|
+
registry_index = values.index("--registry")
|
|
35
|
+
registry_path = values[registry_index + 1] if registry_index
|
|
36
|
+
unless registry_path && !registry_path.start_with?("--")
|
|
37
|
+
raise OptionParser::MissingArgument, USAGE
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
values.slice!(registry_index, 2)
|
|
41
|
+
raise OptionParser::MissingArgument, USAGE if values.empty?
|
|
42
|
+
|
|
43
|
+
[File.expand_path(registry_path), values]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def load_registry(path)
|
|
47
|
+
container = Module.new
|
|
48
|
+
container.module_eval(File.read(path), path)
|
|
49
|
+
registry = container.const_get(:REGISTRY, false) if container.const_defined?(:REGISTRY, false)
|
|
50
|
+
unless registry.is_a?(ReplayRegistry)
|
|
51
|
+
raise ArgumentError,
|
|
52
|
+
"Replay registry '#{path}' must define REGISTRY as a Bitfab::ReplayRegistry."
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
registry
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "optparse"
|
|
5
|
+
|
|
6
|
+
module Bitfab
|
|
7
|
+
# One project function exposed through the SDK-owned replay command.
|
|
8
|
+
ReplayRegistration = Data.define(
|
|
9
|
+
:client,
|
|
10
|
+
:receiver,
|
|
11
|
+
:method_name,
|
|
12
|
+
:trace_function_key,
|
|
13
|
+
:options,
|
|
14
|
+
:options_factory
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
ReplayRegistryContext = Data.define(:params)
|
|
18
|
+
|
|
19
|
+
# Project-owned registry of production replay roots.
|
|
20
|
+
class ReplayRegistry
|
|
21
|
+
OPTION_NAMES = %i[
|
|
22
|
+
limit trace_ids name max_concurrency code_change_description
|
|
23
|
+
code_change_files experiment_group_id dataset_id grader_ids mock
|
|
24
|
+
mock_override adapt_inputs db_branch
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
def initialize
|
|
28
|
+
@entries = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def names
|
|
32
|
+
@entries.keys.freeze
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Register the exact traced receiver method production invokes.
|
|
36
|
+
#
|
|
37
|
+
# A +bitfab_span+ method carries its trace function key automatically.
|
|
38
|
+
# Handler-instrumented or otherwise plain methods must pass
|
|
39
|
+
# +trace_function_key:+ explicitly. Pass executable per-function replay
|
|
40
|
+
# behavior such as +mock_override:+ and +adapt_inputs:+ as keyword options;
|
|
41
|
+
# the installed command forwards them to +Bitfab::Client#replay+.
|
|
42
|
+
def register(name, receiver, method_name, client: nil, trace_function_key: nil, options_factory: nil, **options)
|
|
43
|
+
raise ArgumentError, "Replay registry names cannot be empty." if name.to_s.empty?
|
|
44
|
+
raise ArgumentError, "Replay registry already contains '#{name}'." if @entries.key?(name.to_s)
|
|
45
|
+
if options_factory && !options_factory.respond_to?(:call)
|
|
46
|
+
raise ArgumentError, "Replay registry options_factory must be callable."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
key = trace_function_key || Traceable.trace_function_key_for(receiver, method_name)
|
|
50
|
+
unless key
|
|
51
|
+
raise ArgumentError,
|
|
52
|
+
"Replay registry entry uses a plain method. Set trace_function_key: " \
|
|
53
|
+
"to the key its production handler records."
|
|
54
|
+
end
|
|
55
|
+
validate_options(options)
|
|
56
|
+
|
|
57
|
+
@entries[name.to_s] = ReplayRegistration.new(
|
|
58
|
+
client: client || Bitfab.client,
|
|
59
|
+
receiver:,
|
|
60
|
+
method_name: method_name.to_sym,
|
|
61
|
+
trace_function_key: key,
|
|
62
|
+
options: options.freeze,
|
|
63
|
+
options_factory:
|
|
64
|
+
)
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def fetch(name)
|
|
69
|
+
@entries.fetch(name)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def validate_options(options)
|
|
73
|
+
unknown = options.keys - OPTION_NAMES
|
|
74
|
+
return if unknown.empty?
|
|
75
|
+
|
|
76
|
+
raise ArgumentError,
|
|
77
|
+
"Unknown replay registry option(s): #{unknown.sort.join(", ")}. " \
|
|
78
|
+
"Allowed options: #{OPTION_NAMES.sort.join(", ")}."
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Argument parsing and output for the SDK-owned replay command.
|
|
83
|
+
module ReplayCli
|
|
84
|
+
module_function
|
|
85
|
+
|
|
86
|
+
def run(registry, argv: ARGV, stdout: $stdout, stderr: $stderr)
|
|
87
|
+
args = parse(registry, argv.dup)
|
|
88
|
+
registration = registry.fetch(args.fetch(:pipeline))
|
|
89
|
+
options = registration.options.dup
|
|
90
|
+
params = load_parameters(args[:params], args.fetch(:param, []))
|
|
91
|
+
if registration.options_factory
|
|
92
|
+
dynamic_options = registration.options_factory.call(ReplayRegistryContext.new(params:))
|
|
93
|
+
unless dynamic_options.is_a?(Hash)
|
|
94
|
+
raise ArgumentError, "Replay registry options_factory must return a Hash."
|
|
95
|
+
end
|
|
96
|
+
registry.validate_options(dynamic_options)
|
|
97
|
+
options.merge!(dynamic_options)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if options[:trace_ids] && options[:dataset_id]
|
|
101
|
+
raise ArgumentError,
|
|
102
|
+
"Replay registry options trace_ids and dataset_id select different sources and cannot be used together."
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if args[:trace_ids]
|
|
106
|
+
options.delete(:limit)
|
|
107
|
+
options.delete(:dataset_id)
|
|
108
|
+
options[:trace_ids] = args[:trace_ids]
|
|
109
|
+
elsif args[:dataset_id]
|
|
110
|
+
options.delete(:trace_ids)
|
|
111
|
+
options.delete(:limit)
|
|
112
|
+
elsif args[:limit]
|
|
113
|
+
options.delete(:trace_ids)
|
|
114
|
+
options.delete(:dataset_id)
|
|
115
|
+
options[:limit] = args[:limit]
|
|
116
|
+
elsif options[:trace_ids] || options[:dataset_id]
|
|
117
|
+
options.delete(:limit)
|
|
118
|
+
else
|
|
119
|
+
options[:limit] = options.fetch(:limit, 10)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
%i[name max_concurrency experiment_group_id dataset_id grader_ids mock].each do |key|
|
|
123
|
+
options[key] = args[key] unless args[key].nil?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
unless args[:db_branch].nil?
|
|
127
|
+
if args[:db_branch]
|
|
128
|
+
configured = options[:db_branch]
|
|
129
|
+
options[:db_branch] = (configured.nil? || configured == false) ? true : configured
|
|
130
|
+
else
|
|
131
|
+
options[:db_branch] = false
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if args[:code_change]
|
|
136
|
+
code_change = load_code_change(args[:code_change])
|
|
137
|
+
options[:code_change_description] = code_change.fetch("description")
|
|
138
|
+
options[:code_change_files] = code_change.fetch("files")
|
|
139
|
+
elsif args[:no_code_change]
|
|
140
|
+
options[:code_change_description] = nil
|
|
141
|
+
options[:code_change_files] = nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
reporter = Bitfab.method(:report_replay_progress)
|
|
145
|
+
options[:on_item_start] = reporter
|
|
146
|
+
options[:on_item_finish] = reporter
|
|
147
|
+
|
|
148
|
+
count = options[:trace_ids]&.length || options[:limit] || "dataset"
|
|
149
|
+
stderr.puts "[replay] Replaying #{count} traces from \"#{registration.trace_function_key}\"..."
|
|
150
|
+
|
|
151
|
+
result = begin
|
|
152
|
+
registration.client.replay(
|
|
153
|
+
registration.receiver,
|
|
154
|
+
registration.method_name,
|
|
155
|
+
trace_function_key: registration.trace_function_key,
|
|
156
|
+
**options
|
|
157
|
+
)
|
|
158
|
+
rescue ReplayError => error
|
|
159
|
+
error.items.each do |item|
|
|
160
|
+
item_error = item[:trace_error] || item[:replay_error] || item[:error]
|
|
161
|
+
stderr.puts "#{item[:original_trace_id]}: #{item_error}"
|
|
162
|
+
end
|
|
163
|
+
raise
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
render_summary(args.fetch(:pipeline), result, stderr)
|
|
167
|
+
stdout.puts Bitfab.serialize_replay_result(result)
|
|
168
|
+
result
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def parse(registry, argv)
|
|
172
|
+
if argv.include?("--db-branch") && argv.include?("--no-db-branch")
|
|
173
|
+
raise OptionParser::InvalidArgument, "--db-branch and --no-db-branch cannot be used together"
|
|
174
|
+
end
|
|
175
|
+
args = {}
|
|
176
|
+
parser = OptionParser.new do |options|
|
|
177
|
+
options.banner = "Usage: bitfab-replay <#{registry.names.join("|")}> [options]"
|
|
178
|
+
options.on("--limit N", Integer) { |value| args[:limit] = positive_integer("--limit", value) }
|
|
179
|
+
options.on("--trace-ids IDS") { |value| args[:trace_ids] = comma_separated("--trace-ids", value) }
|
|
180
|
+
options.on("--name NAME") { |value| args[:name] = value }
|
|
181
|
+
options.on("--concurrency N", Integer) do |value|
|
|
182
|
+
args[:max_concurrency] = positive_integer("--concurrency", value)
|
|
183
|
+
end
|
|
184
|
+
options.on("--max-concurrency N", Integer) do |value|
|
|
185
|
+
args[:max_concurrency] = positive_integer("--max-concurrency", value)
|
|
186
|
+
end
|
|
187
|
+
options.on("--code-change PATH") { |value| args[:code_change] = value }
|
|
188
|
+
options.on("--experiment-group-id UUID") { |value| args[:experiment_group_id] = value }
|
|
189
|
+
options.on("--dataset-id UUID") { |value| args[:dataset_id] = value }
|
|
190
|
+
options.on("--grader-ids IDS") { |value| args[:grader_ids] = comma_separated("--grader-ids", value) }
|
|
191
|
+
options.on("--mock STRATEGY", %w[none all marked]) { |value| args[:mock] = value }
|
|
192
|
+
options.on("--db-branch") { args[:db_branch] = true }
|
|
193
|
+
options.on("--no-db-branch") { args[:db_branch] = false }
|
|
194
|
+
options.on("--no-code-change") { args[:no_code_change] = true }
|
|
195
|
+
options.on("--params PATH") { |value| args[:params] = value }
|
|
196
|
+
options.on("--param NAME=VALUE") { |value| (args[:param] ||= []) << value }
|
|
197
|
+
end
|
|
198
|
+
parser.parse!(argv)
|
|
199
|
+
if args[:trace_ids] && args[:dataset_id]
|
|
200
|
+
raise OptionParser::InvalidArgument,
|
|
201
|
+
"--trace-ids and --dataset-id select different replay sources and cannot be used together"
|
|
202
|
+
end
|
|
203
|
+
if args[:code_change] && args[:no_code_change]
|
|
204
|
+
raise OptionParser::InvalidArgument, "--code-change and --no-code-change cannot be used together"
|
|
205
|
+
end
|
|
206
|
+
pipeline = argv.shift
|
|
207
|
+
raise OptionParser::MissingArgument, parser.banner unless pipeline && registry.names.include?(pipeline)
|
|
208
|
+
raise OptionParser::InvalidArgument, "unexpected arguments: #{argv.join(" ")}" unless argv.empty?
|
|
209
|
+
|
|
210
|
+
args.merge(pipeline:)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def positive_integer(flag, value)
|
|
214
|
+
raise OptionParser::InvalidArgument, "#{flag} must be a positive integer" if value < 1
|
|
215
|
+
|
|
216
|
+
value
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def comma_separated(flag, value)
|
|
220
|
+
values = value.split(",").map(&:strip).reject(&:empty?)
|
|
221
|
+
raise OptionParser::InvalidArgument, "#{flag} must contain at least one value" if values.empty?
|
|
222
|
+
|
|
223
|
+
values
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def load_code_change(path)
|
|
227
|
+
value = JSON.parse(File.read(path))
|
|
228
|
+
unless value.is_a?(Hash) && value["description"].is_a?(String) && value["files"].is_a?(Array)
|
|
229
|
+
raise ArgumentError, "Invalid --code-change file '#{path}': expected { description, files }."
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
value
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def parse_parameter(raw)
|
|
236
|
+
key, separator, value = raw.partition("=")
|
|
237
|
+
key = key.strip
|
|
238
|
+
if separator.empty? || key.empty?
|
|
239
|
+
raise OptionParser::InvalidArgument,
|
|
240
|
+
"Invalid --param '#{raw}': expected a non-empty name=value pair"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
parsed = begin
|
|
244
|
+
JSON.parse(value)
|
|
245
|
+
rescue JSON::ParserError
|
|
246
|
+
value
|
|
247
|
+
end
|
|
248
|
+
[key, parsed]
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def load_parameters(path, raw_parameters)
|
|
252
|
+
params = {}
|
|
253
|
+
if path
|
|
254
|
+
value = JSON.parse(File.read(path))
|
|
255
|
+
unless value.is_a?(Hash)
|
|
256
|
+
raise ArgumentError, "Invalid --params file '#{path}': expected a JSON object."
|
|
257
|
+
end
|
|
258
|
+
params.merge!(value)
|
|
259
|
+
end
|
|
260
|
+
raw_parameters.each do |raw|
|
|
261
|
+
key, value = parse_parameter(raw)
|
|
262
|
+
params[key] = value
|
|
263
|
+
end
|
|
264
|
+
params.freeze
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def render_summary(pipeline, result, stderr)
|
|
268
|
+
same = 0
|
|
269
|
+
changed = 0
|
|
270
|
+
errors = 0
|
|
271
|
+
result[:items].each do |item|
|
|
272
|
+
if item[:error]
|
|
273
|
+
errors += 1
|
|
274
|
+
elsif item[:result] == item[:original_output]
|
|
275
|
+
same += 1
|
|
276
|
+
else
|
|
277
|
+
changed += 1
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
stderr.puts "\n─── Summary ───"
|
|
282
|
+
stderr.puts " Pipeline: #{pipeline}"
|
|
283
|
+
stderr.puts " Replayed: #{result[:items].length}"
|
|
284
|
+
stderr.puts " Same: #{same}"
|
|
285
|
+
stderr.puts " Changed: #{changed}"
|
|
286
|
+
stderr.puts " Errors: #{errors}" if errors > 0
|
|
287
|
+
stderr.puts "\n #{result[:test_run_url]}"
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -15,6 +15,8 @@ require_relative "bitfab/replay"
|
|
|
15
15
|
require_relative "bitfab/replay_branch"
|
|
16
16
|
require_relative "bitfab/client"
|
|
17
17
|
require_relative "bitfab/traceable"
|
|
18
|
+
require_relative "bitfab/replay_registry"
|
|
19
|
+
require_relative "bitfab/replay_cli"
|
|
18
20
|
|
|
19
21
|
module Bitfab
|
|
20
22
|
# No-op span handle returned when outside a span context.
|
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bitfab
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.38.
|
|
4
|
+
version: 0.38.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
@@ -131,11 +131,13 @@ description: Client library for sending function execution spans to the Bitfab A
|
|
|
131
131
|
Provides automatic tracing with nested span support.
|
|
132
132
|
email:
|
|
133
133
|
- team@goharvest.ai
|
|
134
|
-
executables:
|
|
134
|
+
executables:
|
|
135
|
+
- bitfab-replay
|
|
135
136
|
extensions: []
|
|
136
137
|
extra_rdoc_files: []
|
|
137
138
|
files:
|
|
138
139
|
- README.md
|
|
140
|
+
- exe/bitfab-replay
|
|
139
141
|
- lib/bitfab.rb
|
|
140
142
|
- lib/bitfab/client.rb
|
|
141
143
|
- lib/bitfab/compress.rb
|
|
@@ -147,6 +149,8 @@ files:
|
|
|
147
149
|
- lib/bitfab/payload_budget.rb
|
|
148
150
|
- lib/bitfab/replay.rb
|
|
149
151
|
- lib/bitfab/replay_branch.rb
|
|
152
|
+
- lib/bitfab/replay_cli.rb
|
|
153
|
+
- lib/bitfab/replay_registry.rb
|
|
150
154
|
- lib/bitfab/serialize.rb
|
|
151
155
|
- lib/bitfab/span_context.rb
|
|
152
156
|
- lib/bitfab/traceable.rb
|