2023-08-28 22:12:22 +02:00
|
|
|
use std::os::windows::process::CommandExt;
|
2022-10-15 14:36:40 -05:00
|
|
|
use std::process::ExitStatus;
|
2022-02-22 08:36:56 +01:00
|
|
|
|
|
|
|
|
use crate::command::Command;
|
2022-09-11 17:11:05 -05:00
|
|
|
use crate::options::{
|
|
|
|
|
CmdFailureAction, CommandInputPolicy, CommandOutputPolicy, Options, OutputStyleOption, Shell,
|
|
|
|
|
};
|
2022-02-22 08:36:56 +01:00
|
|
|
use crate::output::progress_bar::get_progress_bar;
|
2022-02-22 13:09:56 +01:00
|
|
|
use crate::timer::{execute_and_measure, TimerResult};
|
2022-02-22 11:23:53 +01:00
|
|
|
use crate::util::randomized_environment_offset;
|
2022-02-22 08:36:56 +01:00
|
|
|
use crate::util::units::Second;
|
|
|
|
|
|
|
|
|
|
use super::timing_result::TimingResult;
|
|
|
|
|
|
2022-02-22 11:45:46 +01:00
|
|
|
use anyhow::{bail, Context, Result};
|
2022-02-22 08:36:56 +01:00
|
|
|
use statistical::mean;
|
|
|
|
|
|
|
|
|
|
pub trait Executor {
|
2022-02-22 10:59:33 +01:00
|
|
|
/// Run the given command and measure the execution time
|
|
|
|
|
fn run_command_and_measure(
|
2022-02-22 08:36:56 +01:00
|
|
|
&self,
|
|
|
|
|
command: &Command<'_>,
|
|
|
|
|
command_failure_action: Option<CmdFailureAction>,
|
|
|
|
|
) -> Result<(TimingResult, ExitStatus)>;
|
|
|
|
|
|
2022-02-22 10:59:33 +01:00
|
|
|
/// Perform a calibration of this executor. For example,
|
|
|
|
|
/// when running commands through a shell, we need to
|
|
|
|
|
/// measure the shell spawning time separately in order
|
|
|
|
|
/// to subtract it from the full runtime later.
|
2022-02-22 08:36:56 +01:00
|
|
|
fn calibrate(&mut self) -> Result<()>;
|
|
|
|
|
|
2022-02-22 10:59:33 +01:00
|
|
|
/// Return the time overhead for this executor when
|
|
|
|
|
/// performing a measurement. This should return the time
|
|
|
|
|
/// that is being used in addition to the actual runtime
|
|
|
|
|
/// of the command.
|
2022-02-22 08:36:56 +01:00
|
|
|
fn time_overhead(&self) -> Second;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-22 13:09:56 +01:00
|
|
|
fn run_command_and_measure_common(
|
|
|
|
|
mut command: std::process::Command,
|
|
|
|
|
command_failure_action: CmdFailureAction,
|
2022-09-11 16:20:22 -05:00
|
|
|
command_input_policy: &CommandInputPolicy,
|
2022-05-14 13:21:41 -04:00
|
|
|
command_output_policy: &CommandOutputPolicy,
|
2022-02-22 13:09:56 +01:00
|
|
|
command_name: &str,
|
|
|
|
|
) -> Result<TimerResult> {
|
2022-09-11 16:20:22 -05:00
|
|
|
let stdin = command_input_policy.get_stdin()?;
|
2023-03-15 09:09:44 +01:00
|
|
|
let (stdout, stderr) = command_output_policy.get_stdout_stderr()?;
|
2022-09-11 16:20:22 -05:00
|
|
|
command.stdin(stdin).stdout(stdout).stderr(stderr);
|
2022-02-22 13:09:56 +01:00
|
|
|
|
|
|
|
|
command.env(
|
|
|
|
|
"HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET",
|
|
|
|
|
randomized_environment_offset::value(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let result = execute_and_measure(command)
|
|
|
|
|
.with_context(|| format!("Failed to run command '{}'", command_name))?;
|
|
|
|
|
|
|
|
|
|
if command_failure_action == CmdFailureAction::RaiseError && !result.status.success() {
|
|
|
|
|
bail!(
|
|
|
|
|
"{}. Use the '-i'/'--ignore-failure' option if you want to ignore this. \
|
|
|
|
|
Alternatively, use the '--show-output' option to debug what went wrong.",
|
|
|
|
|
result.status.code().map_or(
|
|
|
|
|
"The process has been terminated by a signal".into(),
|
|
|
|
|
|c| format!("Command terminated with non-zero exit code: {}", c)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-22 13:31:03 +01:00
|
|
|
pub struct RawExecutor<'a> {
|
|
|
|
|
options: &'a Options,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> RawExecutor<'a> {
|
|
|
|
|
pub fn new(options: &'a Options) -> Self {
|
|
|
|
|
RawExecutor { options }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Executor for RawExecutor<'a> {
|
|
|
|
|
fn run_command_and_measure(
|
|
|
|
|
&self,
|
|
|
|
|
command: &Command<'_>,
|
|
|
|
|
command_failure_action: Option<CmdFailureAction>,
|
|
|
|
|
) -> Result<(TimingResult, ExitStatus)> {
|
|
|
|
|
let result = run_command_and_measure_common(
|
|
|
|
|
command.get_command()?,
|
|
|
|
|
command_failure_action.unwrap_or(self.options.command_failure_action),
|
2022-09-11 16:20:22 -05:00
|
|
|
&self.options.command_input_policy,
|
2022-05-14 13:21:41 -04:00
|
|
|
&self.options.command_output_policy,
|
2022-02-22 13:31:03 +01:00
|
|
|
&command.get_command_line(),
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
|
TimingResult {
|
|
|
|
|
time_real: result.time_real,
|
|
|
|
|
time_user: result.time_user,
|
|
|
|
|
time_system: result.time_system,
|
|
|
|
|
},
|
|
|
|
|
result.status,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn calibrate(&mut self) -> Result<()> {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn time_overhead(&self) -> Second {
|
|
|
|
|
0.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-22 08:36:56 +01:00
|
|
|
pub struct ShellExecutor<'a> {
|
|
|
|
|
options: &'a Options,
|
|
|
|
|
shell: &'a Shell,
|
|
|
|
|
shell_spawning_time: Option<TimingResult>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> ShellExecutor<'a> {
|
|
|
|
|
pub fn new(shell: &'a Shell, options: &'a Options) -> Self {
|
|
|
|
|
ShellExecutor {
|
|
|
|
|
shell,
|
|
|
|
|
options,
|
|
|
|
|
shell_spawning_time: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Executor for ShellExecutor<'a> {
|
2022-02-22 10:59:33 +01:00
|
|
|
fn run_command_and_measure(
|
2022-02-22 08:36:56 +01:00
|
|
|
&self,
|
|
|
|
|
command: &Command<'_>,
|
|
|
|
|
command_failure_action: Option<CmdFailureAction>,
|
|
|
|
|
) -> Result<(TimingResult, ExitStatus)> {
|
2023-08-28 22:12:22 +02:00
|
|
|
let on_windows_cmd = cfg!(windows) && *self.shell == Shell::Default("cmd.exe");
|
2022-02-22 11:23:53 +01:00
|
|
|
let mut command_builder = self.shell.command();
|
|
|
|
|
command_builder
|
2022-10-22 17:39:05 +02:00
|
|
|
.arg(
|
2023-08-28 22:12:22 +02:00
|
|
|
if on_windows_cmd {
|
2022-10-22 17:39:05 +02:00
|
|
|
"/C"
|
|
|
|
|
} else {
|
|
|
|
|
"-c"
|
|
|
|
|
},
|
2023-08-28 22:12:22 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Windows needs special treatment for its behavior on parsing cmd arguments
|
|
|
|
|
if on_windows_cmd {
|
|
|
|
|
command_builder.raw_arg(command.get_command_line());
|
|
|
|
|
} else {
|
|
|
|
|
command_builder.arg(command.get_command_line());
|
|
|
|
|
}
|
2022-02-22 11:23:53 +01:00
|
|
|
|
2022-02-22 13:09:56 +01:00
|
|
|
let mut result = run_command_and_measure_common(
|
|
|
|
|
command_builder,
|
|
|
|
|
command_failure_action.unwrap_or(self.options.command_failure_action),
|
2022-09-11 16:20:22 -05:00
|
|
|
&self.options.command_input_policy,
|
2022-05-14 13:21:41 -04:00
|
|
|
&self.options.command_output_policy,
|
2022-02-22 13:09:56 +01:00
|
|
|
&command.get_command_line(),
|
|
|
|
|
)?;
|
2022-02-22 08:36:56 +01:00
|
|
|
|
|
|
|
|
// Subtract shell spawning time
|
|
|
|
|
if let Some(spawning_time) = self.shell_spawning_time {
|
2022-02-22 13:01:39 +01:00
|
|
|
result.time_real = (result.time_real - spawning_time.time_real).max(0.0);
|
|
|
|
|
result.time_user = (result.time_user - spawning_time.time_user).max(0.0);
|
|
|
|
|
result.time_system = (result.time_system - spawning_time.time_system).max(0.0);
|
2022-02-22 08:36:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
|
TimingResult {
|
2022-02-22 13:01:39 +01:00
|
|
|
time_real: result.time_real,
|
|
|
|
|
time_user: result.time_user,
|
|
|
|
|
time_system: result.time_system,
|
2022-02-22 08:36:56 +01:00
|
|
|
},
|
|
|
|
|
result.status,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Measure the average shell spawning time
|
|
|
|
|
fn calibrate(&mut self) -> Result<()> {
|
|
|
|
|
const COUNT: u64 = 50;
|
|
|
|
|
let progress_bar = if self.options.output_style != OutputStyleOption::Disabled {
|
|
|
|
|
Some(get_progress_bar(
|
|
|
|
|
COUNT,
|
|
|
|
|
"Measuring shell spawning time",
|
|
|
|
|
self.options.output_style,
|
|
|
|
|
))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut times_real: Vec<Second> = vec![];
|
|
|
|
|
let mut times_user: Vec<Second> = vec![];
|
|
|
|
|
let mut times_system: Vec<Second> = vec![];
|
|
|
|
|
|
|
|
|
|
for _ in 0..COUNT {
|
|
|
|
|
// Just run the shell without any command
|
2022-02-22 10:59:33 +01:00
|
|
|
let res = self.run_command_and_measure(&Command::new(None, ""), None);
|
2022-02-22 08:36:56 +01:00
|
|
|
|
|
|
|
|
match res {
|
|
|
|
|
Err(_) => {
|
|
|
|
|
let shell_cmd = if cfg!(windows) {
|
|
|
|
|
format!("{} /C \"\"", self.shell)
|
|
|
|
|
} else {
|
|
|
|
|
format!("{} -c \"\"", self.shell)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bail!(
|
|
|
|
|
"Could not measure shell execution time. Make sure you can run '{}'.",
|
|
|
|
|
shell_cmd
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Ok((r, _)) => {
|
|
|
|
|
times_real.push(r.time_real);
|
|
|
|
|
times_user.push(r.time_user);
|
|
|
|
|
times_system.push(r.time_system);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(bar) = progress_bar.as_ref() {
|
|
|
|
|
bar.inc(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(bar) = progress_bar.as_ref() {
|
|
|
|
|
bar.finish_and_clear()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.shell_spawning_time = Some(TimingResult {
|
|
|
|
|
time_real: mean(×_real),
|
|
|
|
|
time_user: mean(×_user),
|
|
|
|
|
time_system: mean(×_system),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn time_overhead(&self) -> Second {
|
|
|
|
|
self.shell_spawning_time.unwrap().time_real
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2022-02-22 08:58:57 +01:00
|
|
|
pub struct MockExecutor {
|
|
|
|
|
shell: Option<String>,
|
2022-02-22 08:36:56 +01:00
|
|
|
}
|
|
|
|
|
|
2022-02-22 08:58:57 +01:00
|
|
|
impl MockExecutor {
|
|
|
|
|
pub fn new(shell: Option<String>) -> Self {
|
2022-02-22 08:36:56 +01:00
|
|
|
MockExecutor { shell }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn extract_time<S: AsRef<str>>(sleep_command: S) -> Second {
|
|
|
|
|
assert!(sleep_command.as_ref().starts_with("sleep "));
|
|
|
|
|
sleep_command
|
|
|
|
|
.as_ref()
|
|
|
|
|
.trim_start_matches("sleep ")
|
|
|
|
|
.parse::<Second>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-22 08:58:57 +01:00
|
|
|
impl Executor for MockExecutor {
|
2022-02-22 10:59:33 +01:00
|
|
|
fn run_command_and_measure(
|
2022-02-22 08:36:56 +01:00
|
|
|
&self,
|
|
|
|
|
command: &Command<'_>,
|
|
|
|
|
_command_failure_action: Option<CmdFailureAction>,
|
|
|
|
|
) -> Result<(TimingResult, ExitStatus)> {
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
|
let status = {
|
|
|
|
|
use std::os::unix::process::ExitStatusExt;
|
|
|
|
|
ExitStatus::from_raw(0)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
|
let status = {
|
|
|
|
|
use std::os::windows::process::ExitStatusExt;
|
|
|
|
|
ExitStatus::from_raw(0)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
|
TimingResult {
|
2022-02-22 10:55:58 +01:00
|
|
|
time_real: Self::extract_time(command.get_command_line()),
|
2022-02-22 08:36:56 +01:00
|
|
|
time_user: 0.0,
|
|
|
|
|
time_system: 0.0,
|
|
|
|
|
},
|
|
|
|
|
status,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn calibrate(&mut self) -> Result<()> {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn time_overhead(&self) -> Second {
|
2022-02-22 08:58:57 +01:00
|
|
|
match &self.shell {
|
|
|
|
|
None => 0.0,
|
|
|
|
|
Some(shell) => Self::extract_time(shell),
|
2022-02-22 08:36:56 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_mock_executor_extract_time() {
|
|
|
|
|
assert_eq!(MockExecutor::extract_time("sleep 0.1"), 0.1);
|
|
|
|
|
}
|