Skip to content

Commit 81f2535

Browse files
authored
ZJIT: Enable sample rate for side exit tracing (#14696)
1 parent 7ae67e8 commit 81f2535

File tree

5 files changed

+45
-5
lines changed

5 files changed

+45
-5
lines changed

doc/zjit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ make -j
155155

156156
### Tracing side exits
157157

158-
Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program.
158+
Through [Stackprof](https://github.com/tmm1/stackprof), detailed information about the methods that the JIT side-exits from can be displayed after some execution of a program. Optionally, you can use `--zjit-trace-exits-sample-rate=N` to sample every N-th occurrence. Enabling `--zjit-trace-exits-sample-rate=N` will automatically enable `--zjit-trace-exits`.
159159

160160
```bash
161161
./miniruby --zjit-trace-exits script.rb

zjit.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def dump_exit_locations(filename)
128128

129129
File.open(filename, "wb") do |file|
130130
Marshal.dump(RubyVM::ZJIT.exit_locations, file)
131+
file.size
131132
end
132133
end
133134

@@ -275,9 +276,9 @@ def print_stats
275276
def dump_locations # :nodoc:
276277
return unless trace_exit_locations_enabled?
277278

278-
filename = "zjit_exit_locations.dump"
279-
dump_exit_locations(filename)
279+
filename = "zjit_exits_#{Time.now.to_i}.dump"
280+
n_bytes = dump_exit_locations(filename)
280281

281-
$stderr.puts("ZJIT exit locations dumped to `#{filename}`.")
282+
$stderr.puts("#{n_bytes} bytes written to #{filename}.")
282283
end
283284
end

zjit/src/options.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ pub struct Options {
7272
/// Trace and write side exit source maps to /tmp for stackprof.
7373
pub trace_side_exits: bool,
7474

75+
/// Frequency of tracing side exits.
76+
pub trace_side_exits_sample_interval: usize,
77+
7578
/// Dump code map to /tmp for performance profilers.
7679
pub perf: bool,
7780

@@ -98,6 +101,7 @@ impl Default for Options {
98101
dump_lir: false,
99102
dump_disasm: false,
100103
trace_side_exits: false,
104+
trace_side_exits_sample_interval: 0,
101105
perf: false,
102106
allowed_iseqs: None,
103107
log_compiled_iseqs: None,
@@ -120,7 +124,9 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[
120124
("--zjit-log-compiled-iseqs=path",
121125
"Log compiled ISEQs to the file. The file will be truncated."),
122126
("--zjit-trace-exits",
123-
"Record Ruby source location when side-exiting.")
127+
"Record Ruby source location when side-exiting."),
128+
("--zjit-trace-exits-sample-rate",
129+
"Frequency at which to record side exits. Must be `usize`.")
124130
];
125131

126132
#[derive(Clone, Copy, Debug)]
@@ -245,6 +251,13 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
245251
options.trace_side_exits = true;
246252
}
247253

254+
("trace-exits-sample-rate", sample_interval) => {
255+
// Even if `trace_side_exits` is already set, set it.
256+
options.trace_side_exits = true;
257+
// `sample_interval ` must provide a string that can be validly parsed to a `usize`.
258+
options.trace_side_exits_sample_interval = sample_interval.parse::<usize>().ok()?;
259+
}
260+
248261
("debug", "") => options.debug = true,
249262

250263
("disable-hir-opt", "") => options.disable_hir_opt = true,

zjit/src/state.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,16 @@ impl ZJITState {
223223
pub fn get_line_samples() -> Option<&'static mut Vec<i32>> {
224224
ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.line_samples)
225225
}
226+
227+
/// Get number of skipped samples.
228+
pub fn get_skipped_samples() -> Option<&'static mut usize> {
229+
ZJITState::get_instance().exit_locations.as_mut().map(|el| &mut el.skipped_samples)
230+
}
231+
232+
/// Get number of skipped samples.
233+
pub fn set_skipped_samples(n: usize) -> Option<()> {
234+
ZJITState::get_instance().exit_locations.as_mut().map(|el| el.skipped_samples = n)
235+
}
226236
}
227237

228238
/// Initialize ZJIT
@@ -354,6 +364,20 @@ pub extern "C" fn rb_zjit_record_exit_stack(exit_pc: *const VALUE) {
354364
return;
355365
}
356366

367+
// When `trace_side_exits_sample_interval` is zero, then the feature is disabled.
368+
if get_option!(trace_side_exits_sample_interval) != 0 {
369+
// If `trace_side_exits_sample_interval` is set, then can safely unwrap
370+
// both `get_skipped_samples` and `set_skipped_samples`.
371+
let skipped_samples = *ZJITState::get_skipped_samples().unwrap();
372+
if skipped_samples < get_option!(trace_side_exits_sample_interval) {
373+
// Skip sample and increment counter.
374+
ZJITState::set_skipped_samples(skipped_samples + 1).unwrap();
375+
return;
376+
} else {
377+
ZJITState::set_skipped_samples(0).unwrap();
378+
}
379+
}
380+
357381
let (stack_length, frames_buffer, lines_buffer) = record_profiling_frames();
358382

359383
// Can safely unwrap since `trace_side_exits` must be true at this point

zjit/src/stats.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,8 @@ pub struct SideExitLocations {
501501
pub raw_samples: Vec<VALUE>,
502502
/// Line numbers of the iseq caller.
503503
pub line_samples: Vec<i32>,
504+
/// Skipped samples
505+
pub skipped_samples: usize
504506
}
505507

506508
/// Primitive called in zjit.rb

0 commit comments

Comments
 (0)