diff --git a/src/hyperfine/benchmark.rs b/src/hyperfine/benchmark.rs index 68cb25d..a136a66 100644 --- a/src/hyperfine/benchmark.rs +++ b/src/hyperfine/benchmark.rs @@ -1,6 +1,4 @@ use std::io; -use std::process::{Command, ExitStatus, Stdio}; -use std::time::Instant; use colored::*; use statistical::{mean, standard_deviation}; @@ -10,9 +8,10 @@ use hyperfine::internal::{get_progress_bar, max, min, CmdFailureAction, Hyperfin use hyperfine::warnings::Warnings; use hyperfine::format::{format_duration, format_duration_unit}; use hyperfine::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD}; - -#[cfg(not(target_os = "windows"))] -use hyperfine::cputime::{cpu_time_interval, get_cpu_times}; +use hyperfine::timer::Timer; +use hyperfine::timer::wallclocktimer::WallClockTimer; +use hyperfine::timer::cputimer::CPUTimer; +use hyperfine::shell::run_shell_command; /// Results from timing a single shell command #[derive(Debug, Copy, Clone)] @@ -42,13 +41,13 @@ pub fn time_shell_command( failure_action: CmdFailureAction, shell_spawning_time: Option, ) -> io::Result<(TimingResult, bool)> { - let start = Instant::now(); - let timer = cpu_interval_timer(); + let wallclock_timer = WallClockTimer::start(); + let cpu_timer = CPUTimer::start(); let status = run_shell_command(shell_cmd)?; - let (mut time_user, mut time_system) = timer(); - let duration = start.elapsed(); + let mut time_real = wallclock_timer.stop(); + let (mut time_user, mut time_system) = cpu_timer.stop(); if failure_action == CmdFailureAction::RaiseError && !status.success() { return Err(io::Error::new( @@ -58,9 +57,6 @@ pub fn time_shell_command( )); } - // Real time - let mut time_real = duration.as_secs() as f64 + (duration.subsec_nanos() as f64) * 1e-9; - // Correct for shell spawning time if let Some(spawning_time) = shell_spawning_time { time_real = subtract_shell_spawning_time(time_real, spawning_time.time_real); @@ -116,49 +112,6 @@ pub fn mean_shell_spawning_time(style: &OutputStyleOption) -> io::Result Box (f64, f64)> { - let start_cpu = get_cpu_times(); - let timer = move || { - let end_cpu = get_cpu_times(); - let cpu_interval = cpu_time_interval(&start_cpu, &end_cpu); - (cpu_interval.user, cpu_interval.system) - }; - - Box::new(timer) -} - -/// Return a timer Fn that will always return (0,0) when called -#[cfg(target_os = "windows")] -fn cpu_interval_timer() -> Box (f64, f64)> { - Box::new(|| (0f64, 0f64)) -} - -/// Run a standard sh command -#[cfg(not(target_os = "windows"))] -fn run_shell_command(command: &str) -> io::Result { - Command::new("sh") - .arg("-c") - .arg(command) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() -} - -/// Run a Windows command using cmd -#[cfg(target_os = "windows")] -fn run_shell_command(command: &str) -> io::Result { - Command::new("cmd") - .arg(command) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() -} - /// Run the command specified by `--prepare`. fn run_preparation_command(command: &Option) -> io::Result<()> { if let &Some(ref preparation_command) = command { @@ -286,8 +239,15 @@ pub fn run_benchmark( let (user_str, user_unit) = format_duration_unit(user_mean, None); let system_str = format_duration(system_mean, Some(user_unit)); - output_times(mean_str, stddev_str, user_str, system_str); - + println!( + " Time ({} ± {}): {:>8} ± {:>8} [User: {}, System: {}]", + "mean".green().bold(), + "σ".green(), + mean_str.green().bold(), + stddev_str.green(), + user_str.blue(), + system_str.blue() + ); println!(" "); println!( @@ -331,27 +291,3 @@ pub fn run_benchmark( Ok(()) } - -#[cfg(not(target_os = "windows"))] -fn output_times(mean_str: String, stddev_str: String, user_str: String, system_str: String) { - println!( - " Time ({} ± {}): {:>8} ± {:>8} [User: {}, System: {}]", - "mean".green().bold(), - "σ".green(), - mean_str.green().bold(), - stddev_str.green(), - user_str.blue(), - system_str.blue() - ); -} - -#[cfg(target_os = "windows")] -fn output_times(mean_str: String, stddev_str: String, _user_str: String, _system_str: String) { - println!( - " Time ({} ± {}): {:>8} ± {:>8}", - "mean".green().bold(), - "σ".green(), - mean_str.green().bold(), - stddev_str.green(), - ); -} diff --git a/src/hyperfine/mod.rs b/src/hyperfine/mod.rs index ddafd2f..df4f9a9 100644 --- a/src/hyperfine/mod.rs +++ b/src/hyperfine/mod.rs @@ -3,6 +3,5 @@ pub mod format; pub mod internal; pub mod outlier_detection; pub mod warnings; - -#[cfg(not(target_os = "windows"))] -pub mod cputime; +pub mod timer; +pub mod shell; diff --git a/src/hyperfine/shell.rs b/src/hyperfine/shell.rs new file mode 100644 index 0000000..0b4effb --- /dev/null +++ b/src/hyperfine/shell.rs @@ -0,0 +1,25 @@ +use std::io; +use std::process::{Command, ExitStatus, Stdio}; + +/// Run a standard shell command +#[cfg(not(target_os = "windows"))] +pub fn run_shell_command(command: &str) -> io::Result { + Command::new("sh") + .arg("-c") + .arg(command) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() +} + +/// Run a Windows shell command using cmd.exe +#[cfg(target_os = "windows")] +pub fn run_shell_command(command: &str) -> io::Result { + Command::new("cmd") + .arg(command) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() +} diff --git a/src/hyperfine/cputime.rs b/src/hyperfine/timer/cputimer.rs similarity index 72% rename from src/hyperfine/cputime.rs rename to src/hyperfine/timer/cputimer.rs index 41aa850..853602d 100644 --- a/src/hyperfine/cputime.rs +++ b/src/hyperfine/timer/cputimer.rs @@ -1,13 +1,10 @@ -#![cfg(not(target_os = "windows"))] - -use libc::{getrusage, rusage, RUSAGE_CHILDREN}; - use std::mem; use hyperfine::internal::Second; +use hyperfine::timer::Timer; #[derive(Debug, Copy, Clone)] -pub struct CPUTimes { +struct CPUTimes { /// Total amount of time spent executing in user mode user_usec: i64, @@ -16,7 +13,7 @@ pub struct CPUTimes { } #[derive(Debug, Copy, Clone)] -pub struct CPUInterval { +struct CPUInterval { /// Total amount of time spent executing in user mode pub user: Second, @@ -25,7 +22,9 @@ pub struct CPUInterval { } /// Read CPU execution times ('user' and 'system') -pub fn get_cpu_times() -> CPUTimes { +fn get_cpu_times() -> CPUTimes { + use libc::{getrusage, rusage, RUSAGE_CHILDREN}; + let result: rusage = unsafe { let mut buf = mem::zeroed(); let success = getrusage(RUSAGE_CHILDREN, &mut buf); @@ -44,13 +43,34 @@ pub fn get_cpu_times() -> CPUTimes { } /// Compute the time intervals in between two `CPUTimes` snapshots -pub fn cpu_time_interval(start: &CPUTimes, end: &CPUTimes) -> CPUInterval { +fn cpu_time_interval(start: &CPUTimes, end: &CPUTimes) -> CPUInterval { CPUInterval { user: ((end.user_usec - start.user_usec) as f64) * 1e-6, system: ((end.system_usec - start.system_usec) as f64) * 1e-6, } } +/// A timer that measures system and user time +pub struct CPUTimer { + start_cpu: CPUTimes, +} + +impl Timer for CPUTimer { + type Result = (Second, Second); + + fn start() -> Self { + CPUTimer { + start_cpu: get_cpu_times(), + } + } + + fn stop(&self) -> Self::Result { + let end_cpu = get_cpu_times(); + let cpu_interval = cpu_time_interval(&self.start_cpu, &end_cpu); + (cpu_interval.user, cpu_interval.system) + } +} + #[test] fn test_cpu_time_interval() { let t_a = CPUTimes { diff --git a/src/hyperfine/timer/mod.rs b/src/hyperfine/timer/mod.rs new file mode 100644 index 0000000..151d261 --- /dev/null +++ b/src/hyperfine/timer/mod.rs @@ -0,0 +1,10 @@ +pub mod wallclocktimer; +pub mod cputimer; + +/// Defines a general timer with start/stop functionality. +pub trait Timer { + type Result; + + fn start() -> Self; + fn stop(&self) -> Self::Result; +} diff --git a/src/hyperfine/timer/wallclocktimer.rs b/src/hyperfine/timer/wallclocktimer.rs new file mode 100644 index 0000000..61a5a0c --- /dev/null +++ b/src/hyperfine/timer/wallclocktimer.rs @@ -0,0 +1,23 @@ +use std::time::Instant; + +use hyperfine::internal::Second; +use hyperfine::timer::Timer; + +pub struct WallClockTimer { + start: Instant, +} + +impl Timer for WallClockTimer { + type Result = Second; + + fn start() -> WallClockTimer { + WallClockTimer { + start: Instant::now(), + } + } + + fn stop(&self) -> Second { + let duration = self.start.elapsed(); + duration.as_secs() as f64 + (duration.subsec_nanos() as f64) * 1e-9 + } +}