diff --git a/Cargo.lock b/Cargo.lock index 0010ab9..6199411 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -392,6 +392,8 @@ dependencies = [ "serde", "serde_json", "shell-words", + "static_assertions", + "statistical", "tempfile", "thiserror", "uom", @@ -818,8 +820,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] -name = "strip-ansi-escapes" -version = "0.2.1" +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "statistical" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index e0e33e0..4dc39ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ rand = "0.10" shell-words = "1.1" thiserror = "2.0" anyhow = "1.0" -uom = { version = "0.36.0", default-features = false, features = ["std", "si", "f64"] } +static_assertions = "1.1.0" [target.'cfg(not(windows))'.dependencies] libc = "0.2" diff --git a/src/benchmark/benchmark_result.rs b/src/benchmark/benchmark_result.rs index cead751..662a079 100644 --- a/src/benchmark/benchmark_result.rs +++ b/src/benchmark/benchmark_result.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use serde::Serialize; -use crate::{benchmark::measurement::Measurements, util::units::Second}; +use crate::benchmark::{measurement::Measurements, quantity::Second}; /// Parameter value and whether it was used in the command line template #[derive(Debug, Default, Clone, Serialize, PartialEq)] diff --git a/src/benchmark/executor.rs b/src/benchmark/executor.rs index afc8405..cffa61f 100644 --- a/src/benchmark/executor.rs +++ b/src/benchmark/executor.rs @@ -2,6 +2,9 @@ use std::os::windows::process::CommandExt; use std::process::ExitStatus; +use crate::benchmark::quantity::mean; +use crate::benchmark::quantity::Byte; +use crate::benchmark::quantity::Second; use crate::command::Command; use crate::options::{ CmdFailureAction, CommandInputPolicy, CommandOutputPolicy, Options, OutputStyleOption, Shell, @@ -161,8 +164,8 @@ impl Executor for RawExecutor<'_> { Ok(()) } - fn time_overhead(&self) -> Time { - Time::zero() + fn time_overhead(&self) -> Second { + Second::zero() } } @@ -212,11 +215,20 @@ impl Executor for ShellExecutor<'_> { )?; // Subtract shell spawning time + fn ensure_non_negative(time: Second) -> Second { + if time < Second::zero() { + Second::zero() + } else { + time + } + } + if let Some(spawning_time) = self.shell_spawning_time { result.time_wall_clock = - (result.time_wall_clock - spawning_time.time_wall_clock).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); + ensure_non_negative(result.time_wall_clock - spawning_time.time_wall_clock); + result.time_user = ensure_non_negative(result.time_user - spawning_time.time_user); + result.time_system = + ensure_non_negative(result.time_system - spawning_time.time_system); } Ok(( @@ -243,7 +255,7 @@ impl Executor for ShellExecutor<'_> { None }; - let mut times_wall_clock: Vec = vec![]; + let mut times_wall_clock: Vec = vec![]; // TODO let mut times_user: Vec = vec![]; let mut times_system: Vec = vec![]; @@ -289,7 +301,7 @@ impl Executor for ShellExecutor<'_> { time_wall_clock: mean(×_wall_clock), time_user: mean(×_user), time_system: mean(×_system), - memory_usage_byte: 0, + memory_usage_byte: Byte::new(0), }); Ok(()) @@ -312,7 +324,7 @@ impl MockExecutor { fn extract_time>(sleep_command: S) -> Time { assert!(sleep_command.as_ref().starts_with("sleep ")); - Time::new::( + Second::new( sleep_command .as_ref() .trim_start_matches("sleep ") @@ -345,9 +357,9 @@ impl Executor for MockExecutor { Ok(( TimingResult { time_wall_clock: Self::extract_time(command.get_command_line()), - time_user: 0.0, - time_system: 0.0, - memory_usage_byte: 0, + time_user: Second::zero(), + time_system: Second::zero(), + memory_usage_byte: Byte::new(0), }, status, )) @@ -359,7 +371,7 @@ impl Executor for MockExecutor { fn time_overhead(&self) -> Time { match &self.shell { - None => Time::zero(), + None => Second::zero(), Some(shell) => Self::extract_time(shell), } } @@ -367,8 +379,5 @@ impl Executor for MockExecutor { #[test] fn test_mock_executor_extract_time() { - assert_eq!( - MockExecutor::extract_time("sleep 0.1"), - Time::new::(0.1) - ); + assert_eq!(MockExecutor::extract_time("sleep 0.1"), Second::new(0.1)); } diff --git a/src/benchmark/measurement.rs b/src/benchmark/measurement.rs index 5940bf7..86114a0 100644 --- a/src/benchmark/measurement.rs +++ b/src/benchmark/measurement.rs @@ -1,12 +1,7 @@ use serde::Serialize; -use statistical::{mean, median, standard_deviation}; - -use crate::util::units::Second; -use crate::{ - outlier_detection::modified_zscores, - util::min_max::{max, min}, -}; +use crate::benchmark::quantity::{max, mean, median, min, standard_deviation, Byte, Second}; +use crate::outlier_detection::modified_zscores; /// Performance metric measurements and exit code for a single run #[derive(Debug, Clone, Serialize, PartialEq)] @@ -20,8 +15,8 @@ pub struct Measurement { /// Time spent in kernel mode pub system_time: Second, - /// Maximum memory usage of the process, in bytes - pub memory_usage_byte: u64, + /// Maximum memory usage of the process + pub memory_usage_byte: Byte, /// Exit codes of the process pub exit_code: Option, @@ -65,11 +60,10 @@ impl Measurements { pub fn stddev(&self) -> Option { let times = self.wall_clock_times(); - let t_mean = mean(×); - if times.len() > 1 { - Some(standard_deviation(×, Some(t_mean))) - } else { + if times.len() < 2 { None + } else { + Some(standard_deviation(×)) } } @@ -111,7 +105,13 @@ impl Measurements { } pub fn modified_zscores(&self) -> Vec { - modified_zscores(&self.wall_clock_times()) + modified_zscores( + &self + .wall_clock_times() + .iter() + .map(|t| t.value_in::()) // TODO + .collect::>(), + ) } pub fn peak_memory_usage_mean(&self) -> Information { diff --git a/src/benchmark/mod.rs b/src/benchmark/mod.rs index c41771a..7ab08b9 100644 --- a/src/benchmark/mod.rs +++ b/src/benchmark/mod.rs @@ -12,6 +12,7 @@ use std::time::Instant; use crate::benchmark::benchmark_result::Parameter; use crate::benchmark::executor::BenchmarkIteration; use crate::benchmark::measurement::{Measurement, Measurements}; +use crate::benchmark::quantity::{MilliSecond, Second}; use crate::command::Command; use crate::options::{ CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption, @@ -26,7 +27,6 @@ use crate::output::warnings::{OutlierWarningOptions, Warnings}; use crate::parameter::ParameterNameAndValue; use crate::quantity::{byte, const_time_from_seconds, ratio, second, FormatQuantity, Time, Zero}; use crate::util::exit_code::extract_exit_code; -use crate::util::units::Second; use benchmark_result::BenchmarkResult; use measurement::{Measurement, Measurements}; @@ -36,7 +36,7 @@ use colored::*; use self::executor::Executor; /// Threshold for warning about fast execution time -pub const MIN_EXECUTION_TIME: Time = const_time_from_seconds(5e-3); +pub const MIN_EXECUTION_TIME: MilliSecond = MilliSecond::new(5.); pub struct Benchmark<'a> { number: usize, @@ -265,7 +265,7 @@ impl<'a> Benchmark<'a> { }; let preparation_result = run_preparation_command()?; - let preparation_overhead = preparation_result.map_or(0.0, |res| { + let preparation_overhead = preparation_result.map_or(Second::zero(), |res| { res.time_wall_clock + self.executor.time_overhead() }); @@ -294,7 +294,7 @@ impl<'a> Benchmark<'a> { } let conclusion_result = run_conclusion_command()?; - let conclusion_overhead = conclusion_result.map_or(0.0, |res| { + let conclusion_overhead = conclusion_result.map_or(Second::zero(), |res| { res.time_wall_clock + self.executor.time_overhead() }); @@ -434,7 +434,7 @@ impl<'a> Benchmark<'a> { && measurements .wall_clock_times() .iter() - .any(|&t| t < MIN_EXECUTION_TIME) + .any(|&t| t < MIN_EXECUTION_TIME.convert_to::()) { warnings.push(Warnings::FastExecutionTime); } diff --git a/src/benchmark/quantity.rs b/src/benchmark/quantity.rs index 2a8ae10..fc9780f 100644 --- a/src/benchmark/quantity.rs +++ b/src/benchmark/quantity.rs @@ -1,103 +1,205 @@ -use std::fmt::{self, Display, Formatter}; +use std::{ + fmt::{self, Display, Formatter}, + ops::{Add, Div, Sub}, +}; use serde::Serialize; -#[derive(Debug, Clone, Serialize, PartialEq)] -enum MetricPrefix { - Nano, - Micro, - Milli, - None, +pub trait IsQuantity { + type Value; + + const IS_TIME: bool; + const METRIC_PREFIX_TIME: i32; + const IS_INFORMATION: bool; + const BINARY_PREFIX_INFORMATION: i32; + + fn unsafe_from(value: Self::Value) -> Self; } -impl Display for MetricPrefix { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - MetricPrefix::Nano => write!(f, "n"), - MetricPrefix::Micro => write!(f, "µ"), - MetricPrefix::Milli => write!(f, "m"), - MetricPrefix::None => Ok(()), +#[derive(Debug, Clone, Copy, Default, Serialize, PartialEq, Eq, PartialOrd)] // TODO: check partialord +pub struct Quantity< + Value, + const IS_TIME: bool, + const METRIC_PREFIX_TIME: i32, + const IS_INFORMATION: bool, + const BINARY_PREFIX_INFORMATION: i32, +>(Value); + +impl< + Value, + const IS_TIME: bool, + const METRIC_PREFIX_TIME: i32, + const IS_INFORMATION: bool, + const BINARY_PREFIX_INFORMATION: i32, + > IsQuantity + for Quantity +{ + type Value = Value; + const IS_TIME: bool = IS_TIME; + const METRIC_PREFIX_TIME: i32 = METRIC_PREFIX_TIME; + const IS_INFORMATION: bool = IS_INFORMATION; + const BINARY_PREFIX_INFORMATION: i32 = BINARY_PREFIX_INFORMATION; + + fn unsafe_from(value: Self::Value) -> Self { + Self(value) + } +} + +type Time = Quantity; +type Information = Quantity; + +pub type Second = Time<0>; +pub type MilliSecond = Time<-3>; +pub type MicroSecond = Time<-6>; + +pub type Byte = Information<0>; +pub type KibiByte = Information<10>; +pub type MebiByte = Information<20>; +pub type GibiByte = Information<30>; +pub type TebiByte = Information<40>; + +impl< + Value: Copy + Default, + const IS_TIME: bool, + const METRIC_PREFIX_TIME: i32, + const IS_INFORMATION: bool, + const BINARY_PREFIX_INFORMATION: i32, + > Quantity +{ + pub const fn new(value: Value) -> Self { + Self(value) + } + + pub fn zero() -> Self { + Self::new(Value::default()) + } + + pub fn value_in(&self) -> Value { + const { + assert!(IS_TIME == TargetUnit::IS_TIME); + assert!(METRIC_PREFIX_TIME == TargetUnit::METRIC_PREFIX_TIME); + assert!(IS_INFORMATION == TargetUnit::IS_INFORMATION); + assert!(BINARY_PREFIX_INFORMATION == TargetUnit::BINARY_PREFIX_INFORMATION); } + + self.0 } } -#[derive(Debug, Clone, Serialize, PartialEq)] -enum BinaryPrefix { - None, - Kibi, - Mebi, - Gibi, - Tebi, +impl Time { + pub fn convert_to>(&self) -> TargetUnit { + let factor = 10_f64.powi(METRIC_PREFIX_TIME - TargetUnit::METRIC_PREFIX_TIME); + + TargetUnit::unsafe_from(self.0 * factor) + } } -impl Display for BinaryPrefix { +impl Add> for Time { + type Output = Time; + + fn add(self, rhs: Time) -> Self::Output { + Time::new(self.0 + rhs.0) + } +} + +impl Sub> for Time { + type Output = Time; + + fn sub(self, rhs: Time) -> Self::Output { + Time::new(self.0 - rhs.0) + } +} + +impl Div> for Time { + type Output = f64; + + fn div(self, rhs: Time) -> Self::Output { + self.0 / rhs.0 + } +} + +impl Display for Time { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - BinaryPrefix::None => Ok(()), - BinaryPrefix::Kibi => write!(f, "Ki"), - BinaryPrefix::Mebi => write!(f, "Mi"), - BinaryPrefix::Gibi => write!(f, "Gi"), - BinaryPrefix::Tebi => write!(f, "Ti"), + let prefix = match METRIC_PREFIX_TIME { + -6 => "µ", + -3 => "m", + 0 => "s", + _ => unreachable!(), + }; + write!(f, "{} {}s", self.0, prefix) + } +} + +impl Display for Information { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let prefix = match BINARY_PREFIX_INFORMATION { + 0 => "", + 10 => "Ki", + 20 => "Mi", + 30 => "Gi", + 40 => "Ti", + _ => unreachable!(), + }; + write!(f, "{} {}B", self.0, prefix) + } +} + +macro_rules! quantity_fn { + ($name:ident, $unwrapped_values:ident, $body:expr) => { + pub fn $name< + const IS_TIME: bool, + const METRIC_PREFIX_TIME: i32, + const IS_INFORMATION: bool, + const BINARY_PREFIX_INFORMATION: i32, + >( + values: &[Quantity< + f64, + IS_TIME, + METRIC_PREFIX_TIME, + IS_INFORMATION, + BINARY_PREFIX_INFORMATION, + >], + ) -> Quantity { + let $unwrapped_values: Vec = values.iter().map(|v| v.0).collect(); + let result_value = $body; + Quantity::new(result_value) } - } + }; } -#[derive(Debug, Clone, Serialize, PartialEq)] -pub enum Unit { - Second(MetricPrefix), - Byte(BinaryPrefix), -} - -pub const SECOND: Unit = Unit::Second(MetricPrefix::None); -pub const MILLISECOND: Unit = Unit::Second(MetricPrefix::Milli); -pub const MICROSECOND: Unit = Unit::Second(MetricPrefix::Micro); -pub const BYTE: Unit = Unit::Byte(BinaryPrefix::None); - -impl Display for Unit { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Unit::Second(prefix) => write!(f, "{prefix}s",), - Unit::Byte(prefix) => write!(f, "{prefix}B"), - } - } -} - -#[derive(Debug, Clone, Serialize, PartialEq)] -pub struct Quantity { - value: Value, - unit: Unit, -} - -impl Quantity { - pub fn new(value: Value, unit: Unit) -> Self { - Self { value, unit } - } -} - -impl Display for Quantity { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{} {}", self.value, self.unit) - } -} - -pub fn mean(values: &[Quantity]) -> Quantity { - statistical::mean(values).unwrap() -} - -pub type Time = Quantity; -pub type Memory = Quantity; +quantity_fn!(mean, values, statistical::mean(&values)); +quantity_fn!(median, values, statistical::median(&values)); +quantity_fn!(min, values, crate::util::min_max::min(&values)); +quantity_fn!(max, values, crate::util::min_max::max(&values)); +quantity_fn!(standard_deviation, values, { + let mean_value = statistical::mean(&values); + statistical::standard_deviation(&values, Some(mean_value)) +}); #[test] -fn basic() { - let time = Time { - value: 123.4, - unit: Unit::Second(MetricPrefix::Milli), - }; +fn test_quantity() { + let time = MilliSecond::new(123.4); + assert_eq!(time.value_in::(), 123.4); + + let time_s = time.convert_to::(); + approx::assert_relative_eq!(time_s.value_in::(), 0.1234); + + let time_us = time.convert_to::(); + approx::assert_relative_eq!(time_us.value_in::(), 123400.0); +} + +#[test] +fn test_format() { + let time = MilliSecond::new(123.4); assert_eq!(time.to_string(), "123.4 ms"); - let peak_memory_usage = Memory { - value: 8, - unit: Unit::Byte(BinaryPrefix::Kibi), - }; + let peak_memory_usage = KibiByte::new(8); assert_eq!(peak_memory_usage.to_string(), "8 KiB"); } + +#[test] +fn test_mean() { + let values = vec![MilliSecond::new(123.4), MilliSecond::new(234.5)]; + let result = mean(&values); + assert_eq!(result.to_string(), "178.95 ms"); +} diff --git a/src/benchmark/relative_speed.rs b/src/benchmark/relative_speed.rs index e5af3a2..c427444 100644 --- a/src/benchmark/relative_speed.rs +++ b/src/benchmark/relative_speed.rs @@ -1,10 +1,7 @@ use std::cmp::Ordering; use super::benchmark_result::BenchmarkResult; -use crate::{ - options::SortOrder, - quantity::{self, Ratio, Time, Zero}, -}; +use crate::{benchmark::quantity::Second, options::SortOrder}; #[derive(Debug)] pub struct BenchmarkResultWithRelativeSpeed<'a> { @@ -55,8 +52,8 @@ fn compute_relative_speeds<'a>( let is_reference = std::ptr::eq(result, reference); let relative_ordering = compare_mean_time(result, reference); - if result.mean_wall_clock_time() == Time::zero() - || reference.mean_wall_clock_time() == Time::zero() + if result.mean_wall_clock_time() == Second::zero() + || reference.mean_wall_clock_time() == Second::zero() { return BenchmarkResultWithRelativeSpeed { result, @@ -117,7 +114,8 @@ pub fn compute_with_check_from_reference<'a>( reference: &'a BenchmarkResult, sort_order: SortOrder, ) -> Option>> { - if fastest_of(results).mean_wall_clock_time() == 0.0 || reference.mean_wall_clock_time() == 0.0 + if fastest_of(results).mean_wall_clock_time() == Second::zero() + || reference.mean_wall_clock_time() == Second::zero() { return None; } @@ -131,7 +129,7 @@ pub fn compute_with_check( ) -> Option>> { let fastest = fastest_of(results); - if fastest.mean_wall_clock_time() == 0.0 { + if fastest.mean_wall_clock_time() == Second::zero() { return None; } @@ -152,16 +150,19 @@ pub fn compute<'a>( fn create_result(name: &str, mean: f64) -> BenchmarkResult { use std::collections::BTreeMap; - use crate::benchmark::measurement::{Measurement, Measurements}; + use crate::benchmark::{ + measurement::{Measurement, Measurements}, + quantity::Byte, + }; BenchmarkResult { command: name.into(), measurements: Measurements { measurements: vec![Measurement { - wall_clock_time: mean, - user_time: mean, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(mean), + user_time: Second::new(mean), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }], }, diff --git a/src/export/csv.rs b/src/export/csv.rs index 49f3787..014b9fe 100644 --- a/src/export/csv.rs +++ b/src/export/csv.rs @@ -4,6 +4,7 @@ use csv::WriterBuilder; use super::Exporter; use crate::benchmark::benchmark_result::BenchmarkResult; +use crate::benchmark::quantity::Second; use crate::options::SortOrder; use crate::quantity::{FormatQuantity, TimeUnit}; @@ -45,17 +46,14 @@ impl Exporter for CsvExporter { let mut fields = vec![Cow::Borrowed(res.command.as_bytes())]; for f in &[ res.mean_wall_clock_time(), - res.measurements.stddev().unwrap_or(0.0), + res.measurements.stddev().unwrap_or_default(), res.measurements.median(), res.measurements.user_mean(), res.measurements.system_mean(), res.measurements.min(), res.measurements.max(), ] { - fields.push(Cow::Owned( - f.format_with_precision(CSV_UNIT, CSV_PRECISION) - .into_bytes(), - )) + fields.push(Cow::Owned(f.value_in::().to_string().into_bytes())) } for v in res.parameters.values() { fields.push(Cow::Borrowed(v.value.as_bytes())) @@ -67,20 +65,13 @@ impl Exporter for CsvExporter { } } -#[cfg(test)] -use crate::benchmark::benchmark_result::Parameter; - -#[cfg(test)] -use crate::benchmark::measurement::{Measurement, Measurements}; - #[test] fn test_csv() { use crate::benchmark::benchmark_result::Parameter; use crate::benchmark::measurement::{Measurement, Measurements}; - use crate::quantity::{byte, second, Information, Time, Zero}; + use crate::benchmark::quantity::{Byte, Second}; use std::collections::BTreeMap; - use std::process::ExitStatus; let exporter = CsvExporter::default(); @@ -89,24 +80,24 @@ fn test_csv() { command: String::from("command_a"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 7.0, - user_time: 7.0, - system_time: 0.0, - memory_usage_byte: 1024, + wall_clock_time: Second::new(7.0), + user_time: Second::new(7.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 8.0, - user_time: 8.0, - system_time: 0.0, - memory_usage_byte: 1024, + wall_clock_time: Second::new(8.0), + user_time: Second::new(8.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 12.0, - user_time: 12.0, - system_time: 0.0, - memory_usage_byte: 1024, + wall_clock_time: Second::new(12.0), + user_time: Second::new(12.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), @@ -133,24 +124,24 @@ fn test_csv() { command: String::from("command_b"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 17.0, - user_time: 17.0, - system_time: 0.0, - memory_usage_byte: 1024, + wall_clock_time: Second::new(17.0), + user_time: Second::new(17.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 18.0, - user_time: 18.0, - system_time: 0.0, - memory_usage_byte: 1024, + wall_clock_time: Second::new(18.0), + user_time: Second::new(18.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 19.0, - user_time: 19.0, - system_time: 0.0, - memory_usage_byte: 1024, + wall_clock_time: Second::new(19.0), + user_time: Second::new(19.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), diff --git a/src/export/tests.rs b/src/export/tests.rs index a5e4c23..5b873d7 100644 --- a/src/export/tests.rs +++ b/src/export/tests.rs @@ -1,6 +1,7 @@ use super::Exporter; use crate::benchmark::benchmark_result::BenchmarkResult; use crate::benchmark::measurement::{Measurement, Measurements}; +use crate::benchmark::quantity::{Byte, Second}; use crate::export::asciidoc::AsciidocExporter; use crate::export::orgmode::OrgmodeExporter; use crate::quantity::{byte, second, Information, Time, TimeUnit, Zero}; @@ -30,24 +31,24 @@ fn test_markup_export_auto_ms() { command: String::from("sleep 0.1"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 0.09, - user_time: 0.09, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.09), + user_time: Second::new(0.09), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 0.10, - user_time: 0.10, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.10), + user_time: Second::new(0.10), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 0.14, - user_time: 0.14, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.14), + user_time: Second::new(0.14), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), @@ -57,24 +58,24 @@ fn test_markup_export_auto_ms() { command: String::from("sleep 2"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 2.0, - user_time: 2.0, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.0), + user_time: Second::new(2.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 3.0, - user_time: 3.0, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(3.0), + user_time: Second::new(3.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 4.0, - user_time: 4.0, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(4.0), + user_time: Second::new(4.0), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), @@ -129,24 +130,24 @@ fn test_markup_export_auto_s() { command: String::from("sleep 2"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 2.1, - user_time: 2.1, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.1), + user_time: Second::new(2.1), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 2.2, - user_time: 2.2, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.2), + user_time: Second::new(2.2), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 2.3, - user_time: 2.3, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.3), + user_time: Second::new(2.3), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), @@ -156,24 +157,24 @@ fn test_markup_export_auto_s() { command: String::from("sleep 0.1"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 0.1, - user_time: 0.1, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.1), + user_time: Second::new(0.1), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 0.2, - user_time: 0.2, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.2), + user_time: Second::new(0.2), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 0.3, - user_time: 0.3, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.3), + user_time: Second::new(0.3), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), @@ -228,24 +229,24 @@ fn test_markup_export_manual_ms() { command: String::from("sleep 2"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 2.1, - user_time: 2.1, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.1), + user_time: Second::new(2.1), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 2.2, - user_time: 2.2, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.2), + user_time: Second::new(2.2), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 2.3, - user_time: 2.3, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.3), + user_time: Second::new(2.3), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), @@ -255,24 +256,24 @@ fn test_markup_export_manual_ms() { command: String::from("sleep 0.1"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 0.1, - user_time: 0.1, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.1), + user_time: Second::new(0.1), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 0.2, - user_time: 0.2, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.2), + user_time: Second::new(0.2), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 0.3, - user_time: 0.3, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.3), + user_time: Second::new(0.3), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), @@ -326,24 +327,24 @@ fn test_markup_export_manual_s() { command: String::from("sleep 2"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 2.01, - user_time: 2.01, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.01), + user_time: Second::new(2.01), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 2.02, - user_time: 2.02, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.02), + user_time: Second::new(2.02), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 2.03, - user_time: 2.03, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(2.03), + user_time: Second::new(2.03), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), @@ -353,24 +354,24 @@ fn test_markup_export_manual_s() { command: String::from("sleep 0.1"), measurements: Measurements::new(vec![ Measurement { - wall_clock_time: 0.11, - user_time: 0.11, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.11), + user_time: Second::new(0.11), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 0.12, - user_time: 0.12, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.12), + user_time: Second::new(0.12), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, Measurement { - wall_clock_time: 0.13, - user_time: 0.13, - system_time: 0., - memory_usage_byte: 1024, + wall_clock_time: Second::new(0.13), + user_time: Second::new(0.13), + system_time: Second::zero(), + memory_usage_byte: Byte::new(1024), exit_code: Some(0), }, ]), diff --git a/src/options.rs b/src/options.rs index f5a3285..a387c15 100644 --- a/src/options.rs +++ b/src/options.rs @@ -7,9 +7,10 @@ use std::{cmp, env, fmt, io}; use anyhow::{bail, ensure}; use clap::ArgMatches; +use crate::benchmark::quantity::Second; use crate::command::Commands; use crate::error::OptionsError; -use crate::quantity::{second, Time, TimeUnit}; +use crate::util::units::Unit; use anyhow::Result; @@ -256,7 +257,7 @@ impl Default for Options { Options { run_bounds: RunBounds::default(), warmup_count: 0, - min_benchmarking_time: Time::new::(3.0), + min_benchmarking_time: Second::new(3.0), command_failure_action: CmdFailureAction::RaiseError, reference_command: None, reference_name: None, @@ -442,7 +443,7 @@ impl Options { }; if let Some(time) = matches.get_one::("min-benchmarking-time") { - options.min_benchmarking_time = Time::new::( + options.min_benchmarking_time = Second::new( time.parse::() .map_err(|e| OptionsError::FloatParsingError("min-benchmarking-time", e))?, ); diff --git a/src/output/warnings.rs b/src/output/warnings.rs index 98ebdb7..c53ef6c 100644 --- a/src/output/warnings.rs +++ b/src/output/warnings.rs @@ -1,7 +1,8 @@ use std::fmt; +use crate::benchmark::quantity::Second; use crate::benchmark::MIN_EXECUTION_TIME; -use crate::quantity::{FormatQuantity, Time, TimeUnit}; +use crate::output::format::format_duration; pub struct OutlierWarningOptions { pub warmup_in_use: bool, @@ -21,11 +22,10 @@ impl fmt::Display for Warnings { match *self { Warnings::FastExecutionTime => write!( f, - "Command took less than {} ms to complete. Note that the results might be \ + "Command took less than {MIN_EXECUTION_TIME} to complete. Note that the results might be \ inaccurate because hyperfine can not calibrate the shell startup time much \ more precise than this limit. You can try to use the `-N`/`--shell=none` \ option to disable the shell completely.", - MIN_EXECUTION_TIME.format_with_precision(TimeUnit::MilliSecond, 0) ), Warnings::NonZeroExitCode => write!(f, "Ignoring non-zero exit code."), Warnings::SlowInitialRun(time_first_run, ref options) => write!( diff --git a/src/timer/mod.rs b/src/timer/mod.rs index 15dbc8b..2a7f35e 100644 --- a/src/timer/mod.rs +++ b/src/timer/mod.rs @@ -16,7 +16,7 @@ use std::os::fd::AsFd; #[cfg(target_os = "windows")] use windows_sys::Win32::System::Threading::CREATE_SUSPENDED; -use crate::benchmark::measurement::Measurement; +use crate::benchmark::quantity::{Byte, Second}; use wall_clock_timer::WallClockTimer; use std::io::Read; @@ -30,7 +30,7 @@ pub struct TimerResult { pub time_wall_clock: Second, pub time_user: Second, pub time_system: Second, - pub memory_usage_byte: u64, + pub memory_usage_byte: Byte, /// The exit status of the process pub status: ExitStatus, } diff --git a/src/timer/unix_timer.rs b/src/timer/unix_timer.rs index b76438c..3e57323 100644 --- a/src/timer/unix_timer.rs +++ b/src/timer/unix_timer.rs @@ -8,22 +8,22 @@ use std::process::{Child, ExitStatus}; use anyhow::Result; -use crate::util::units::Second; +use crate::benchmark::quantity::{Byte, Second}; #[derive(Debug, Copy, Clone)] struct ResourceUsage { /// Total amount of time spent executing in user mode - pub user_usec: Second, + pub user_usec: f64, /// Total amount of time spent executing in kernel mode - pub system_usec: Second, + pub system_usec: f64, /// Maximum amount of memory used by the process, in bytes pub memory_usage_byte: i64, } #[allow(clippy::useless_conversion)] -fn timeval_to_seconds(tv: libc::timeval) -> Second { +fn timeval_to_seconds(tv: libc::timeval) -> f64 { const MICROSEC_PER_SEC: i64 = 1000 * 1000; (i64::from(tv.tv_sec) * MICROSEC_PER_SEC + i64::from(tv.tv_usec)) as f64 * 1e-6 } @@ -68,13 +68,13 @@ impl CPUTimer { Self {} } - pub fn stop(&self, child: Child) -> Result<(ExitStatus, Second, Second, u64)> { + pub fn stop(&self, child: Child) -> Result<(ExitStatus, Second, Second, Byte)> { let (status, usage) = wait4(child)?; Ok(( status, - usage.user_usec, - usage.system_usec, - u64::try_from(usage.memory_usage_byte).unwrap_or(0), + Second::new(usage.user_usec), + Second::new(usage.system_usec), + Byte::new(u64::try_from(usage.memory_usage_byte).unwrap_or(0)), )) } } diff --git a/src/timer/wall_clock_timer.rs b/src/timer/wall_clock_timer.rs index 3a7233a..2de3241 100644 --- a/src/timer/wall_clock_timer.rs +++ b/src/timer/wall_clock_timer.rs @@ -1,6 +1,6 @@ use std::time::Instant; -use crate::quantity::{nanosecond, second, Time}; +use crate::benchmark::quantity::Second; pub struct WallClockTimer { start: Instant, @@ -15,8 +15,7 @@ impl WallClockTimer { pub fn stop(&self) -> Time { let duration = self.start.elapsed(); - - Time::new::(duration.as_secs() as f64) - + Time::new::(duration.subsec_nanos() as f64) + // TODO + Second::new(duration.as_secs() as f64 + f64::from(duration.subsec_nanos()) * 1e-9) } }