diff --git a/src/hyperfine/app.rs b/src/hyperfine/app.rs index c486322..5863553 100644 --- a/src/hyperfine/app.rs +++ b/src/hyperfine/app.rs @@ -86,13 +86,13 @@ fn build_app() -> App<'static, 'static> { Arg::with_name("parameter-scan") .long("parameter-scan") .short("P") + .takes_value(true) + .allow_hyphen_values(true) + .value_names(&["VAR", "MIN", "MAX"]) .help( "Perform benchmark runs for each value in the range MIN..MAX. Replaces the \ string '{VAR}' in each command by the current parameter value.", - ) - .takes_value(true) - .allow_hyphen_values(true) - .value_names(&["VAR", "MIN", "MAX"]), + ), ) .arg( Arg::with_name("style") @@ -124,6 +124,16 @@ fn build_app() -> App<'static, 'static> { .short("i") .help("Ignore non-zero exit codes."), ) + .arg( + Arg::with_name("time-unit") + .long("time-unit") + .short("u") + .takes_value(true) + .value_name("millisecond|second") + .possible_values(&["millisecond", "second"]) + .help( "Set the time unit used for CLI output and Markdown export.", + ), + ) .arg( Arg::with_name("export-csv") .long("export-csv") diff --git a/src/hyperfine/benchmark.rs b/src/hyperfine/benchmark.rs index 5d7c1fd..6e89c53 100644 --- a/src/hyperfine/benchmark.rs +++ b/src/hyperfine/benchmark.rs @@ -254,7 +254,7 @@ pub fn run_benchmark( run_preparation_command(&options.shell, &prepare_cmd, options.show_output)?; let msg = { - let mean = format_duration(mean(×_real), None); + let mean = format_duration(mean(×_real), options.time_unit); format!("Current estimate: {}", mean.to_string().green()) }; progress_bar.set_message(&msg); @@ -287,7 +287,7 @@ pub fn run_benchmark( let system_mean = mean(×_system); // Formatting and console output - let (mean_str, unit_mean) = format_duration_unit(t_mean, None); + let (mean_str, unit_mean) = format_duration_unit(t_mean, options.time_unit); let stddev_str = format_duration(t_stddev, Some(unit_mean)); let min_str = format_duration(t_min, Some(unit_mean)); let max_str = format_duration(t_max, Some(unit_mean)); diff --git a/src/hyperfine/export/csv.rs b/src/hyperfine/export/csv.rs index e58b043..7be8b46 100644 --- a/src/hyperfine/export/csv.rs +++ b/src/hyperfine/export/csv.rs @@ -1,6 +1,6 @@ use super::Exporter; -use hyperfine::types::BenchmarkResult; +use hyperfine::types::{BenchmarkResult, Unit}; use std::io::{Error, ErrorKind, Result}; @@ -10,10 +10,10 @@ use csv::WriterBuilder; pub struct CsvExporter {} impl Exporter for CsvExporter { - fn serialize(&self, results: &Vec) -> Result> { + fn serialize(&self, results: &Vec, _unit: Option) -> Result> { let mut writer = WriterBuilder::new().from_writer(vec![]); for res in results { - // The list of times can not be exported to the CSV file - remove it: + // The list of times cannot be exported to the CSV file - remove it: let mut result = res.clone(); result.times = None; diff --git a/src/hyperfine/export/json.rs b/src/hyperfine/export/json.rs index 0a2cb66..4e6e311 100644 --- a/src/hyperfine/export/json.rs +++ b/src/hyperfine/export/json.rs @@ -1,5 +1,5 @@ use super::Exporter; -use hyperfine::types::BenchmarkResult; +use hyperfine::types::{BenchmarkResult, Unit}; use std::io::{Error, ErrorKind, Result}; @@ -14,7 +14,7 @@ struct HyperfineSummary<'a> { pub struct JsonExporter {} impl Exporter for JsonExporter { - fn serialize(&self, results: &Vec) -> Result> { + fn serialize(&self, results: &Vec, _unit: Option) -> Result> { let mut output = to_vec_pretty(&HyperfineSummary { results }); for content in output.iter_mut() { content.push(b'\n'); diff --git a/src/hyperfine/export/markdown.rs b/src/hyperfine/export/markdown.rs index 948b424..00b22c0 100644 --- a/src/hyperfine/export/markdown.rs +++ b/src/hyperfine/export/markdown.rs @@ -1,7 +1,7 @@ use super::Exporter; -use hyperfine::format::{Unit, format_duration_value}; -use hyperfine::types::BenchmarkResult; +use hyperfine::format::format_duration_value; +use hyperfine::types::{BenchmarkResult, Unit}; use std::io::Result; @@ -9,8 +9,11 @@ use std::io::Result; pub struct MarkdownExporter {} impl Exporter for MarkdownExporter { - fn serialize(&self, results: &Vec) -> Result> { - let unit = if let Some(first_result) = results.first() { + fn serialize(&self, results: &Vec, unit: Option) -> Result> { + let unit = if unit.is_some() { + // Use the given unit for all entries. + unit.unwrap() + } else if let Some(first_result) = results.first() { // Use the first BenchmarkResult entry to determine the unit for all entries. format_duration_value(first_result.mean, None).1 } else { @@ -59,7 +62,7 @@ fn add_table_row(dest: &mut Vec, entry: &BenchmarkResult, unit: Unit) { /// in the output. /// /// This also demonstrates that the first entry's units (ms) are used to set -/// the units for all entries. +/// the units for all entries when the time unit is not given. #[test] fn test_markdown_format_ms() { let exporter = MarkdownExporter::default(); @@ -88,7 +91,7 @@ fn test_markdown_format_ms() { vec![2.0, 2.0, 2.0], // times )); - let formatted = String::from_utf8(exporter.serialize(&timing_results).unwrap()).unwrap(); + let formatted = String::from_utf8(exporter.serialize(&timing_results, None).unwrap()).unwrap(); let formatted_expected = format!( "{}\ @@ -100,7 +103,7 @@ fn test_markdown_format_ms() { } /// This (again) demonstrates that the first entry's units (s) are used to set -/// the units for all entries. +/// the units for all entries when the time unit is not given. #[test] fn test_markdown_format_s() { let exporter = MarkdownExporter::default(); @@ -129,7 +132,7 @@ fn test_markdown_format_s() { vec![0.1, 0.1, 0.1], // times )); - let formatted = String::from_utf8(exporter.serialize(&timing_results).unwrap()).unwrap(); + let formatted = String::from_utf8(exporter.serialize(&timing_results, None).unwrap()).unwrap(); let formatted_expected = format!( "{}\ @@ -139,3 +142,84 @@ fn test_markdown_format_s() { assert_eq!(formatted_expected, formatted); } + +/// The given time unit (s) is used to set the units for all entries. +#[test] +fn test_markdown_format_time_unit_s() { + let exporter = MarkdownExporter::default(); + + let mut timing_results = vec![]; + + timing_results.push(BenchmarkResult::new( + String::from("sleep 0.1"), + 0.1057, // mean + 0.0016, // std dev + 0.0009, // user_mean + 0.0011, // system_mean + 0.1023, // min + 0.1080, // max + vec![0.1, 0.1, 0.1], // times + )); + + timing_results.push(BenchmarkResult::new( + String::from("sleep 2"), + 2.0050, // mean + 0.0020, // std dev + 0.0009, // user_mean + 0.0012, // system_mean + 2.0020, // min + 2.0080, // max + vec![2.0, 2.0, 2.0], // times + )); + + let formatted = String::from_utf8(exporter.serialize(&timing_results, Some(Unit::Second)).unwrap()).unwrap(); + + let formatted_expected = format!( +"{}\ +| `sleep 0.1` | 0.106 ± 0.002 | 0.102…0.108 | +| `sleep 2` | 2.005 ± 0.002 | 2.002…2.008 | +", table_header("s".to_string())); + + assert_eq!(formatted_expected, formatted); +} + +/// This (again) demonstrates that the given time unit (ms) is used to set +/// the units for all entries. +#[test] +fn test_markdown_format_time_unit_ms() { + let exporter = MarkdownExporter::default(); + + let mut timing_results = vec![]; + + timing_results.push(BenchmarkResult::new( + String::from("sleep 2"), + 2.0050, // mean + 0.0020, // std dev + 0.0009, // user_mean + 0.0012, // system_mean + 2.0020, // min + 2.0080, // max + vec![2.0, 2.0, 2.0], // times + )); + + timing_results.push(BenchmarkResult::new( + String::from("sleep 0.1"), + 0.1057, // mean + 0.0016, // std dev + 0.0009, // user_mean + 0.0011, // system_mean + 0.1023, // min + 0.1080, // max + vec![0.1, 0.1, 0.1], // times + )); + + let formatted = String::from_utf8(exporter.serialize(&timing_results, Some(Unit::MilliSecond)).unwrap()).unwrap(); + + let formatted_expected = format!( +"{}\ +| `sleep 2` | 2005.0 ± 2.0 | 2002.0…2008.0 | +| `sleep 0.1` | 105.7 ± 1.6 | 102.3…108.0 | +", table_header("ms".to_string())); + + assert_eq!(formatted_expected, formatted); +} diff --git a/src/hyperfine/export/mod.rs b/src/hyperfine/export/mod.rs index 294188d..852c4fd 100644 --- a/src/hyperfine/export/mod.rs +++ b/src/hyperfine/export/mod.rs @@ -9,7 +9,7 @@ use self::markdown::MarkdownExporter; use std::fs::File; use std::io::{Result, Write}; -use hyperfine::types::BenchmarkResult; +use hyperfine::types::{BenchmarkResult, Unit}; /// The desired form of exporter to use for a given file. #[derive(Clone)] @@ -27,7 +27,7 @@ pub enum ExportType { /// Interface for different exporters. trait Exporter { /// Export the given entries in the serialized form. - fn serialize(&self, results: &Vec) -> Result>; + fn serialize(&self, results: &Vec, unit: Option) -> Result>; } struct ExporterWithFilename { @@ -62,9 +62,9 @@ impl ExportManager { } /// Write the given results to all Exporters contained within this manager - pub fn write_results(&self, results: Vec) -> Result<()> { + pub fn write_results(&self, results: Vec, unit: Option) -> Result<()> { for e in &self.exporters { - let file_content = e.exporter.serialize(&results)?; + let file_content = e.exporter.serialize(&results, unit)?; write_to_file(&e.filename, &file_content)?; } Ok(()) diff --git a/src/hyperfine/format.rs b/src/hyperfine/format.rs index 5aba921..7f7471c 100644 --- a/src/hyperfine/format.rs +++ b/src/hyperfine/format.rs @@ -1,28 +1,4 @@ -use hyperfine::types::Second; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Unit { - Second, - MilliSecond, -} - -impl Unit { - /// The abbreviation of the Unit. - pub fn short_name(&self) -> String { - match *self { - Unit::Second => String::from("s"), - Unit::MilliSecond => String::from("ms"), - } - } - - /// Returns the Second value formatted for the Unit. - pub fn format(&self, value: Second) -> String { - match *self { - Unit::Second => format!("{:.3}", value), - Unit::MilliSecond => format!("{:.1}", value * 1e3), - } - } -} +use hyperfine::types::{Second, Unit}; /// Format the given duration as a string. The output-unit can be enforced by setting `unit` to /// `Some(target_unit)`. If `unit` is `None`, it will be determined automatically. @@ -48,20 +24,6 @@ pub fn format_duration_value(duration: Second, unit: Option) -> (String, U } } -#[test] -fn test_unit_short_name() { - assert_eq!("s", Unit::Second.short_name()); - assert_eq!("ms", Unit::MilliSecond.short_name()); -} - -// Note - the values are rounded when formatted. -#[test] -fn test_unit_format() { - let value: Second = 123.456789; - assert_eq!("123.457", Unit::Second.format(value)); - assert_eq!("123456.8", Unit::MilliSecond.format(value)); -} - #[test] fn test_format_duration_unit_basic() { let (out_str, out_unit) = format_duration_unit(1.3, None); diff --git a/src/hyperfine/types.rs b/src/hyperfine/types.rs index 1754ea9..dd2547d 100644 --- a/src/hyperfine/types.rs +++ b/src/hyperfine/types.rs @@ -10,6 +10,31 @@ pub const DEFAULT_SHELL: &str = "cmd.exe"; /// Type alias for unit of time pub type Second = f64; +/// Supported time units +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Unit { + Second, + MilliSecond, +} + +impl Unit { + /// The abbreviation of the Unit. + pub fn short_name(&self) -> String { + match *self { + Unit::Second => String::from("s"), + Unit::MilliSecond => String::from("ms"), + } + } + + /// Returns the Second value formatted for the Unit. + pub fn format(&self, value: Second) -> String { + match *self { + Unit::Second => format!("{:.3}", value), + Unit::MilliSecond => format!("{:.1}", value * 1e3), + } + } +} + /// A command that should be benchmarked. #[derive(Debug, Clone)] pub struct Command<'a> { @@ -122,6 +147,9 @@ pub struct HyperfineOptions { /// Forward benchmark's stdout to hyperfine's stdout pub show_output: bool, + + /// Which time unit to use for CLI & Markdown output + pub time_unit: Option, } impl Default for HyperfineOptions { @@ -135,6 +163,7 @@ impl Default for HyperfineOptions { output_style: OutputStyleOption::Full, shell: DEFAULT_SHELL.to_string(), show_output: false, + time_unit: None, } } } @@ -192,3 +221,17 @@ impl BenchmarkResult { } } } + +#[test] +fn test_unit_short_name() { + assert_eq!("s", Unit::Second.short_name()); + assert_eq!("ms", Unit::MilliSecond.short_name()); +} + +// Note - the values are rounded when formatted. +#[test] +fn test_unit_format() { + let value: Second = 123.456789; + assert_eq!("123.457", Unit::Second.format(value)); + assert_eq!("123456.8", Unit::MilliSecond.format(value)); +} diff --git a/src/main.rs b/src/main.rs index e9412a3..7940e6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,7 +46,7 @@ use hyperfine::error::{OptionsError, ParameterScanError}; use hyperfine::export::{ExportManager, ExportType}; use hyperfine::internal::write_benchmark_comparison; use hyperfine::types::{ - BenchmarkResult, CmdFailureAction, Command, HyperfineOptions, OutputStyleOption, + BenchmarkResult, Unit, CmdFailureAction, Command, HyperfineOptions, OutputStyleOption, }; /// Print error message to stderr and terminate @@ -95,15 +95,15 @@ fn main() { let export_manager = build_export_manager(&matches); let commands = build_commands(&matches); - let res = match options { - Ok(opts) => run(&commands, &opts), - Err(e) => error(e.description()), + let res = match &options { + &Ok(ref opts) => run(&commands, &opts), + &Err(ref e) => error(e.description()), }; match res { Ok(timing_results) => { write_benchmark_comparison(&timing_results); - let ans = export_manager.write_results(timing_results); + let ans = export_manager.write_results(timing_results, options.unwrap().time_unit); if let Err(e) = ans { error(&format!( "The following error occurred while exporting: {}", @@ -196,6 +196,12 @@ fn build_hyperfine_options(matches: &ArgMatches) -> Result Some(Unit::MilliSecond), + Some("second") => Some(Unit::Second), + _ => None, + }; + Ok(options) }