Skip to content

Commit 51d53c4

Browse files
committed
feat: add --out-dir option
1 parent 34afaa0 commit 51d53c4

File tree

7 files changed

+82
-45
lines changed

7 files changed

+82
-45
lines changed

README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,34 @@ A recorder of Ruby programs that produces [CodeTracer](https://github.com/metacr
66
> Currently it is in a very early phase: we're welcoming contribution and discussion!
77
88

9-
### usage
9+
### Usage
1010

1111
you can currently use it directly with
1212

1313
```bash
14-
ruby trace.rb <path to ruby file>
15-
# produces several trace json files in the current directory
16-
# or in the folder of `$CODETRACER_DB_TRACE_PATH` if such an env var is defined
14+
ruby trace.rb [--out-dir DIR] <path to ruby file>
15+
# produces several trace json files in DIR,
16+
# or in `$CODETRACER_RUBY_RECORDER_OUT_DIR` if DIR is not provided.
17+
# Defaults to the current directory.
18+
# Pass --help to list all options.
1719
```
1820

1921
You can also invoke a lightweight CLI that loads the native tracer extension
2022
directly:
2123

2224
```bash
23-
ruby src/native_trace.rb <path to ruby file>
25+
ruby src/native_trace.rb [--out-dir DIR] <path to ruby file>
26+
# Uses DIR or `$CODETRACER_RUBY_RECORDER_OUT_DIR` to choose where traces are saved.
2427
```
2528

2629
however you probably want to use it in combination with CodeTracer, which would be released soon.
2730

28-
### env variables
31+
### ENV variables
2932

3033
* if you pass `CODETRACER_RUBY_TRACER_DEBUG=1`, you enables some additional debug-related logging
31-
* `CODETRACER_DB_TRACE_PATH` can be used to override the path to `trace.json` (it's used internally by codetracer as well)
34+
* `CODETRACER_RUBY_RECORDER_OUT_DIR` can be used to specify the directory for trace files
3235

33-
## future directions
36+
## Future directions
3437

3538
The current Ruby support is a prototype. In the future, it may be expanded to function in a way to similar to the more complete implementations, e.g. [Noir](https://github.com/blocksense-network/noir/tree/blocksense/tooling/tracer).
3639

ext/native_tracer/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ The produced shared library can be required from Ruby:
2020
require_relative 'target/release/libcodetracer_ruby_recorder'
2121
```
2222

23-
Once loaded, the tracer starts writing a trace to `trace.json` or the
24-
path specified via the `CODETRACER_DB_TRACE_PATH` environment variable.
23+
Once loaded, the tracer starts writing a trace to `trace.json` in the
24+
directory specified via the `CODETRACER_RUBY_RECORDER_OUT_DIR` environment
25+
variable (defaults to the current directory).
2526

2627
## Publishing platform-specific gems
2728

src/native_trace.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,29 @@
22
# SPDX-License-Identifier: MIT
33
# Simple utility loading the native tracer extension and executing a program.
44

5+
require 'optparse'
6+
7+
options = {}
8+
parser = OptionParser.new do |opts|
9+
opts.banner = "usage: ruby native_trace.rb [options] <program> [args]"
10+
opts.on('-o DIR', '--out-dir DIR', 'Directory to write trace files') do |dir|
11+
options[:out_dir] = dir
12+
end
13+
opts.on('-h', '--help', 'Print this help') do
14+
puts opts
15+
exit
16+
end
17+
end
18+
parser.order!
19+
520
if ARGV.empty?
6-
$stderr.puts("usage: ruby native_trace.rb <program> [args]")
21+
$stderr.puts parser
722
exit 1
823
end
924

25+
out_dir = options[:out_dir] || ENV['CODETRACER_RUBY_RECORDER_OUT_DIR'] || Dir.pwd
26+
ENV['CODETRACER_RUBY_RECORDER_OUT_DIR'] = out_dir
27+
1028
# Path to the compiled native extension
1129
ext_path = File.expand_path('../ext/native_tracer/target/release/libcodetracer_ruby_recorder', __dir__)
1230
require ext_path

src/recorder.rb

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def to_data_for_json
4242
{variable_id: self.variable_id, value: value.to_data_for_json}
4343
end
4444
end
45-
45+
4646

4747
ValueRecord = Struct.new(:kind, :type_id, :i, :b, :text, :r, :msg, :elements, :is_slice, :field_values, keyword_init: true) do
4848
def to_data_for_json
@@ -108,7 +108,7 @@ def to_data_for_json
108108
class TraceRecord
109109
# part of the final trace
110110
attr_accessor :steps, :calls, :variables, :events, :types, :flow, :paths
111-
111+
112112
# internal helpers
113113
attr_accessor :stack, :step_stack, :exprs, :tracing
114114
attr_accessor :t1, :t2, :t3, :t4 # tracepoints
@@ -166,7 +166,7 @@ def path_id(path)
166166
path_id
167167
end
168168
end
169-
169+
170170
def register_step(path, line)
171171
step_record = StepRecord.new(self.path_id(path), line)
172172
@events << [:Step, step_record] # because we convert later to {Step: step-record}: default enum json format in serde/rust
@@ -245,7 +245,7 @@ def load_exprs(path)
245245
{}
246246
end
247247

248-
def serialize(program)
248+
def serialize(program, out_dir = nil)
249249
if ENV["CODETRACER_RUBY_TRACER_DEBUG"] == "1"
250250
pp @events
251251
end
@@ -258,22 +258,25 @@ def serialize(program)
258258
workdir: Dir.pwd
259259
}
260260
# pp output
261-
261+
262262
json_output = JSON.pretty_generate(output)
263263
metadata_json_output = JSON.pretty_generate(metadata_output)
264264
paths_json_output = JSON.pretty_generate($codetracer_record.paths)
265265

266-
trace_path = ENV["CODETRACER_DB_TRACE_PATH"] || "trace.json"
267-
trace_folder = File.dirname(trace_path)
266+
out_dir = out_dir.nil? || out_dir.empty? ?
267+
(ENV["CODETRACER_RUBY_RECORDER_OUT_DIR"] || ".") : out_dir
268+
269+
trace_folder = out_dir
268270
FileUtils.mkdir_p(trace_folder)
269-
trace_metadata_path = File.join(trace_folder , "trace_metadata.json")
270-
trace_paths_path = File.join(trace_folder , "trace_paths.json")
271-
271+
trace_path = File.join(trace_folder, "trace.json")
272+
trace_metadata_path = File.join(trace_folder, "trace_metadata.json")
273+
trace_paths_path = File.join(trace_folder, "trace_paths.json")
274+
272275
# p trace_path, json_output
273276
File.write(trace_path, json_output)
274277
File.write(trace_metadata_path, metadata_json_output)
275278
File.write(trace_paths_path, paths_json_output)
276-
279+
277280
$stderr.write("=================================================\n")
278281
$stderr.write("codetracer ruby tracer: saved trace to #{trace_folder}\n")
279282
end

src/trace.rb

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# See LICENSE file in the project root for full license information.
44

55
require 'json'
6+
require 'optparse'
67
require_relative 'recorder'
78

89
# Warning:
@@ -172,7 +173,7 @@ def record_return(tp)
172173
old_puts "return"
173174
return_value = to_value(tp.return_value)
174175
@record.register_step(tp.path, tp.lineno)
175-
# return value support inspired by existing IDE-s/envs like
176+
# return value support inspired by existing IDE-s/envs like
176177
# Visual Studio/JetBrains IIRC
177178
# (Nikola Gamzakov showed me some examples)
178179
@record.register_variable("<return_value>", return_value)
@@ -192,18 +193,18 @@ def record_step(tp)
192193
def record_event(caller, content)
193194
# reason/effect are on different steps:
194195
# reason: before `p` is called;
195-
# effect: now, when the args are evaluated
196+
# effect: now, when the args are evaluated
196197
# which can happen after many calls/steps;
197198
# maybe add a step for this call?
198199
begin
199200
location = caller[0].split[0].split(':')[0..1]
200201
path, line = location[0], location[1].to_i
201202
@record.register_step(path, line)
202203
rescue
203-
# ignore for now: we'll just jump to last previous step
204+
# ignore for now: we'll just jump to last previous step
204205
# which might be from args
205206
end
206-
# start is last step on this level: log for reason: the previous step on this level
207+
# start is last step on this level: log for reason: the previous step on this level
207208
@record.events << [:Event, RecordEvent.new(EVENT_KIND_WRITE, content, "")]
208209
end
209210

@@ -231,7 +232,7 @@ def deactivate
231232
end
232233

233234
private
234-
235+
235236
def load_variables(binding)
236237
if !binding.nil?
237238
# $stdout.write binding.local_variables
@@ -243,19 +244,31 @@ def load_variables(binding)
243244
else
244245
[]
245246
end
246-
end
247+
end
247248
end
248249

249250

250251
$tracer = Tracer.new($codetracer_record)
251252

252253
if __FILE__ == $PROGRAM_NAME
253-
if ARGV[0].nil?
254-
$stderr.puts('ruby trace.rb <program> [<args>]')
255-
exit(1)
254+
options = {}
255+
parser = OptionParser.new do |opts|
256+
opts.banner = "usage: ruby trace.rb [options] <program> [args]"
257+
opts.on('-o DIR', '--out-dir DIR', 'Directory to write trace files') do |dir|
258+
options[:out_dir] = dir
259+
end
260+
opts.on('-h', '--help', 'Print this help') do
261+
puts opts
262+
exit
263+
end
256264
end
265+
parser.order!
257266

258-
program = ARGV[0]
267+
program = ARGV.shift
268+
if program.nil?
269+
$stderr.puts parser
270+
exit 1
271+
end
259272

260273
$tracer.record.register_call('', 1, '<top-level>', [])
261274
$tracer.ignore('lib/ruby')
@@ -264,8 +277,6 @@ def load_variables(binding)
264277
$tracer.ignore('<internal:')
265278
$tracer.ignore('gems/')
266279

267-
trace_args = ARGV
268-
ARGV = ARGV[1..-1]
269280
$tracer.activate
270281
begin
271282
Kernel.load(program)
@@ -278,9 +289,9 @@ def load_variables(binding)
278289
old_puts '====================='
279290
old_puts ''
280291
end
281-
ARGV = trace_args
282292

283293
$tracer.stop_tracing
284294

285-
$tracer.record.serialize(program)
295+
out_dir = options[:out_dir] || ENV['CODETRACER_RUBY_RECORDER_OUT_DIR'] || Dir.pwd
296+
$tracer.record.serialize(program, out_dir)
286297
end

test/benchmarks/run_benchmark.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
abort("Unknown benchmark '#{BENCHMARK}'")
1515
end
1616

17-
PROGRAM = File.expand_path("programs/#{BENCHMARK}.rb", __dir__)
17+
PROGRAM = File.join('test', 'benchmarks', 'programs', "#{BENCHMARK}.rb")
1818
FIXTURE = File.expand_path("fixtures/#{BENCHMARK}_trace.json", __dir__)
1919
TMP_DIR = File.expand_path('tmp', __dir__)
20-
OUTPUT = File.join(TMP_DIR, "#{BENCHMARK}_trace.json")
20+
OUTPUT_DIR = File.join(TMP_DIR, BENCHMARK)
2121
EXPECTED_HASH = HASHES[BENCHMARK]
2222

2323
FileUtils.mkdir_p(TMP_DIR)
24+
FileUtils.mkdir_p(OUTPUT_DIR)
2425

2526
unless File.exist?(FIXTURE) && Digest::SHA256.file(FIXTURE).hexdigest == EXPECTED_HASH
2627
warn "Reference trace missing or corrupt. Attempting to fetch via git lfs..."
@@ -31,8 +32,7 @@
3132
raise 'reference trace hash mismatch' unless Digest::SHA256.file(FIXTURE).hexdigest == EXPECTED_HASH
3233

3334
elapsed = Benchmark.realtime do
34-
env = { 'CODETRACER_DB_TRACE_PATH' => OUTPUT }
35-
system(env, 'ruby', File.expand_path('../../src/trace.rb', __dir__), PROGRAM)
35+
system('ruby', File.expand_path('../../src/trace.rb', __dir__), '--out-dir', OUTPUT_DIR, PROGRAM)
3636
raise 'trace failed' unless $?.success?
3737
end
3838
puts "Benchmark runtime: #{(elapsed * 1000).round} ms"
@@ -43,7 +43,8 @@ def files_identical?(a, b)
4343
File.binread(a) == File.binread(b)
4444
end
4545

46-
if files_identical?(FIXTURE, OUTPUT)
46+
OUTPUT_TRACE = File.join(OUTPUT_DIR, 'trace.json')
47+
if files_identical?(FIXTURE, OUTPUT_TRACE)
4748
puts 'Trace matches reference.'
4849
else
4950
warn 'Trace differs from reference!'

test/test_tracer.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ def run_trace(program_name)
1414
base = File.basename(program_name, '.rb')
1515
Dir.chdir(File.expand_path('..', __dir__)) do
1616
program = File.join('test', 'programs', program_name)
17-
output = File.join('test', 'tmp', "#{base}_trace.json")
18-
env = { 'CODETRACER_DB_TRACE_PATH' => output }
19-
system(env, 'ruby', 'src/trace.rb', program)
17+
out_dir = File.join('test', 'tmp', base)
18+
FileUtils.mkdir_p(out_dir)
19+
system('ruby', 'src/trace.rb', '--out-dir', out_dir, program)
2020
raise "trace failed" unless $?.success?
21+
JSON.parse(File.read(File.join(out_dir, 'trace.json')))
2122
end
22-
JSON.parse(File.read(File.join(TMP_DIR, "#{base}_trace.json")))
2323
end
2424

2525
def expected_trace(program_name)

0 commit comments

Comments
 (0)