2022-02-20 13:44:34 +01:00
|
|
|
use super::benchmark_result::BenchmarkResult;
|
2022-02-22 13:31:03 +01:00
|
|
|
use super::executor::{Executor, MockExecutor, RawExecutor, ShellExecutor};
|
2022-02-21 08:46:10 +01:00
|
|
|
use super::{relative_speed, Benchmark};
|
2024-04-27 20:16:24 +02:00
|
|
|
use colored::*;
|
|
|
|
|
use std::cmp::Ordering;
|
2026-10-02 17:18:56 +08:00
|
|
|
use std::io::{self, Write};
|
2022-02-20 13:44:34 +01:00
|
|
|
|
2024-04-27 20:16:24 +02:00
|
|
|
use crate::command::{Command, Commands};
|
2022-02-07 21:03:23 +01:00
|
|
|
use crate::export::ExportManager;
|
2023-06-03 21:06:50 +02:00
|
|
|
use crate::options::{ExecutorKind, Options, OutputStyleOption, SortOrder};
|
2026-10-02 17:18:56 +08:00
|
|
|
use crate::output::console_writeln;
|
2022-02-07 21:03:23 +01:00
|
|
|
|
2022-02-21 08:46:10 +01:00
|
|
|
use anyhow::Result;
|
2022-02-20 13:44:34 +01:00
|
|
|
|
2022-02-20 14:04:37 +01:00
|
|
|
pub struct Scheduler<'a> {
|
|
|
|
|
commands: &'a Commands<'a>,
|
|
|
|
|
options: &'a Options,
|
|
|
|
|
export_manager: &'a ExportManager,
|
|
|
|
|
results: Vec<BenchmarkResult>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Scheduler<'a> {
|
|
|
|
|
pub fn new(
|
|
|
|
|
commands: &'a Commands,
|
|
|
|
|
options: &'a Options,
|
|
|
|
|
export_manager: &'a ExportManager,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
commands,
|
|
|
|
|
options,
|
|
|
|
|
export_manager,
|
|
|
|
|
results: vec![],
|
2022-02-20 13:44:34 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-20 14:04:37 +01:00
|
|
|
pub fn run_benchmarks(&mut self) -> Result<()> {
|
2022-02-22 08:36:56 +01:00
|
|
|
let mut executor: Box<dyn Executor> = match self.options.executor_kind {
|
2022-02-22 13:31:03 +01:00
|
|
|
ExecutorKind::Raw => Box::new(RawExecutor::new(self.options)),
|
2022-02-22 08:58:57 +01:00
|
|
|
ExecutorKind::Mock(ref shell) => Box::new(MockExecutor::new(shell.clone())),
|
2022-02-22 10:13:06 +01:00
|
|
|
ExecutorKind::Shell(ref shell) => Box::new(ShellExecutor::new(shell, self.options)),
|
2022-02-22 08:36:56 +01:00
|
|
|
};
|
|
|
|
|
|
2024-04-27 20:16:24 +02:00
|
|
|
let reference = self
|
|
|
|
|
.options
|
|
|
|
|
.reference_command
|
|
|
|
|
.as_ref()
|
2025-03-15 19:45:41 +00:00
|
|
|
.map(|cmd| Command::new(self.options.reference_name.as_deref(), cmd));
|
2024-04-27 20:16:24 +02:00
|
|
|
|
2022-02-22 08:36:56 +01:00
|
|
|
executor.calibrate()?;
|
2022-02-20 13:44:34 +01:00
|
|
|
|
2024-04-27 20:16:24 +02:00
|
|
|
for (number, cmd) in reference.iter().chain(self.commands.iter()).enumerate() {
|
2022-02-20 14:04:37 +01:00
|
|
|
self.results
|
2022-02-22 08:36:56 +01:00
|
|
|
.push(Benchmark::new(number, cmd, self.options, &*executor).run()?);
|
2022-02-20 14:04:37 +01:00
|
|
|
|
2023-04-20 12:24:10 +02:00
|
|
|
// We export results after each individual benchmark, because
|
|
|
|
|
// we would risk losing them if a later benchmark fails.
|
2026-10-05 14:59:18 +02:00
|
|
|
self.export_manager
|
2026-10-05 15:07:46 +02:00
|
|
|
.write_results(&self.results, true, self.options.reference_index)?;
|
2022-02-20 14:04:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-02 17:18:56 +08:00
|
|
|
pub fn print_relative_speed_comparison(&self) -> Result<()> {
|
2022-02-20 14:04:37 +01:00
|
|
|
if self.options.output_style == OutputStyleOption::Disabled {
|
2026-10-02 17:18:56 +08:00
|
|
|
return Ok(());
|
2022-02-20 14:04:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.results.len() < 2 {
|
2026-10-02 17:18:56 +08:00
|
|
|
return Ok(());
|
2022-02-20 14:04:37 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-27 20:16:24 +02:00
|
|
|
let reference = self
|
2026-10-05 15:07:46 +02:00
|
|
|
.options
|
|
|
|
|
.reference_index
|
2026-10-05 14:59:18 +02:00
|
|
|
.map(|index| &self.results[index])
|
2024-04-27 20:16:24 +02:00
|
|
|
.unwrap_or_else(|| relative_speed::fastest_of(&self.results));
|
|
|
|
|
|
|
|
|
|
if let Some(annotated_results) = relative_speed::compute_with_check_from_reference(
|
2023-06-03 21:06:50 +02:00
|
|
|
&self.results,
|
2024-04-27 20:16:24 +02:00
|
|
|
reference,
|
2023-06-03 21:06:50 +02:00
|
|
|
self.options.sort_order_speed_comparison,
|
|
|
|
|
) {
|
2026-10-02 17:18:56 +08:00
|
|
|
let mut stdout = io::stdout().lock();
|
2023-06-03 21:06:50 +02:00
|
|
|
match self.options.sort_order_speed_comparison {
|
|
|
|
|
SortOrder::MeanTime => {
|
2026-10-02 17:18:56 +08:00
|
|
|
console_writeln!(stdout, "{}", "Summary".bold())?;
|
2022-02-20 14:04:37 +01:00
|
|
|
|
2024-04-27 20:16:24 +02:00
|
|
|
let reference = annotated_results.iter().find(|r| r.is_reference).unwrap();
|
|
|
|
|
let others = annotated_results.iter().filter(|r| !r.is_reference);
|
2022-02-20 14:04:37 +01:00
|
|
|
|
2026-10-02 17:18:56 +08:00
|
|
|
console_writeln!(
|
|
|
|
|
stdout,
|
2023-06-03 21:06:50 +02:00
|
|
|
" {} ran",
|
2026-10-05 14:14:38 +02:00
|
|
|
reference.result.command_with_unused_parameters().cyan()
|
|
|
|
|
);
|
2022-02-20 14:04:37 +01:00
|
|
|
|
2023-06-03 21:06:50 +02:00
|
|
|
for item in others {
|
2024-06-30 20:48:18 +02:00
|
|
|
let stddev = if let Some(stddev) = item.relative_speed_stddev {
|
|
|
|
|
format!(" ± {}", format!("{:.2}", stddev).green())
|
|
|
|
|
} else {
|
|
|
|
|
"".into()
|
|
|
|
|
};
|
2024-04-27 20:16:24 +02:00
|
|
|
let comparator = match item.relative_ordering {
|
2024-06-30 20:48:18 +02:00
|
|
|
Ordering::Less => format!(
|
|
|
|
|
"{}{} times slower than",
|
|
|
|
|
format!("{:8.2}", item.relative_speed).bold().green(),
|
|
|
|
|
stddev
|
|
|
|
|
),
|
|
|
|
|
Ordering::Greater => format!(
|
|
|
|
|
"{}{} times faster than",
|
|
|
|
|
format!("{:8.2}", item.relative_speed).bold().green(),
|
|
|
|
|
stddev
|
|
|
|
|
),
|
|
|
|
|
Ordering::Equal => format!(
|
|
|
|
|
" As fast ({}{}) as",
|
|
|
|
|
format!("{:.2}", item.relative_speed).bold().green(),
|
|
|
|
|
stddev
|
|
|
|
|
),
|
2024-04-27 20:16:24 +02:00
|
|
|
};
|
2026-10-02 17:18:56 +08:00
|
|
|
console_writeln!(
|
|
|
|
|
stdout,
|
2024-06-30 20:48:18 +02:00
|
|
|
"{} {}",
|
2024-04-27 20:16:24 +02:00
|
|
|
comparator,
|
2026-10-05 14:14:38 +02:00
|
|
|
&item.result.command_with_unused_parameters().magenta()
|
|
|
|
|
);
|
2023-06-03 21:06:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SortOrder::Command => {
|
2026-10-02 17:18:56 +08:00
|
|
|
console_writeln!(stdout, "{}", "Relative speed comparison".bold())?;
|
2023-06-03 21:06:50 +02:00
|
|
|
|
|
|
|
|
for item in annotated_results {
|
2026-10-05 15:07:46 +02:00
|
|
|
let relationship = if self.options.reference_index.is_some() {
|
|
|
|
|
item.reference_label()
|
2026-10-05 14:59:18 +02:00
|
|
|
} else {
|
2026-10-05 15:07:46 +02:00
|
|
|
""
|
2026-10-05 14:59:18 +02:00
|
|
|
};
|
2026-10-02 17:18:56 +08:00
|
|
|
console_writeln!(
|
|
|
|
|
stdout,
|
2026-10-05 14:59:18 +02:00
|
|
|
" {}{} {}{}",
|
2023-06-03 21:06:50 +02:00
|
|
|
format!("{:10.2}", item.relative_speed).bold().green(),
|
2024-04-27 20:16:24 +02:00
|
|
|
if item.is_reference {
|
2023-06-03 21:06:50 +02:00
|
|
|
" ".into()
|
2023-06-03 23:24:11 +02:00
|
|
|
} else if let Some(stddev) = item.relative_speed_stddev {
|
2024-05-31 13:26:35 -07:00
|
|
|
format!(" ± {}", format!("{stddev:5.2}").green())
|
2023-06-03 21:06:50 +02:00
|
|
|
} else {
|
2023-06-03 23:24:11 +02:00
|
|
|
" ".into()
|
2023-06-03 21:06:50 +02:00
|
|
|
},
|
2026-10-05 14:14:38 +02:00
|
|
|
item.result.command_with_unused_parameters(),
|
2026-10-05 14:59:18 +02:00
|
|
|
relationship,
|
2026-10-02 17:18:56 +08:00
|
|
|
)?;
|
2023-06-03 21:06:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-02-20 14:04:37 +01:00
|
|
|
}
|
2022-02-20 13:44:34 +01:00
|
|
|
} else {
|
2026-10-02 17:18:56 +08:00
|
|
|
console_writeln!(
|
|
|
|
|
io::stderr(),
|
2022-02-20 14:04:37 +01:00
|
|
|
"{}: The benchmark comparison could not be computed as some benchmark times are zero. \
|
|
|
|
|
This could be caused by background interference during the initial calibration phase \
|
|
|
|
|
of hyperfine, in combination with very fast commands (faster than a few milliseconds). \
|
2023-04-20 12:32:11 +02:00
|
|
|
Try to re-run the benchmark on a quiet system. If you did not do so already, try the \
|
|
|
|
|
--shell=none/-N option. If it does not help either, you command is most likely too fast \
|
|
|
|
|
to be accurately benchmarked by hyperfine.",
|
2022-02-20 14:04:37 +01:00
|
|
|
"Note".bold().red()
|
2026-10-02 17:18:56 +08:00
|
|
|
)?;
|
2022-02-20 14:04:37 +01:00
|
|
|
}
|
2026-10-02 17:18:56 +08:00
|
|
|
|
|
|
|
|
Ok(())
|
2022-02-20 14:04:37 +01:00
|
|
|
}
|
2023-04-20 12:24:10 +02:00
|
|
|
|
|
|
|
|
pub fn final_export(&self) -> Result<()> {
|
2026-10-05 14:59:18 +02:00
|
|
|
self.export_manager
|
2026-10-05 15:07:46 +02:00
|
|
|
.write_results(&self.results, false, self.options.reference_index)
|
2023-04-20 12:24:10 +02:00
|
|
|
}
|
2022-02-20 13:44:34 +01:00
|
|
|
}
|
2024-12-29 13:40:05 +01:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn generate_results(args: &[&'static str]) -> Result<Vec<BenchmarkResult>> {
|
|
|
|
|
use crate::cli::get_cli_arguments;
|
|
|
|
|
|
|
|
|
|
let args = ["hyperfine", "--debug-mode", "--style=none"]
|
|
|
|
|
.iter()
|
2024-12-29 13:57:53 +01:00
|
|
|
.chain(args);
|
2024-12-29 13:40:05 +01:00
|
|
|
let cli_arguments = get_cli_arguments(args);
|
|
|
|
|
let mut options = Options::from_cli_arguments(&cli_arguments)?;
|
|
|
|
|
|
|
|
|
|
assert_eq!(options.executor_kind, ExecutorKind::Mock(None));
|
|
|
|
|
|
|
|
|
|
let commands = Commands::from_cli_arguments(&cli_arguments)?;
|
2024-12-29 13:57:18 +01:00
|
|
|
let export_manager = ExportManager::from_cli_arguments(
|
|
|
|
|
&cli_arguments,
|
|
|
|
|
options.time_unit,
|
|
|
|
|
options.sort_order_exports,
|
|
|
|
|
)?;
|
2024-12-29 13:40:05 +01:00
|
|
|
|
|
|
|
|
options.validate_against_command_list(&commands)?;
|
|
|
|
|
|
|
|
|
|
let mut scheduler = Scheduler::new(&commands, &options, &export_manager);
|
|
|
|
|
|
|
|
|
|
scheduler.run_benchmarks()?;
|
|
|
|
|
Ok(scheduler.results)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn scheduler_basic() -> Result<()> {
|
|
|
|
|
insta::assert_yaml_snapshot!(generate_results(&["--runs=2", "sleep 0.123", "sleep 0.456"])?, @r#"
|
|
|
|
|
- command: sleep 0.123
|
2026-10-05 14:13:58 +02:00
|
|
|
runs:
|
|
|
|
|
- wall_clock_time: 0.123
|
2026-10-05 14:14:10 +02:00
|
|
|
user_time: 0
|
|
|
|
|
system_time: 0
|
2026-10-05 14:14:25 +02:00
|
|
|
memory_usage_byte: 0
|
|
|
|
|
exit_code: 0
|
2026-10-05 14:13:58 +02:00
|
|
|
- wall_clock_time: 0.123
|
2026-10-05 14:14:10 +02:00
|
|
|
user_time: 0
|
|
|
|
|
system_time: 0
|
2026-10-05 14:14:25 +02:00
|
|
|
memory_usage_byte: 0
|
|
|
|
|
exit_code: 0
|
2024-12-29 13:40:05 +01:00
|
|
|
- command: sleep 0.456
|
2026-10-05 14:13:58 +02:00
|
|
|
runs:
|
|
|
|
|
- wall_clock_time: 0.456
|
2026-10-05 14:14:10 +02:00
|
|
|
user_time: 0
|
|
|
|
|
system_time: 0
|
2026-10-05 14:14:25 +02:00
|
|
|
memory_usage_byte: 0
|
|
|
|
|
exit_code: 0
|
2026-10-05 14:13:58 +02:00
|
|
|
- wall_clock_time: 0.456
|
2026-10-05 14:14:10 +02:00
|
|
|
user_time: 0
|
|
|
|
|
system_time: 0
|
2026-10-05 14:14:25 +02:00
|
|
|
memory_usage_byte: 0
|
|
|
|
|
exit_code: 0
|
2024-12-29 13:40:05 +01:00
|
|
|
"#);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|