Skip to content

Commit 2578e51

Browse files
committed
Allow using HARNESS environment variable to select harness
This allow `ruby` script direct calls to still select the harness without using any wrapper script.
1 parent 84af30a commit 2578e51

File tree

6 files changed

+108
-52
lines changed

6 files changed

+108
-52
lines changed

README.md

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ To run one or more specific benchmarks and record the data:
9393

9494
### Running a single benchmark
9595

96-
The easiest way to run a single benchmark once is using the `run_once.rb` script:
96+
This is the easiest way to run a single benchmark.
97+
It requires no setup at all and assumes nothing about the Ruby you are benchmarking.
98+
It's also convenient for profiling, debugging, etc, especially since all benchmarked code runs in that process.
9799

100+
```bash
101+
ruby benchmarks/fib.rb
98102
```
99-
./run_once.rb benchmarks/some_benchmark.rb
100-
```
101-
102-
This automatically sets the environment to run the benchmark once (no warmup iterations).
103103

104104
### Benchmark organization
105105

@@ -225,34 +225,35 @@ You can find several test harnesses in the `harness/` directory:
225225
* `chain` - a harness to chain multiple harnesses together
226226
* `mplr` - a harness for multiple iterations with time limits
227227

228-
To use a specific harness, use the `run_once.rb` script:
228+
### Selecting a harness
229229

230-
```
231-
# Use default harness
232-
./run_once.rb benchmarks/railsbench/benchmark.rb
230+
**Using the `HARNESS` environment variable**
233231

234-
# Use the 'once' harness
235-
./run_once.rb --harness=once benchmarks/railsbench/benchmark.rb
232+
```bash
233+
# Run with specific harness
234+
HARNESS=perf ruby benchmarks/fib.rb
235+
HARNESS=stackprof ruby benchmarks/railsbench/benchmark.rb
236+
HARNESS=vernier ruby benchmarks/optcarrot/benchmark.rb
236237

237-
# Use the 'perf' harness
238-
./run_once.rb --harness=perf benchmarks/railsbench/benchmark.rb
238+
# Combine with Ruby options
239+
HARNESS=perf ruby --yjit benchmarks/fib.rb
240+
HARNESS=once ruby --yjit-stats benchmarks/railsbench/benchmark.rb
241+
```
239242

240-
# Use the 'stackprof' harness
241-
./run_once.rb --harness=stackprof benchmarks/railsbench/benchmark.rb
243+
**Alternative: Use `run_once.rb` with `--harness` option**:
242244

243-
# Use the 'vernier' harness
244-
./run_once.rb --harness=vernier benchmarks/railsbench/benchmark.rb
245+
```bash
246+
./run_once.rb --harness=perf benchmarks/railsbench/benchmark.rb
247+
./run_once.rb --harness=stackprof benchmarks/fib.rb
245248

246-
# Pass Ruby options like --yjit (use -- separator)
249+
# With Ruby options (use -- separator)
247250
./run_once.rb -- --yjit benchmarks/railsbench/benchmark.rb
248-
249-
# Combine harness option with Ruby options
250-
./run_once.rb --harness=default -- --yjit-stats benchmarks/railsbench/benchmark.rb
251+
./run_once.rb --harness=perf -- --yjit-stats benchmarks/fib.rb
251252
```
252253

253254
When using `run_benchmarks.rb`, you can specify a harness with the `--harness` option:
254255

255-
```
256+
```bash
256257
./run_benchmarks.rb --harness=once
257258
./run_benchmarks.rb --harness=perf
258259
```
@@ -278,10 +279,13 @@ You can also use `--warmup`, `--bench`, or `--once` to set these environment var
278279
./run_benchmarks.rb railsbench --once
279280
```
280281

281-
You can also use the `run_once.rb` script to run benchmarks just once, for example
282-
with the `--yjit-stats` command-line option:
282+
You can also run a single benchmark directly with Ruby:
283283

