Skip to content

Commit 41b11a3

Browse files
authored
ZJIT: Add --zjit-stats=quiet option to collect stats without printing (ruby#14467)
Similar to YJIT's --yjit-stats=quiet, this option allows ZJIT to collect statistics and make them available via the Ruby API without printing them at exit. This is useful for programmatic access to stats without the output noise. - Added print_stats field to Options struct - Modified option parsing to support --zjit-stats=quiet - Added rb_zjit_print_stats_p primitive to check if stats should be printed - Updated zjit.rb to only register at_exit handler when print_stats is true - Update the help text shown by `ruby --help` to indicate that --zjit-stats now accepts an optional =quiet parameter. - Added test for --zjit-stats=quiet option
1 parent 08091ad commit 41b11a3

File tree

4 files changed

+48
-3
lines changed

4 files changed

+48
-3
lines changed

test/ruby/test_zjit.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ def test_stats_enabled
2727
RUBY
2828
end
2929

30+
def test_stats_quiet
31+
# Test that --zjit-stats=quiet collects stats but doesn't print them
32+
script = <<~RUBY
33+
def test = 42
34+
test
35+
test
36+
puts RubyVM::ZJIT.stats_enabled?
37+
RUBY
38+
39+
stats_header = "***ZJIT: Printing ZJIT statistics on exit***"
40+
41+
# With --zjit-stats, stats should be printed to stderr
42+
out, err, status = eval_with_jit(script, stats: true)
43+
assert_success(out, err, status)
44+
assert_includes(err, stats_header)
45+
assert_equal("true\n", out)
46+
47+
# With --zjit-stats=quiet, stats should NOT be printed but still enabled
48+
out, err, status = eval_with_jit(script, stats: :quiet)
49+
assert_success(out, err, status)
50+
refute_includes(err, stats_header)
51+
assert_equal("true\n", out)
52+
end
53+
3054
def test_enable_through_env
3155
child_env = {'RUBY_YJIT_ENABLE' => nil, 'RUBY_ZJIT_ENABLE' => '1'}
3256
assert_in_out_err([child_env, '-v'], '') do |stdout, stderr|
@@ -2490,7 +2514,7 @@ def eval_with_jit(
24902514
if zjit
24912515
args << "--zjit-call-threshold=#{call_threshold}"
24922516
args << "--zjit-num-profiles=#{num_profiles}"
2493-
args << "--zjit-stats" if stats
2517+
args << "--zjit-stats#{"=#{stats}" unless stats == true}" if stats
24942518
args << "--zjit-debug" if debug
24952519
if allowed_iseqs
24962520
jitlist = Tempfile.new("jitlist")

zjit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ rb_zjit_insn_leaf(int insn, const VALUE *opes)
343343
VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self);
344344
VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key);
345345
VALUE rb_zjit_stats_enabled_p(rb_execution_context_t *ec, VALUE self);
346+
VALUE rb_zjit_print_stats_p(rb_execution_context_t *ec, VALUE self);
346347

347348
// Preprocessed zjit.rb generated during build
348349
#include "zjit.rbinc"

zjit.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# for which CRuby is built.
99
module RubyVM::ZJIT
1010
# Avoid calling a Ruby method here to avoid interfering with compilation tests
11-
if Primitive.rb_zjit_stats_enabled_p
11+
if Primitive.rb_zjit_print_stats_p
1212
at_exit { print_stats }
1313
end
1414
end

zjit/src/options.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub struct Options {
4141
/// Enable YJIT statsitics
4242
pub stats: bool,
4343

44+
/// Print stats on exit (when stats is also true)
45+
pub print_stats: bool,
46+
4447
/// Enable debug logging
4548
pub debug: bool,
4649

@@ -78,6 +81,7 @@ impl Default for Options {
7881
exec_mem_bytes: 64 * 1024 * 1024,
7982
num_profiles: DEFAULT_NUM_PROFILES,
8083
stats: false,
84+
print_stats: false,
8185
debug: false,
8286
disable_hir_opt: false,
8387
dump_hir_init: None,
@@ -103,7 +107,7 @@ pub const ZJIT_OPTIONS: &[(&str, &str)] = &[
103107
"Number of calls to trigger JIT (default: 2)."),
104108
("--zjit-num-profiles=num",
105109
"Number of profiled calls before JIT (default: 1, max: 255)."),
106-
("--zjit-stats", "Enable collecting ZJIT statistics."),
110+
("--zjit-stats[=quiet]", "Enable collecting ZJIT statistics (=quiet to suppress output)."),
107111
("--zjit-perf", "Dump ISEQ symbols into /tmp/perf-{}.map for Linux perf."),
108112
("--zjit-log-compiled-iseqs=path",
109113
"Log compiled ISEQs to the file. The file will be truncated."),
@@ -220,6 +224,11 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
220224

221225
("stats", "") => {
222226
options.stats = true;
227+
options.print_stats = true;
228+
}
229+
("stats", "quiet") => {
230+
options.stats = true;
231+
options.print_stats = false;
223232
}
224233

225234
("debug", "") => options.debug = true,
@@ -344,3 +353,14 @@ pub extern "C" fn rb_zjit_stats_enabled_p(_ec: EcPtr, _self: VALUE) -> VALUE {
344353
Qfalse
345354
}
346355
}
356+
357+
/// Return Qtrue if stats should be printed at exit.
358+
#[unsafe(no_mangle)]
359+
pub extern "C" fn rb_zjit_print_stats_p(_ec: EcPtr, _self: VALUE) -> VALUE {
360+
// Builtin zjit.rb calls this even if ZJIT is disabled, so OPTIONS may not be set.
361+
if unsafe { OPTIONS.as_ref() }.is_some_and(|opts| opts.stats && opts.print_stats) {
362+
Qtrue
363+
} else {
364+
Qfalse
365+
}
366+
}

0 commit comments

Comments
 (0)