Skip to content

Commit f3191ef

Browse files
committed
expose HYPERFINE_ITERATION to intermediate commands
Fixes: sharkdp#781 Expose the `$HYPERFINE_ITERATION` variable to intermediate commands. This will allow iteration-specific prepare/conclude, such as: ```bash hyperfine --runs 5 --prepare 'echo touch file${HYPERFINE_ITERATION}.dat' \ 'echo testing file${HYPERFINE_ITERATION}.dat' \ --conclude 'echo rm file${HYPERFINE_ITERATION}.dat' --show-output ```
1 parent 92cc4f2 commit f3191ef

File tree

3 files changed

+158
-26
lines changed

3 files changed

+158
-26
lines changed

src/benchmark/executor.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub trait Executor {
3737
fn run_command_and_measure(
3838
&self,
3939
command: &Command<'_>,
40-
iteration: BenchmarkIteration,
40+
iteration: &BenchmarkIteration,
4141
command_failure_action: Option<CmdFailureAction>,
4242
output_policy: &CommandOutputPolicy,
4343
) -> Result<(TimingResult, ExitStatus)>;
@@ -57,7 +57,7 @@ pub trait Executor {
5757

5858
fn run_command_and_measure_common(
5959
mut command: std::process::Command,
60-
iteration: BenchmarkIteration,
60+
iteration: &BenchmarkIteration,
6161
command_failure_action: CmdFailureAction,
6262
command_input_policy: &CommandInputPolicy,
6363
command_output_policy: &CommandOutputPolicy,
@@ -115,7 +115,7 @@ impl Executor for RawExecutor<'_> {
115115
fn run_command_and_measure(
116116
&self,
117117
command: &Command<'_>,
118-
iteration: BenchmarkIteration,
118+
iteration: &BenchmarkIteration,
119119
command_failure_action: Option<CmdFailureAction>,
120120
output_policy: &CommandOutputPolicy,
121121
) -> Result<(TimingResult, ExitStatus)> {
@@ -168,7 +168,7 @@ impl Executor for ShellExecutor<'_> {
168168
fn run_command_and_measure(
169169
&self,
170170
command: &Command<'_>,
171-
iteration: BenchmarkIteration,
171+
iteration: &BenchmarkIteration,
172172
command_failure_action: Option<CmdFailureAction>,
173173
output_policy: &CommandOutputPolicy,
174174
) -> Result<(TimingResult, ExitStatus)> {
@@ -232,7 +232,7 @@ impl Executor for ShellExecutor<'_> {
232232
// Just run the shell without any command
233233
let res = self.run_command_and_measure(
234234
&Command::new(None, ""),
235-
BenchmarkIteration::NonBenchmarkRun,
235+
&BenchmarkIteration::NonBenchmarkRun,
236236
None,
237237
&CommandOutputPolicy::Null,
238238
);
@@ -305,7 +305,7 @@ impl Executor for MockExecutor {
305305
fn run_command_and_measure(
306306
&self,
307307
command: &Command<'_>,
308-
_iteration: BenchmarkIteration,
308+
_iteration: &BenchmarkIteration,
309309
_command_failure_action: Option<CmdFailureAction>,
310310
_output_policy: &CommandOutputPolicy,
311311
) -> Result<(TimingResult, ExitStatus)> {

src/benchmark/mod.rs

+36-20
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ impl<'a> Benchmark<'a> {
5959
command: &Command<'_>,
6060
error_output: &'static str,
6161
output_policy: &CommandOutputPolicy,
62+
iteration: &executor::BenchmarkIteration,
6263
) -> Result<TimingResult> {
6364
self.executor
6465
.run_command_and_measure(
6566
command,
66-
executor::BenchmarkIteration::NonBenchmarkRun,
67+
iteration,
6768
Some(CmdFailureAction::RaiseError),
6869
output_policy,
6970
)
@@ -76,6 +77,7 @@ impl<'a> Benchmark<'a> {
7677
&self,
7778
parameters: impl IntoIterator<Item = ParameterNameAndValue<'a>>,
7879
output_policy: &CommandOutputPolicy,
80+
iteration: executor::BenchmarkIteration,
7981
) -> Result<TimingResult> {
8082
let command = self
8183
.options
@@ -87,7 +89,7 @@ impl<'a> Benchmark<'a> {
8789
Append ' || true' to the command if you are sure that this can be ignored.";
8890

8991
Ok(command
90-
.map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy))
92+
.map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy, &iteration))
9193
.transpose()?
9294
.unwrap_or_default())
9395
}
@@ -97,6 +99,7 @@ impl<'a> Benchmark<'a> {
9799
&self,
98100
parameters: impl IntoIterator<Item = ParameterNameAndValue<'a>>,
99101
output_policy: &CommandOutputPolicy,
102+
iteration: executor::BenchmarkIteration,
100103
) -> Result<TimingResult> {
101104
let command = self
102105
.options
@@ -108,7 +111,7 @@ impl<'a> Benchmark<'a> {
108111
Append ' || true' to the command if you are sure that this can be ignored.";
109112

110113
Ok(command
111-
.map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy))
114+
.map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy, &iteration))
112115
.transpose()?
113116
.unwrap_or_default())
114117
}
@@ -118,23 +121,25 @@ impl<'a> Benchmark<'a> {
118121
&self,
119122
command: &Command<'_>,
120123
output_policy: &CommandOutputPolicy,
124+
iteration: &executor::BenchmarkIteration,
121125
) -> Result<TimingResult> {
122126
let error_output = "The preparation command terminated with a non-zero exit code. \
123127
Append ' || true' to the command if you are sure that this can be ignored.";
124128

125-
self.run_intermediate_command(command, error_output, output_policy)
129+
self.run_intermediate_command(command, error_output, output_policy, iteration)
126130
}
127131

128132
/// Run the command specified by `--conclude`.
129133
fn run_conclusion_command(
130134
&self,
131135
command: &Command<'_>,
132136
output_policy: &CommandOutputPolicy,
137+
iteration: executor::BenchmarkIteration,
133138
) -> Result<TimingResult> {
134139
let error_output = "The conclusion command terminated with a non-zero exit code. \
135140
Append ' || true' to the command if you are sure that this can be ignored.";
136141

137-
self.run_intermediate_command(command, error_output, output_policy)
142+
self.run_intermediate_command(command, error_output, output_policy, &iteration)
138143
}
139144

140145
/// Run the benchmark for a single command
@@ -170,10 +175,10 @@ impl<'a> Benchmark<'a> {
170175
)
171176
});
172177

