Skip to content

Commit 36505bf

Browse files
committed
feat: add binary trace support
upgrade runtime_tracing to 0.12 and allow flushing traces as json or binary; benchmark script now measures native binary format
1 parent 9d1e952 commit 36505bf

File tree

10 files changed

+213
-47
lines changed

10 files changed

+213
-47
lines changed

.agents/codex-setup

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ AGENTS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
44
cd $AGENTS_DIR
55

66
sudo apt-get update
7-
sudo apt-get install -y --no-install-recommends just ruby-full libclang-dev
7+
sudo apt-get install -y --no-install-recommends \
8+
just \
9+
ruby-full \
10+
libclang-dev \
11+
capnproto libcapnp-dev
812

913
pushd ..
1014
just build-extension
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
There is a new version of the runtime_tracing create used by the `gems/codetracer-ruby-recorder/ext/native_tracer/src/lib.rs` module.
2+
3+
The primary new feature is that the create support a new binary format for the trace files and requires the user to choose between json and binary when flushing the trace to a file.
4+
5+
Please migrate the project to the new version of runtime_tracing and enhance the benchmark setup to compare the pure ruby implementation vs json in the native implementation vs the new binary format in the native implementation.

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,38 @@ jobs:
1616
- uses: actions/checkout@v4
1717
with:
1818
lfs: true
19+
- name: Install system dependencies
20+
shell: bash
21+
run: |
22+
if [[ "$RUNNER_OS" == "Linux" ]]; then
23+
sudo apt-get update
24+
sudo apt-get install -y --no-install-recommends libclang-dev capnproto libcapnp-dev pkg-config
25+
# Set environment variables for Rust/bindgen
26+
LLVM_VERSION=$(ls /usr/lib/ | grep llvm | sort -V | tail -1)
27+
echo "LIBCLANG_PATH=/usr/lib/$LLVM_VERSION/lib" >> $GITHUB_ENV
28+
echo "CLANG_PATH=/usr/bin/clang" >> $GITHUB_ENV
29+
elif [[ "$RUNNER_OS" == "macOS" ]]; then
30+
brew install capnp pkg-config
31+
elif [[ "$RUNNER_OS" == "Windows" ]]; then
32+
# Install LLVM/Clang for Windows
33+
choco install llvm -y
34+
# Install vcpkg for Cap'n Proto on Windows
35+
git clone https://github.com/Microsoft/vcpkg.git
36+
cd vcpkg
37+
./bootstrap-vcpkg.bat
38+
./vcpkg install capnproto:x64-windows
39+
echo "VCPKG_ROOT=$(pwd)" >> $GITHUB_ENV
40+
echo "CMAKE_TOOLCHAIN_FILE=$(pwd)/scripts/buildsystems/vcpkg.cmake" >> $GITHUB_ENV
41+
cd ..
42+
fi
1943
- name: Setup Ruby
2044
uses: ruby/setup-ruby@v1
2145
with:
2246
ruby-version: '3.2'
47+
- name: Setup Rust
48+
uses: dtolnay/rust-toolchain@stable
49+
- name: Install Ruby dependencies
50+
run: bundle install
2351
- name: Setup just
2452
uses: extractions/setup-just@v1
2553
- name: Build extension
@@ -33,10 +61,23 @@ jobs:
3361
- uses: actions/checkout@v4
3462
with:
3563
lfs: true
64+
- name: Install system dependencies
65+
shell: bash
66+
run: |
67+
sudo apt-get update
68+
sudo apt-get install -y --no-install-recommends libclang-dev capnproto libcapnp-dev pkg-config
69+
# Set environment variables for Rust/bindgen
70+
LLVM_VERSION=$(ls /usr/lib/ | grep llvm | sort -V | tail -1)
71+
echo "LIBCLANG_PATH=/usr/lib/$LLVM_VERSION/lib" >> $GITHUB_ENV
72+
echo "CLANG_PATH=/usr/bin/clang" >> $GITHUB_ENV
3673
- name: Setup Ruby
3774
uses: ruby/setup-ruby@v1
3875
with:
3976
ruby-version: '3.2'
77+
- name: Setup Rust
78+
uses: dtolnay/rust-toolchain@stable
79+
- name: Install Ruby dependencies
80+
run: bundle install
4081
- name: Setup just
4182
uses: extractions/setup-just@v1
4283
- name: Build extension

flake.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
description = "Development environment for codetracer-ruby-recorder";
33

4-
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
4+
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
55

