Add --conclude option with tests

This implementation of --conclude is basically identical
to the existing implementation for --prepare. I have not
adapted any existing tests. All tests which test --conclude
functionality are new.

The --conclude commands will run in the same order in which
they are supplied on the cli in the same manner as --prepare
and there must either be one --conclude command, or else the
same number of --concludes as there are commands.
This commit is contained in:
Jack O'Connor authored and David Peter committed 2024-02-02 17:46:23 +01:00
1 parent 91388e7302
commit e13d0cb144
5 files changed
+239 -2

No files matched your search

+38 -2
View File
@@ -110,6 +110,14 @@ impl<'a> Benchmark<'a> {
self.run_intermediate_command(command, error_output)
}
/// Run the command specified by `--conclude`.
fn run_conclusion_command(&self, command: &Command<'_>) -> Result<TimingResult> {
let error_output = "The conclusion command terminated with a non-zero exit code. \
Append ' || true' to the command if you are sure that this can be ignored.";
self.run_intermediate_command(command, error_output)
}
/// Run the benchmark for a single command
pub fn run(&self) -> Result<BenchmarkResult> {
if self.options.output_style != OutputStyleOption::Disabled {
@@ -146,6 +154,25 @@ impl<'a> Benchmark<'a> {
.transpose()
};
let conclusion_command = self.options.conclusion_command.as_ref().map(|values| {
let conclusion_command = if values.len() == 1 {
&values[0]
} else {
&values[self.number]
};
Command::new_parametrized(
None,
conclusion_command,
self.command.get_parameters().iter().cloned(),
)
});
let run_conclusion_command = || {
conclusion_command
.as_ref()
.map(|cmd| self.run_conclusion_command(cmd))
.transpose()
};
self.run_setup_command(self.command.get_parameters().iter().cloned())?;
// Warmup phase
@@ -163,6 +190,7 @@ impl<'a> Benchmark<'a> {
for _ in 0..self.options.warmup_count {
let _ = run_preparation_command()?;
let _ = self.executor.run_command_and_measure(self.command, None)?;
let _ = run_conclusion_command()?;
if let Some(bar) = progress_bar.as_ref() {
bar.inc(1)
}
@@ -191,10 +219,16 @@ impl<'a> Benchmark<'a> {
let (res, status) = self.executor.run_command_and_measure(self.command, None)?;
let success = status.success();
let conclusion_result = run_conclusion_command()?;
let conclusion_overhead =
conclusion_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead());
// Determine number of benchmark runs
let runs_in_min_time = (self.options.min_benchmarking_time
/ (res.time_real + self.executor.time_overhead() + preparation_overhead))
as u64;
/ (res.time_real
+ self.executor.time_overhead()
+ preparation_overhead
+ conclusion_overhead)) as u64;
let count = {
let min = cmp::max(runs_in_min_time, self.options.run_bounds.min);
@@ -251,6 +285,8 @@ impl<'a> Benchmark<'a> {
if let Some(bar) = progress_bar.as_ref() {
bar.inc(1)
}
run_conclusion_command()?;
}
if let Some(bar) = progress_bar.as_ref() {
+17
View File
@@ -102,6 +102,23 @@ fn build_command() -> Command {
be run prior to the corresponding benchmark command.",
),
)
.arg(
Arg::new("conclude")
.long("conclude")
.short('C')
.action(ArgAction::Append)
.num_args(1)
.value_name("CMD")
.value_hint(ValueHint::CommandString)
.help(
"Execute CMD after each timing run. This is useful for killing \
long-running processes started (e.g. a web server started in --prepare), \
for example.\nThe --conclude option can be specified once for all \
commands or multiple times, once for each command. In the latter case, \
each conclude command will be run after the corresponding benchmark \
command.",
),
)
.arg(
Arg::new("cleanup")
.long("cleanup")
+17
View File
@@ -207,6 +207,9 @@ pub struct Options {
/// Command(s) to run before each timing run
pub preparation_command: Option<Vec<String>>,
/// Command(s) to run after each timing run
pub conclusion_command: Option<Vec<String>>,
/// Command to run before each *batch* of timing runs, i.e. before each individual benchmark
pub setup_command: Option<String>,
@@ -243,6 +246,7 @@ impl Default for Options {
min_benchmarking_time: 3.0,
command_failure_action: CmdFailureAction::RaiseError,
preparation_command: None,
conclusion_command: None,
setup_command: None,
cleanup_command: None,
output_style: OutputStyleOption::Full,
@@ -304,6 +308,10 @@ impl Options {
.get_many::<String>("prepare")
.map(|values| values.map(String::from).collect::<Vec<String>>());
options.conclusion_command = matches
.get_many::<String>("conclude")
.map(|values| values.map(String::from).collect::<Vec<String>>());
options.cleanup_command = matches.get_one::<String>("cleanup").map(String::from);
options.command_output_policy = if matches.get_flag("show-output") {
@@ -432,6 +440,15 @@ impl Options {
);
}
if let Some(conclusion_command) = &self.conclusion_command {
ensure!(
conclusion_command.len() <= 1
|| commands.num_commands() == conclusion_command.len(),
"The '--conclude' option has to be provided just once or N times, where N is the \
number of benchmark commands."
);
}
Ok(())
}
}