173-
let run_preparation_command = || {
178+
let run_preparation_command = |iteration: &executor::BenchmarkIteration| {
174179
preparation_command
175180
.as_ref()
176-
.map(|cmd| self.run_preparation_command(cmd, output_policy))
181+
.map(|cmd| self.run_preparation_command(cmd, output_policy, iteration))
177182
.transpose()
178183
};
179184

@@ -189,14 +194,18 @@ impl<'a> Benchmark<'a> {
189194
self.command.get_parameters().iter().cloned(),
190195
)
191196
});
192-
let run_conclusion_command = || {
197+
let run_conclusion_command = |iteration: executor::BenchmarkIteration| {
193198
conclusion_command
194199
.as_ref()
195-
.map(|cmd| self.run_conclusion_command(cmd, output_policy))
200+
.map(|cmd| self.run_conclusion_command(cmd, output_policy, iteration))
196201
.transpose()
197202
};
198203

199-
self.run_setup_command(self.command.get_parameters().iter().cloned(), output_policy)?;
204+
self.run_setup_command(
205+
self.command.get_parameters().iter().cloned(),
206+
output_policy,
207+
executor::BenchmarkIteration::NonBenchmarkRun,
208+
)?;
200209

201210
// Warmup phase
202211
if self.options.warmup_count > 0 {
@@ -211,14 +220,15 @@ impl<'a> Benchmark<'a> {
211220
};
212221

213222
for i in 0..self.options.warmup_count {
214-
let _ = run_preparation_command()?;
223+
let warmup_iteration = BenchmarkIteration::Warmup(i);
224+
let _ = run_preparation_command(&warmup_iteration)?;
215225
let _ = self.executor.run_command_and_measure(
216226
self.command,
217-
BenchmarkIteration::Warmup(i),
227+
&warmup_iteration,
218228
None,
219229
output_policy,
220230
)?;
221-
let _ = run_conclusion_command()?;
231+
let _ = run_conclusion_command(warmup_iteration)?;
222232
if let Some(bar) = progress_bar.as_ref() {
223233
bar.inc(1)
224234
}
@@ -239,20 +249,21 @@ impl<'a> Benchmark<'a> {
239249
None
240250
};
241251

242-
let preparation_result = run_preparation_command()?;
252+
let benchmark_iteration = BenchmarkIteration::Benchmark(0);
253+
let preparation_result = run_preparation_command(&benchmark_iteration)?;
243254
let preparation_overhead =
244255
preparation_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead());
245256

246257
// Initial timing run
247258
let (res, status) = self.executor.run_command_and_measure(
248259
self.command,
249-
BenchmarkIteration::Benchmark(0),
260+
&benchmark_iteration,
250261
None,
251262
output_policy,
252263
)?;
253264
let success = status.success();
254265

255-
let conclusion_result = run_conclusion_command()?;
266+
let conclusion_result = run_conclusion_command(benchmark_iteration)?;
256267
let conclusion_overhead =
257268
conclusion_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead());
258269

@@ -295,7 +306,8 @@ impl<'a> Benchmark<'a> {
295306

296307
// Gather statistics (perform the actual benchmark)
297308
for i in 0..count_remaining {
298-
run_preparation_command()?;
309+
let benchmark_iteration = BenchmarkIteration::Benchmark(i + 1);
310+
run_preparation_command(&benchmark_iteration)?;
299311

300312
let msg = {
301313
let mean = format_duration(mean(&times_real), self.options.time_unit);
@@ -308,7 +320,7 @@ impl<'a> Benchmark<'a> {
308320

309321
let (res, status) = self.executor.run_command_and_measure(
310322
self.command,
311-
BenchmarkIteration::Benchmark(i + 1),
323+
&benchmark_iteration,
312324
None,
313325
output_policy,
314326
)?;
@@ -326,7 +338,7 @@ impl<'a> Benchmark<'a> {
326338
bar.inc(1)
327339
}
328340

329-
run_conclusion_command()?;
341+
run_conclusion_command(benchmark_iteration)?;
330342
}
331343

332344
if let Some(bar) = progress_bar.as_ref() {
@@ -441,7 +453,11 @@ impl<'a> Benchmark<'a> {
441453
println!(" ");
442454
}
443455

444-
self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?;
456+
self.run_cleanup_command(
457+
self.command.get_parameters().iter().cloned(),
458+
output_policy,
459+
executor::BenchmarkIteration::NonBenchmarkRun,
460+
)?;
445461

446462
Ok(BenchmarkResult {
447463
command: self.command.get_name(),

tests/integration_tests.rs

+116
Original file line numberDiff line numberDiff line change
@@ -638,3 +638,119 @@ fn windows_quote_before_quote_args() {
638638
.assert()
639639
.success();
640640
}
641+
642+
#[cfg(unix)]
643+
#[test]
644+
fn hyperfine_iteration_env_var_in_prepare_command() {
645+
use tempfile::tempdir;
646+
647+
let tempdir = tempdir().unwrap();
648+
let output_path = tempdir.path().join("iteration_output.txt");
649+
650+
// Write HYPERFINE_ITERATION value to a file during prepare
651+
hyperfine()
652+
.arg("--runs=2")
653+
.arg("--warmup=1")
654+
.arg(format!(
655+
"--prepare=echo $HYPERFINE_ITERATION >> {}",
656+
output_path.to_string_lossy()
657+
))
658+
.arg("echo test")
659+
.assert()
660+
.success();
661+
662+
let contents = std::fs::read_to_string(output_path).unwrap();
663+
let lines: Vec<&str> = contents.lines().collect();
664+
665+
assert_eq!(lines.len(), 3);
666+
assert_eq!(lines[0], "warmup-0");
667+
assert_eq!(lines[1], "0");
668+
assert_eq!(lines[2], "1");
669+
}
670+
671+
#[cfg(windows)]
672+
#[test]
673+
fn hyperfine_iteration_env_var_in_prepare_command() {
674+
use tempfile::tempdir;
675+
676+
let tempdir = tempdir().unwrap();
677+
let output_path = tempdir.path().join("iteration_output.txt");
678+
679+
// Write HYPERFINE_ITERATION value to a file during prepare
680+
hyperfine()
681+
.arg("--runs=2")
682+
.arg("--warmup=1")
683+
.arg(format!(
684+
"--prepare=echo %HYPERFINE_ITERATION% >> {}",
685+
output_path.to_string_lossy()
686+
))
687+
.arg("echo test")
688+
.assert()
689+
.success();
690+
691+
let contents = std::fs::read_to_string(output_path).unwrap();
692+
let lines: Vec<String> = contents.lines().map(|l| l.trim().to_string()).collect();
693+
694+
assert_eq!(lines.len(), 3);
695+
assert_eq!(lines[0], "warmup-0");
696+
assert_eq!(lines[1], "0");
697+
assert_eq!(lines[2], "1");
698+
}
699+
700+
#[cfg(unix)]
701+
#[test]
702+
fn hyperfine_iteration_env_var_in_conclude_command() {
703+
use tempfile::tempdir;
704+
705+
let tempdir = tempdir().unwrap();
706+
let output_path = tempdir.path().join("iteration_output.txt");
707+
708+
// Write HYPERFINE_ITERATION value to a file during conclude
709+
hyperfine()
710+
.arg("--runs=2")
711+
.arg("--warmup=1")
712+
.arg(format!(
713+
"--conclude=echo $HYPERFINE_ITERATION >> {}",
714+
output_path.to_string_lossy()
715+
))
716+
.arg("echo test")
717+
.assert()
718+
.success();
719+
720+
let contents = std::fs::read_to_string(output_path).unwrap();
721+
let lines: Vec<&str> = contents.lines().collect();
722+
723+
assert_eq!(lines.len(), 3);
724+
assert_eq!(lines[0], "warmup-0");
725+
assert_eq!(lines[1], "0");
726+
assert_eq!(lines[2], "1");
727+
}
728+
729+
#[cfg(windows)]
730+
#[test]
731+
fn hyperfine_iteration_env_var_in_conclude_command() {
732+
use tempfile::tempdir;
733+
734+
let tempdir = tempdir().unwrap();
735+
let output_path = tempdir.path().join("iteration_output.txt");
736+
737+
// Write HYPERFINE_ITERATION value to a file during conclude
738+
hyperfine()
739+
.arg("--runs=2")
740+
.arg("--warmup=1")
741+
.arg(format!(
742+
"--conclude=echo %HYPERFINE_ITERATION% >> {}",
743+
output_path.to_string_lossy()
744+
))
745+
.arg("echo test")
746+
.assert()
747+
.success();
748+
749+
let contents = std::fs::read_to_string(output_path).unwrap();
750+
let lines: Vec<String> = contents.lines().map(|l| l.trim().to_string()).collect();
751+
752+
assert_eq!(lines.len(), 3);
753+
assert_eq!(lines[0], "warmup-0");
754+
assert_eq!(lines[1], "0");
755+
assert_eq!(lines[2], "1");
756+
}

0 commit comments

Comments
 (0)