66
outputs = {
77
self,
@@ -33,6 +33,8 @@
3333
# For build automation
3434
just
3535
git-lfs
36+
37+
capnproto # Required for the native tracer's Cap'n Proto serialization
3638
] ++ pkgs.lib.optionals isLinux [
3739
# C standard library headers required for Ruby C extension compilation on Linux
3840
# Without this, build fails with "stdarg.h file not found" error

gems/codetracer-ruby-recorder/ext/native_tracer/Cargo.lock

Lines changed: 36 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gems/codetracer-ruby-recorder/ext/native_tracer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ crate-type = ["cdylib"]
1111

1212
[dependencies]
1313
rb-sys = "0.9"
14-
runtime_tracing = "0.10.0"
14+
runtime_tracing = "0.12.1"
1515

1616
[build-dependencies]
1717
rb-sys-env = "0.2"

gems/codetracer-ruby-recorder/ext/native_tracer/src/lib.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ fn value_type_id(val: &ValueRecord) -> runtime_tracing::TypeId {
133133
| Reference { type_id, .. }
134134
| Raw { type_id, .. }
135135
| Error { type_id, .. }
136+
| BigInt { type_id, .. }
136137
| None { type_id } => *type_id,
137138
Cell { .. } => runtime_tracing::NONE_TYPE_ID,
138139
}
@@ -266,12 +267,15 @@ unsafe extern "C" fn disable_tracing(self_val: VALUE) -> VALUE {
266267
Qnil.into()
267268
}
268269

269-
fn flush_to_dir(tracer: &Tracer, dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
270+
fn flush_to_dir(tracer: &Tracer, dir: &Path, format: runtime_tracing::TraceEventsFileFormat) -> Result<(), Box<dyn std::error::Error>> {
270271
std::fs::create_dir_all(dir)?;
271-
let events = dir.join("trace.json");
272+
let events = match format {
273+
runtime_tracing::TraceEventsFileFormat::Json => dir.join("trace.json"),
274+
runtime_tracing::TraceEventsFileFormat::Binary => dir.join("trace.bin"),
275+
};
272276
let metadata = dir.join("trace_metadata.json");
273277
let paths = dir.join("trace_paths.json");
274-
tracer.store_trace_events(&events)?;
278+
tracer.store_trace_events(&events, format)?;
275279
tracer.store_trace_metadata(&metadata)?;
276280
tracer.store_trace_paths(&paths)?;
277281
Ok(())
@@ -551,16 +555,32 @@ unsafe fn record_event(tracer: &mut Tracer, path: &str, line: i64, content: &str
551555
}));
552556
}
553557

554-
unsafe extern "C" fn flush_trace(self_val: VALUE, out_dir: VALUE) -> VALUE {
558+
unsafe extern "C" fn flush_trace(self_val: VALUE, out_dir: VALUE, format: VALUE) -> VALUE {
555559
let recorder_ptr = get_recorder(self_val);
556560
let recorder = &mut *recorder_ptr;
557561
let ptr = RSTRING_PTR(out_dir) as *const u8;
558562
let len = RSTRING_LEN(out_dir) as usize;
559563
let slice = std::slice::from_raw_parts(ptr, len);
560564

565+
let fmt = if NIL_P(format) {
566+
runtime_tracing::TraceEventsFileFormat::Json
567+
} else if RB_SYMBOL_P(format) {
568+
let id = rb_sym2id(format);
569+
match CStr::from_ptr(rb_id2name(id)).to_str().unwrap_or("") {
570+
"binary" | "bin" => runtime_tracing::TraceEventsFileFormat::Binary,
571+
"json" => runtime_tracing::TraceEventsFileFormat::Json,
572+
_ => {
573+
rb_raise(rb_eIOError, b"Unknown format\0".as_ptr() as *const c_char);
574+
runtime_tracing::TraceEventsFileFormat::Json
575+
}
576+
}
577+
} else {
578+
runtime_tracing::TraceEventsFileFormat::Json
579+
};
580+
561581
match std::str::from_utf8(slice) {
562582
Ok(path_str) => {
563-
if let Err(e) = flush_to_dir(&recorder.tracer, Path::new(path_str)) {
583+
if let Err(e) = flush_to_dir(&recorder.tracer, Path::new(path_str), fmt) {
564584
rb_raise(rb_eIOError, b"Failed to flush trace: %s\0".as_ptr() as *const c_char, e.to_string().as_ptr() as *const c_char);
565585
}
566586
}
@@ -704,10 +724,10 @@ pub extern "C" fn Init_codetracer_ruby_recorder() {
704724
0
705725
);
706726
rb_define_method(
707-
class,
708-
b"flush_trace\0".as_ptr() as *const c_char,
709-
Some(std::mem::transmute(flush_trace as *const ())),
710-
1
727+
class,
728+
b"flush_trace\0".as_ptr() as *const c_char,
729+
Some(std::mem::transmute(flush_trace as *const ())),
730+
2
711731
);
712732
rb_define_method(
713733
class,

gems/codetracer-ruby-recorder/lib/codetracer_ruby_recorder.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ def self.parse_argv_and_trace_ruby_file(argv)
1515
opts.on('-o DIR', '--out-dir DIR', 'Directory to write trace files') do |dir|
1616
options[:out_dir] = dir
1717
end
18+
opts.on('-f FORMAT', '--format FORMAT', 'trace format: json or binary') do |fmt|
19+
options[:format] = fmt
20+
end
1821
opts.on('-h', '--help', 'Print this help') do
1922
puts opts
2023
exit
@@ -32,11 +35,12 @@ def self.parse_argv_and_trace_ruby_file(argv)
3235
program_args = argv.dup
3336

3437
out_dir = options[:out_dir] || ENV['CODETRACER_RUBY_RECORDER_OUT_DIR'] || Dir.pwd
35-
trace_ruby_file(program, out_dir, program_args)
38+
format = (options[:format] || 'json').to_sym
39+
trace_ruby_file(program, out_dir, program_args, format)
3640
0
3741
end
3842

39-
def self.trace_ruby_file(program, out_dir, program_args = [])
43+
def self.trace_ruby_file(program, out_dir, program_args = [], format = :json)
4044
recorder = RubyRecorder.new
4145
return 1 unless recorder.available?
4246

@@ -56,7 +60,7 @@ def self.trace_ruby_file(program, out_dir, program_args = [])
5660
ARGV.concat(original_argv)
5761

5862
recorder.stop
59-
recorder.flush_trace(out_dir)
63+
recorder.flush_trace(out_dir, format)
6064
end
6165
0
6266
end
@@ -96,8 +100,8 @@ def record_event(path, line, content)
96100
end
97101

98102
# Flush trace to output directory
99-
def flush_trace(out_dir)
100-
@recorder.flush_trace(out_dir) if @recorder
103+
def flush_trace(out_dir, format = :json)
104+
@recorder.flush_trace(out_dir, format) if @recorder
101105
end
102106

103107
# Check if recorder is available

0 commit comments

Comments
 (0)