mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-11-03 01:21:25 +00:00
Export selected metrics side by side in CSV
This commit is contained in:
1 parent
7178fedde1
commit
7cf08c30dc
10 files changed
+101
-70
No files matched your search
@@ -239,8 +239,9 @@ You can also use a preset to select a group of metrics:
|
|||||||
|
|
||||||
### Exporting results
|
### Exporting results
|
||||||
|
|
||||||
Hyperfine can export results to CSV, JSON, Markdown, AsciiDoc, and org-mode. Non-JSON formats
|
Hyperfine can export results to CSV, JSON, Markdown, AsciiDoc, and org-mode. CSV exports all
|
||||||
contain only the primary metric, which is the first metric selected by `--metrics`.
|
selected metrics, while Markdown, AsciiDoc, and org-mode contain only the primary metric,
|
||||||
|
which is the first metric selected by `--metrics`.
|
||||||
|
|
||||||
#### Markdown
|
#### Markdown
|
||||||
|
|
||||||
@@ -276,6 +277,14 @@ uvx --from 'git+https://github.com/sharkdp/hyperfine' \
|
|||||||
|---:|---:|
|
|---:|---:|
|
||||||
|
|
||||||
|
|
||||||
|
#### CSV
|
||||||
|
|
||||||
|
Use `--export-csv <file>` to export one row per command. Each metric selected by `--metrics`
|
||||||
|
contributes a group of columns, in selection order: `<metric>_unit`, `<metric>_mean`,
|
||||||
|
`<metric>_stddev`, `<metric>_median`, `<metric>_min`, and `<metric>_max`.
|
||||||
|
|
||||||
|
CSV uses explicitly selected units, or base units (seconds, bytes, or counts) when omitted.
|
||||||
|
|
||||||
### Detailed benchmark flowchart
|
### Detailed benchmark flowchart
|
||||||
|
|
||||||
The following chart explains the execution order of various benchmark runs when using options
|
The following chart explains the execution order of various benchmark runs when using options
|
||||||
|
|||||||
+6
-4
@@ -232,7 +232,7 @@ of the tool.
|
|||||||
.HP
|
.HP
|
||||||
\fB\-\-metrics\fR \fIMETRIC[:UNIT],...|default|time|all\fP
|
\fB\-\-metrics\fR \fIMETRIC[:UNIT],...|default|time|all\fP
|
||||||
.IP
|
.IP
|
||||||
Select the metrics to display, in order, with an optional unit for each.
|
Select the metrics to measure, in order, with an optional unit for each.
|
||||||
Default: \fBtime_wall_clock,memory_peak_resident\fR, or \fBtime_wall_clock\fR
|
Default: \fBtime_wall_clock,memory_peak_resident\fR, or \fBtime_wall_clock\fR
|
||||||
on Windows, where peak resident memory is not yet supported.
|
on Windows, where peak resident memory is not yet supported.
|
||||||
.IP
|
.IP
|
||||||
@@ -266,9 +266,11 @@ Export the primary metric's summary statistics as an AsciiDoc table to the given
|
|||||||
.HP
|
.HP
|
||||||
\fB\-\-export\-csv\fR \fIFILE\fP
|
\fB\-\-export\-csv\fR \fIFILE\fP
|
||||||
.IP
|
.IP
|
||||||
Export the primary metric's summary statistics as CSV to the given \fIFILE\fP,
|
Export summary statistics for all selected metrics as CSV to the given \fIFILE\fP,
|
||||||
identifying the metric and unit explicitly. Use the explicitly selected unit,
|
one row per command. Each metric contributes columns for unit, mean, stddev, median,
|
||||||
or the base unit (seconds, bytes, or unscaled counts) when omitted.
|
min, and max, prefixed with the metric name, in selection order. The command column
|
||||||
|
comes first and parameter columns come last. Use the explicitly selected units,
|
||||||
|
or base units (seconds, bytes, or unscaled counts) when omitted.
|
||||||
.HP
|
.HP
|
||||||
\fB\-\-export\-json\fR \fIFILE\fP
|
\fB\-\-export\-json\fR \fIFILE\fP
|
||||||
.IP
|
.IP
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ fn generate_results(args: &[&'static str]) -> Result<Vec<BenchmarkResult>> {
|
|||||||
assert_eq!(options.executor_kind, ExecutorKind::Mock(None));
|
assert_eq!(options.executor_kind, ExecutorKind::Mock(None));
|
||||||
|
|
||||||
let commands = Commands::from_cli_arguments(&cli_arguments)?;
|
let commands = Commands::from_cli_arguments(&cli_arguments)?;
|
||||||
let export_manager = ExportManager::from_cli_arguments(&cli_arguments, options.metrics[0])?;
|
let export_manager = ExportManager::from_cli_arguments(&cli_arguments, &options.metrics)?;
|
||||||
|
|
||||||
options.validate_against_command_list(&commands)?;
|
options.validate_against_command_list(&commands)?;
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -58,7 +58,7 @@ fn build_command() -> Command {
|
|||||||
.hide_default_value(true)
|
.hide_default_value(true)
|
||||||
.help("Performance metrics to measure, in order.\n\n\
|
.help("Performance metrics to measure, in order.\n\n\
|
||||||
This is either a preset name, or a comma-separated list of metrics with optional units. \
|
This is either a preset name, or a comma-separated list of metrics with optional units. \
|
||||||
The first metric is used for non-JSON exports, \
|
CSV exports all selected metrics; markup exports use the first metric. \
|
||||||
JSON always includes all collected metrics.\n\n\
|
JSON always includes all collected metrics.\n\n\
|
||||||
Presets (use one on its own):\n \
|
Presets (use one on its own):\n \
|
||||||
default Wall-clock time and peak RSS\n \
|
default Wall-clock time and peak RSS\n \
|
||||||
@@ -303,9 +303,10 @@ fn build_command() -> Command {
|
|||||||
.action(ArgAction::Set)
|
.action(ArgAction::Set)
|
||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.value_hint(ValueHint::FilePath)
|
.value_hint(ValueHint::FilePath)
|
||||||
.help("Export the primary metric summary statistics as CSV to the given FILE. If you need \
|
.help("Export summary statistics for all selected metrics as CSV to the given FILE. If you need \
|
||||||
all metrics for each individual run, use the JSON export format. \
|
all metrics for each individual run, use the JSON export format. \
|
||||||
Use the explicitly selected unit, or the base unit when unspecified."),
|
Column names are prefixed with the metric name. \
|
||||||
|
Use the explicitly selected units, or base units when unspecified."),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("export-json")
|
Arg::new("export-json")
|
||||||
|
|||||||
+54
-49
@@ -4,23 +4,34 @@ use super::Exporter;
|
|||||||
use crate::benchmark::benchmark_result::BenchmarkResult;
|
use crate::benchmark::benchmark_result::BenchmarkResult;
|
||||||
use crate::metric::MetricSelection;
|
use crate::metric::MetricSelection;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CsvExporter {}
|
pub struct CsvExporter {}
|
||||||
|
|
||||||
impl Exporter for CsvExporter {
|
impl Exporter for CsvExporter {
|
||||||
fn serialize(&self, results: &[BenchmarkResult], primary: MetricSelection) -> Result<Vec<u8>> {
|
fn serialize(
|
||||||
// CSV never chooses units automatically: an omitted suffix means base units.
|
&self,
|
||||||
let unit = primary.csv_unit();
|
results: &[BenchmarkResult],
|
||||||
|
metrics: &[MetricSelection],
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
// Omit metrics unavailable for all commands (possible with `--metrics all`).
|
||||||
|
let metrics: Vec<_> = metrics
|
||||||
|
.iter()
|
||||||
|
.filter(|selection| {
|
||||||
|
results
|
||||||
|
.iter()
|
||||||
|
.any(|result| selection.metric.summarize(&result.measurements).is_some())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
let mut writer = WriterBuilder::new().from_writer(vec![]);
|
let mut writer = WriterBuilder::new().from_writer(vec![]);
|
||||||
let mut headers: Vec<String> = [
|
let mut headers = vec!["command".to_owned()];
|
||||||
"command", "metric", "unit", "mean", "stddev", "median", "min", "max",
|
for selection in &metrics {
|
||||||
]
|
headers.extend(
|
||||||
.iter()
|
["unit", "mean", "stddev", "median", "min", "max"]
|
||||||
.copied()
|
.map(|field| format!("{}_{field}", selection.metric.name())),
|
||||||
.map(str::to_owned)
|
);
|
||||||
.collect();
|
}
|
||||||
if let Some(result) = results.first() {
|
if let Some(result) = results.first() {
|
||||||
headers.extend(
|
headers.extend(
|
||||||
result
|
result
|
||||||
@@ -32,36 +43,28 @@ impl Exporter for CsvExporter {
|
|||||||
writer.write_record(headers)?;
|
writer.write_record(headers)?;
|
||||||
|
|
||||||
for result in results {
|
for result in results {
|
||||||
let stats = primary
|
let mut fields = vec![result.get_name().to_owned()];
|
||||||
.metric
|
for selection in &metrics {
|
||||||
.summarize(&result.measurements)
|
// CSV never chooses units automatically: an omitted suffix means base units.
|
||||||
.with_context(|| {
|
let unit = selection.csv_unit();
|
||||||
format!(
|
let stats = selection.metric.summarize(&result.measurements);
|
||||||
"Metric '{}' is unavailable for '{}'",
|
fields.push(unit.symbol.to_owned());
|
||||||
primary.metric.name(),
|
// Preserve numeric precision rather than using rounded display values.
|
||||||
result.get_name()
|
fields.extend(
|
||||||
)
|
[
|
||||||
})?;
|
stats.map(|stats| stats.mean),
|
||||||
let mut fields = vec![
|
stats.and_then(|stats| stats.stddev),
|
||||||
result.get_name().to_owned(),
|
stats.map(|stats| stats.median),
|
||||||
primary.metric.name().to_owned(),
|
stats.map(|stats| stats.min),
|
||||||
unit.symbol.to_owned(),
|
stats.map(|stats| stats.max),
|
||||||
];
|
]
|
||||||
// Preserve numeric precision rather than using rounded display values.
|
.map(|value| {
|
||||||
fields.extend(
|
value
|
||||||
[
|
.map(|value| (value / unit.scale).to_string())
|
||||||
Some(stats.mean),
|
.unwrap_or_default()
|
||||||
stats.stddev,
|
}),
|
||||||
Some(stats.median),
|
);
|
||||||
Some(stats.min),
|
}
|
||||||
Some(stats.max),
|
|
||||||
]
|
|
||||||
.map(|value| {
|
|
||||||
value
|
|
||||||
.map(|value| (value / unit.scale).to_string())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
fields.extend(
|
fields.extend(
|
||||||
result
|
result
|
||||||
.parameters
|
.parameters
|
||||||
@@ -191,15 +194,18 @@ fn test_csv() {
|
|||||||
|
|
||||||
let actual = String::from_utf8(
|
let actual = String::from_utf8(
|
||||||
exporter
|
exporter
|
||||||
.serialize(&results, MetricSelection::default())
|
.serialize(
|
||||||
|
&results,
|
||||||
|
&MetricSelection::parse_list("time_wall_clock,memory_peak_resident").unwrap(),
|
||||||
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
insta::assert_snapshot!(actual, @r#"
|
insta::assert_snapshot!(actual, @r#"
|
||||||
command,metric,unit,mean,stddev,median,min,max,parameter_bar,parameter_foo
|
command,time_wall_clock_unit,time_wall_clock_mean,time_wall_clock_stddev,time_wall_clock_median,time_wall_clock_min,time_wall_clock_max,memory_peak_resident_unit,memory_peak_resident_mean,memory_peak_resident_stddev,memory_peak_resident_median,memory_peak_resident_min,memory_peak_resident_max,parameter_bar,parameter_foo
|
||||||
command_a,time_wall_clock,s,9,2.6457513110645907,8,7,12,two,one
|
command_a,s,9,2.6457513110645907,8,7,12,B,1024,0,1024,1024,1024,two,one
|
||||||
command_b,time_wall_clock,s,18,1,18,17,19,seven,one
|
command_b,s,18,1,18,17,19,B,1024,0,1024,1024,1024,seven,one
|
||||||
"#);
|
"#);
|
||||||
|
|
||||||
for (selection, unit, mean) in [
|
for (selection, unit, mean) in [
|
||||||
@@ -207,12 +213,11 @@ fn test_csv() {
|
|||||||
("memory_peak_resident:KiB", "KiB", "1"),
|
("memory_peak_resident:KiB", "KiB", "1"),
|
||||||
] {
|
] {
|
||||||
let output = exporter
|
let output = exporter
|
||||||
.serialize(&results, MetricSelection::parse_list(selection).unwrap()[0])
|
.serialize(&results, &MetricSelection::parse_list(selection).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut reader = csv::Reader::from_reader(output.as_slice());
|
let mut reader = csv::Reader::from_reader(output.as_slice());
|
||||||
let row = reader.records().next().unwrap().unwrap();
|
let row = reader.records().next().unwrap().unwrap();
|
||||||
assert_eq!(&row[1], "memory_peak_resident");
|
assert_eq!(&row[1], unit);
|
||||||
assert_eq!(&row[2], unit);
|
assert_eq!(&row[2], mean);
|
||||||
assert_eq!(&row[3], mean);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-1
@@ -65,7 +65,12 @@ pub struct JsonExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Exporter for JsonExporter {
|
impl Exporter for JsonExporter {
|
||||||
fn serialize(&self, results: &[BenchmarkResult], primary: MetricSelection) -> Result<Vec<u8>> {
|
fn serialize(
|
||||||
|
&self,
|
||||||
|
results: &[BenchmarkResult],
|
||||||
|
metrics: &[MetricSelection],
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let primary = metrics[0];
|
||||||
let mut output = to_vec_pretty(&HyperfineSummary {
|
let mut output = to_vec_pretty(&HyperfineSummary {
|
||||||
schema_version: JSON_SCHEMA_VERSION,
|
schema_version: JSON_SCHEMA_VERSION,
|
||||||
metadata: &self.metadata,
|
metadata: &self.metadata,
|
||||||
|
|||||||
@@ -77,7 +77,12 @@ pub trait MarkupExporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: MarkupExporter> Exporter for T {
|
impl<T: MarkupExporter> Exporter for T {
|
||||||
fn serialize(&self, results: &[BenchmarkResult], primary: MetricSelection) -> Result<Vec<u8>> {
|
fn serialize(
|
||||||
|
&self,
|
||||||
|
results: &[BenchmarkResult],
|
||||||
|
metrics: &[MetricSelection],
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let primary = metrics[0];
|
||||||
let stats: Vec<_> = results
|
let stats: Vec<_> = results
|
||||||
.iter()
|
.iter()
|
||||||
.map(|result| {
|
.map(|result| {
|
||||||
|
|||||||
+10
-6
@@ -44,8 +44,12 @@ pub enum ExportType {
|
|||||||
|
|
||||||
/// Interface for different exporters.
|
/// Interface for different exporters.
|
||||||
trait Exporter {
|
trait Exporter {
|
||||||
/// Export the given entries in the serialized form.
|
/// Export the given entries using a nonempty list of selected metrics, primary first.
|
||||||
fn serialize(&self, results: &[BenchmarkResult], primary: MetricSelection) -> Result<Vec<u8>>;
|
fn serialize(
|
||||||
|
&self,
|
||||||
|
results: &[BenchmarkResult],
|
||||||
|
metrics: &[MetricSelection],
|
||||||
|
) -> Result<Vec<u8>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ExportTarget {
|
pub enum ExportTarget {
|
||||||
@@ -61,16 +65,16 @@ struct ExporterWithTarget {
|
|||||||
/// Handles the management of multiple file exporters.
|
/// Handles the management of multiple file exporters.
|
||||||
pub struct ExportManager {
|
pub struct ExportManager {
|
||||||
exporters: Vec<ExporterWithTarget>,
|
exporters: Vec<ExporterWithTarget>,
|
||||||
primary: MetricSelection,
|
metrics: Vec<MetricSelection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExportManager {
|
impl ExportManager {
|
||||||
/// Build the ExportManager that will export the results specified
|
/// Build the ExportManager that will export the results specified
|
||||||
/// in the given ArgMatches
|
/// in the given ArgMatches
|
||||||
pub fn from_cli_arguments(matches: &ArgMatches, primary: MetricSelection) -> Result<Self> {
|
pub fn from_cli_arguments(matches: &ArgMatches, metrics: &[MetricSelection]) -> Result<Self> {
|
||||||
let mut export_manager = Self {
|
let mut export_manager = Self {
|
||||||
exporters: vec![],
|
exporters: vec![],
|
||||||
primary,
|
metrics: metrics.to_vec(),
|
||||||
};
|
};
|
||||||
{
|
{
|
||||||
let mut add_exporter = |flag, exporttype| -> Result<()> {
|
let mut add_exporter = |flag, exporttype| -> Result<()> {
|
||||||
@@ -120,7 +124,7 @@ impl ExportManager {
|
|||||||
/// order not to clutter the output of hyperfine with intermediate results).
|
/// order not to clutter the output of hyperfine with intermediate results).
|
||||||
pub fn write_results(&self, results: &[BenchmarkResult], intermediate: bool) -> Result<()> {
|
pub fn write_results(&self, results: &[BenchmarkResult], intermediate: bool) -> Result<()> {
|
||||||
for e in &self.exporters {
|
for e in &self.exporters {
|
||||||
let content = || e.exporter.serialize(results, self.primary);
|
let content = || e.exporter.serialize(results, &self.metrics);
|
||||||
|
|
||||||
match e.target {
|
match e.target {
|
||||||
ExportTarget::File(ref filename) => {
|
ExportTarget::File(ref filename) => {
|
||||||
|
|||||||
+2
-2
@@ -15,10 +15,10 @@ fn get_output<E: Exporter + Default>(results: &[BenchmarkResult], unit: Option<U
|
|||||||
exporter
|
exporter
|
||||||
.serialize(
|
.serialize(
|
||||||
results,
|
results,
|
||||||
MetricSelection {
|
&[MetricSelection {
|
||||||
unit,
|
unit,
|
||||||
..MetricSelection::default()
|
..MetricSelection::default()
|
||||||
},
|
}],
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let export_manager = ExportManager::from_cli_arguments(&cli_arguments, options.metrics[0])?;
|
let export_manager = ExportManager::from_cli_arguments(&cli_arguments, &options.metrics)?;
|
||||||
|
|
||||||
let mut scheduler = Scheduler::new(&commands, &options, &export_manager);
|
let mut scheduler = Scheduler::new(&commands, &options, &export_manager);
|
||||||
scheduler.run_benchmarks()?;
|
scheduler.run_benchmarks()?;
|
||||||
|
|||||||
Reference in new issue
Block a user