mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-10-28 16:18:57 +00:00
Add option to set time units for CLI & Markdown, closes #80
Add option to set the time units to either `MilliSecond` or `Second` for both CLI output and Markdown export, overriding the default logic.
This commit is contained in:
1 parent
1ec810f8d4
commit
f0b6f130ea
9 files changed
+172
-67
No files matched your search
+14
-4
@@ -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")
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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<BenchmarkResult>) -> Result<Vec<u8>> {
|
||||
fn serialize(&self, results: &Vec<BenchmarkResult>, _unit: Option<Unit>) -> Result<Vec<u8>> {
|
||||
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;
|
||||
|
||||
|
||||
@@ -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<BenchmarkResult>) -> Result<Vec<u8>> {
|
||||
fn serialize(&self, results: &Vec<BenchmarkResult>, _unit: Option<Unit>) -> Result<Vec<u8>> {
|
||||
let mut output = to_vec_pretty(&HyperfineSummary { results });
|
||||
for content in output.iter_mut() {
|
||||
content.push(b'\n');
|
||||
|
||||
@@ -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<BenchmarkResult>) -> Result<Vec<u8>> {
|
||||
let unit = if let Some(first_result) = results.first() {
|
||||
fn serialize(&self, results: &Vec<BenchmarkResult>, unit: Option<Unit>) -> Result<Vec<u8>> {
|
||||
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<u8>, 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);
|
||||
}
|
||||
@@ -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<BenchmarkResult>) -> Result<Vec<u8>>;
|
||||
fn serialize(&self, results: &Vec<BenchmarkResult>, unit: Option<Unit>) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
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<BenchmarkResult>) -> Result<()> {
|
||||
pub fn write_results(&self, results: Vec<BenchmarkResult>, unit: Option<Unit>) -> 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(())
|
||||
|
||||
+1
-39
@@ -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<Unit>) -> (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);
|
||||
|
||||
@@ -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<Unit>,
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
+11
-5
@@ -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<HyperfineOptions, Opt
|
||||
options.failure_action = CmdFailureAction::Ignore;
|
||||
}
|
||||
|
||||
options.time_unit = match matches.value_of("time-unit") {
|
||||
Some("millisecond") => Some(Unit::MilliSecond),
|
||||
Some("second") => Some(Unit::Second),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user