284-
```
284+
```bash
285+
# Run once with YJIT stats
286+
ruby --yjit-stats benchmarks/railsbench/benchmark.rb
287+
288+
# Or use run_once.rb script
285289
./run_once.rb -- --yjit-stats benchmarks/railsbench/benchmark.rb
286290
```
287291

@@ -290,13 +294,12 @@ with the `--yjit-stats` command-line option:
290294
There is also a harness to use Linux perf. By default, it only runs a fixed number of iterations.
291295
If `PERF` environment variable is present, it starts the perf subcommand after warmup.
292296

293-
```sh
297+
```bash
294298
# Use `perf record` for both warmup and benchmark
295-
perf record ./run_once.rb --harness=perf -- --yjit-perf=map benchmarks/railsbench/benchmark.rb
299+
HARNESS=perf perf record ruby --yjit-perf=map benchmarks/railsbench/benchmark.rb
296300

297301
# Use `perf record` only for benchmark
298-
PERF=record ./run_once.rb --harness=perf -- --yjit-perf=map benchmarks/railsbench/benchmark.rb
299-
```
302+
HARNESS=perf PERF=record ruby --yjit-perf=map benchmarks/railsbench/benchmark.rb
300303

301304
This is the only harness that uses `run_benchmark`'s argument, `num_itrs_hint`.
302305

harness/perf.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# This is a relatively minimal harness meant for use with Linux perf(1).
44
# Example usage:
55
#
6-
# $ PERF='record -e cycles' ./run_once.rb --harness=perf benchmarks/fib.rb
6+
# $ PERF='record -e cycles' HARNESS=perf ruby benchmarks/fib.rb
77
#
88
# When recording with perf(1), make sure the benchmark runs long enough; you
99
# can tweak the MIN_BENCH_ITRS environment variable to lengthen the run. A race

harness/stackprof.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
# Profile the benchmark (ignoring initialization code) with stackprof.
44
# Customize stackprof options with an env var of STACKPROF_OPTS='key:value,...'.
55
# Usage:
6-
# STACKPROF_OPTS='mode:object' ./run_once.rb --harness=stackprof benchmarks/.../benchmark.rb
7-
# STACKPROF_OPTS='mode:cpu,interval:10' MIN_BENCH_ITRS=10 ./run_once.rb --harness=stackprof benchmarks/.../benchmark.rb
6+
# STACKPROF_OPTS='mode:object' HARNESS=stackprof ruby benchmarks/.../benchmark.rb
7+
# STACKPROF_OPTS='mode:cpu,interval:10' MIN_BENCH_ITRS=10 HARNESS=stackprof ruby benchmarks/.../benchmark.rb
88

99
require_relative "../lib/harness"
1010
require_relative "../lib/harness/extra"

harness/vernier.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
# Profile the benchmark (ignoring initialization code) using vernier and display the profile.
44
# Set NO_VIERWER=1 to disable automatically opening the profile in a browser.
55
# Usage:
6-
# ./run_once.rb --harness=vernier benchmarks/...
7-
# NO_VIEWER=1 ./run_once.rb --harness=vernier benchmarks/...
6+
# HARNESS=vernier ruby benchmarks/...
7+
# NO_VIEWER=1 HARNESS=vernier ruby benchmarks/...
88

99
require_relative "../lib/harness"
1010
require_relative "../lib/harness/extra"

lib/harness/loader.rb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
1-
# Use harness/default.rb by default. You can change it with the --harness option in run_once.rb
2-
# or with -r option when calling Ruby directly.
1+
# Use harness/default.rb by default. You can change it with:
2+
# 1. HARNESS environment variable (preferred, keeps harness close to code)
3+
# 2. --harness option in run_once.rb
4+
# 3. -r option when calling Ruby directly
5+
#
36
# Examples:
4-
# ./run_once.rb benchmarks/railsbench/benchmark.rb # uses harness/default.rb
5-
# ./run_once.rb --harness=once benchmarks/railsbench/benchmark.rb # uses harness/once.rb
7+
# HARNESS=perf ruby benchmarks/railsbench/benchmark.rb # uses harness/perf.rb
8+
# HARNESS=once ruby benchmarks/railsbench/benchmark.rb # uses harness/once.rb
69
# ./run_once.rb --harness=ractor benchmarks/railsbench/benchmark.rb # uses harness/ractor.rb
710

811
# Only load the default harness if no other harness has defined run_benchmark
912
unless defined?(run_benchmark)
13+
harness_name = ENV['HARNESS'] || 'default'
14+
1015
retries = 0
1116
begin
12-
require "default"
17+
require harness_name
1318
rescue LoadError => e
14-
if retries == 0 && e.path == "default"
19+
if retries == 0 && e.path == harness_name
1520
retries += 1
1621
# Add the harness directory to the load path
1722
$LOAD_PATH << File.expand_path("../../harness", __dir__)
1823
retry
1924
end
25+
# Provide helpful error message for invalid harness
26+
if e.path == harness_name
27+
harness_dir = File.expand_path("../../harness", __dir__)
28+
available = Dir.glob("#{harness_dir}/*.rb").map { |f| File.basename(f, '.rb') }.sort
29+
raise LoadError, "Harness '#{harness_name}' not found. Available harnesses: #{available.join(', ')}"
30+
end
2031
raise
2132
end
2233
end

test/run_once_test.rb

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
@script_path = File.expand_path('../run_once.rb', __dir__)
99
@original_env = ENV.to_h
1010

11-
# Create a temp directory with test benchmarks
1211
@tmpdir = Dir.mktmpdir
1312
@test_benchmark = File.join(@tmpdir, 'test_benchmark.rb')
13+
loader_path = File.expand_path('../lib/harness/loader', __dir__)
1414
File.write(@test_benchmark, <<~RUBY)
15+
require "#{loader_path}"
1516
puts "Benchmark executed"
1617
puts "WARMUP_ITRS=\#{ENV['WARMUP_ITRS']}"
1718
puts "MIN_BENCH_ITRS=\#{ENV['MIN_BENCH_ITRS']}"
@@ -21,12 +22,10 @@
2122

2223
after do
2324
FileUtils.rm_rf(@tmpdir) if @tmpdir && Dir.exist?(@tmpdir)
24-
# Restore original environment
2525
ENV.replace(@original_env)
2626
end
2727

2828
def run_script(*args)
29-
# Run the script and capture output
3029
Open3.capture3('ruby', @script_path, *args)
3130
end
3231

@@ -71,7 +70,6 @@ def run_script(*args)
7170
end
7271

7372
it 'loads custom harness when specified' do
74-
# Create a test harness
7573
harness_dir = File.join(File.dirname(@script_path), 'harness')
7674
FileUtils.mkdir_p(harness_dir)
7775
test_harness = File.join(harness_dir, 'test_harness.rb')
@@ -90,6 +88,26 @@ def run_script(*args)
9088
end
9189
end
9290

91+
it 'respects HARNESS environment variable when no --harness option provided' do
92+
harness_dir = File.join(File.dirname(@script_path), 'harness')
93+
FileUtils.mkdir_p(harness_dir)
94+
test_harness = File.join(harness_dir, 'test_harness.rb')
95+
96+
begin
97+
File.write(test_harness, <<~RUBY)
98+
puts "TEST HARNESS LOADED"
99+
RUBY
100+
101+
env = { 'HARNESS' => 'test_harness' }
102+
stdout, _stderr, status = Open3.capture3(env, 'ruby', @script_path, @test_benchmark)
103+
104+
assert status.success?
105+
assert_match(/TEST HARNESS LOADED/, stdout)
106+
ensure
107+
File.delete(test_harness) if File.exist?(test_harness)
108+
end
109+
end
110+
93111
it 'shows error for non-existent harness' do
94112
stdout, _stderr, status = run_script('--harness=nonexistent', @test_benchmark)
95113

@@ -101,23 +119,19 @@ def run_script(*args)
101119

102120
describe 'ractor benchmark detection' do
103121
it 'automatically uses ractor harness for ractor benchmarks' do
104-
# Create a ractor benchmark
105122
ractor_dir = File.join(@tmpdir, 'benchmarks-ractor', 'test')
106123
FileUtils.mkdir_p(ractor_dir)
107124
ractor_benchmark = File.join(ractor_dir, 'benchmark.rb')
108125
File.write(ractor_benchmark, 'puts "Ractor benchmark"')
109126

110127
_stdout, _stderr, _status = run_script(ractor_benchmark)
111128

112-
# The script will try to load ractor harness
113-
# We just verify it detected the ractor path
114129
assert_match(/benchmarks-ractor/, ractor_benchmark)
115130
end
116131
end
117132

118133
describe 'Ruby options pass-through' do
119134
it 'passes Ruby options after -- separator' do
120-
# Create a benchmark that checks for a warning flag
121135
warning_benchmark = File.join(@tmpdir, 'warning_test.rb')
122136
File.write(warning_benchmark, <<~RUBY)
123137
x = 1
@@ -187,7 +201,6 @@ def run_script(*args)
187201

188202
describe 'script examples' do
189203
it 'works with simple benchmark path' do
190-
# Simulates: ./run_once.rb benchmarks/fib.rb
191204
fib_benchmark = File.join(@tmpdir, 'fib.rb')
192205
File.write(fib_benchmark, 'puts "Fibonacci benchmark"')
193206

@@ -198,15 +211,13 @@ def run_script(*args)
198211
end
199212

200213
it 'works with harness option' do
201-
# Simulates: ./run_once.rb --harness=default benchmarks/fib.rb
202214
stdout, _stderr, status = run_script('--harness=default', @test_benchmark)
203215

204216
assert status.success?
205217
assert_match(/Benchmark executed/, stdout)
206218
end
207219

208220
it 'works with Ruby options after -- separator' do
209-
# Simulates: ./run_once.rb -- --yjit benchmarks/fib.rb
210221
stdout, _stderr, status = run_script('--', '--yjit', @test_benchmark)
211222

212223
assert status.success?
@@ -228,7 +239,6 @@ def run_script(*args)
228239
end
229240

230241
it 'identifies first .rb file as benchmark' do
231-
# Even if multiple .rb files mentioned (shouldn't happen), first one wins
232242
other_file = File.join(@tmpdir, 'other.rb')
233243
File.write(other_file, 'puts "Wrong file"')
234244

@@ -246,4 +256,36 @@ def run_script(*args)
246256
assert_match(/invalid option|Error/, stdout)
247257
end
248258
end
259+
260+
describe 'direct ruby execution with HARNESS env var' do
261+
it 'uses HARNESS environment variable when running benchmark directly' do
262+
harness_dir = File.join(File.dirname(@script_path), 'harness')
263+
FileUtils.mkdir_p(harness_dir)
264+
direct_harness = File.join(harness_dir, 'direct_test.rb')
265+
266+
begin
267+
File.write(direct_harness, <<~RUBY)
268+
puts "DIRECT HARNESS RUNNING"
269+
RUBY
270+
271+
env = { 'HARNESS' => 'direct_test' }
272+
stdout, _stderr, status = Open3.capture3(env, 'ruby', @test_benchmark)
273+
274+
assert status.success?, "Direct Ruby execution should work with HARNESS env var"
275+
assert_match(/DIRECT HARNESS RUNNING/, stdout)
276+
assert_match(/Benchmark executed/, stdout)
277+
ensure
278+
File.delete(direct_harness) if File.exist?(direct_harness)
279+
end
280+
end
281+
282+
it 'shows helpful error for invalid HARNESS value' do
283+
env = { 'HARNESS' => 'nonexistent_harness' }
284+
_stdout, stderr, status = Open3.capture3(env, 'ruby', @test_benchmark)
285+
286+
refute status.success?
287+
assert_match(/Harness 'nonexistent_harness' not found/, stderr)
288+
assert_match(/Available harnesses/, stderr)
289+
end
290+
end
249291
end

0 commit comments

Comments
 (0)