mirror of
https://github.com/sharkdp/hyperfine.git
synced 2026-11-01 17:00:18 +00:00
Add hardware counters to JSON exports
This commit is contained in:
1 parent
ab86cb99cc
commit
f89106dd15
13 files changed
+423
-34
No files matched your search
+94
-32
@@ -8,7 +8,7 @@ use crate::benchmark::benchmark_result::BenchmarkResult;
|
||||
use crate::benchmark::measurement::Measurements;
|
||||
use crate::options::SortOrder;
|
||||
use crate::quantity::statistics::{max, mean, median, min, standard_deviation, UnsafeRawValue};
|
||||
use crate::quantity::{byte, second, QuantityInUnit, Ratio, TimeUnit, Zero};
|
||||
use crate::quantity::{byte, ratio, second, QuantityInUnit, Ratio, TimeUnit, Zero};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -40,36 +40,92 @@ struct BenchmarkSummary {
|
||||
time_system: StatisticalSummary,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
memory_peak_resident: Option<StatisticalSummary>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
cpu_cycles: Option<StatisticalSummary>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
instructions: Option<StatisticalSummary>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
cache_references: Option<StatisticalSummary>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
cache_misses: Option<StatisticalSummary>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
branch_misses: Option<StatisticalSummary>,
|
||||
}
|
||||
|
||||
impl BenchmarkSummary {
|
||||
fn from_measurements(measurements: &Measurements) -> Self {
|
||||
let values = &measurements.measurements;
|
||||
let mut memory_values = values
|
||||
.iter()
|
||||
.filter_map(|m| m.memory_peak_resident)
|
||||
.peekable();
|
||||
Self {
|
||||
time_wall_clock: StatisticalSummary::from_values(
|
||||
values.iter().map(|m| m.time_wall_clock),
|
||||
second,
|
||||
),
|
||||
time_user: StatisticalSummary::from_values(values.iter().map(|m| m.time_user), second),
|
||||
values.iter().map(|m| Some(m.time_wall_clock)),
|
||||
Some(second),
|
||||
)
|
||||
.expect("Benchmarks have at least one measurement"),
|
||||
time_user: StatisticalSummary::from_values(
|
||||
values.iter().map(|m| Some(m.time_user)),
|
||||
Some(second),
|
||||
)
|
||||
.expect("Benchmarks have at least one measurement"),
|
||||
time_system: StatisticalSummary::from_values(
|
||||
values.iter().map(|m| m.time_system),
|
||||
second,
|
||||
values.iter().map(|m| Some(m.time_system)),
|
||||
Some(second),
|
||||
)
|
||||
.expect("Benchmarks have at least one measurement"),
|
||||
memory_peak_resident: StatisticalSummary::from_values(
|
||||
values
|
||||
.iter()
|
||||
.filter_map(|m| m.memory_peak_resident)
|
||||
.map(Some),
|
||||
Some(byte),
|
||||
),
|
||||
cpu_cycles: StatisticalSummary::from_values(
|
||||
values.iter().map(|m| {
|
||||
m.hardware_counters
|
||||
.cpu_cycles
|
||||
.map(|v| Ratio::new::<ratio>(v as f64))
|
||||
}),
|
||||
None::<ratio>,
|
||||
),
|
||||
instructions: StatisticalSummary::from_values(
|
||||
values.iter().map(|m| {
|
||||
m.hardware_counters
|
||||
.instructions
|
||||
.map(|v| Ratio::new::<ratio>(v as f64))
|
||||
}),
|
||||
None::<ratio>,
|
||||
),
|
||||
cache_references: StatisticalSummary::from_values(
|
||||
values.iter().map(|m| {
|
||||
m.hardware_counters
|
||||
.cache_references
|
||||
.map(|v| Ratio::new::<ratio>(v as f64))
|
||||
}),
|
||||
None::<ratio>,
|
||||
),
|
||||
cache_misses: StatisticalSummary::from_values(
|
||||
values.iter().map(|m| {
|
||||
m.hardware_counters
|
||||
.cache_misses
|
||||
.map(|v| Ratio::new::<ratio>(v as f64))
|
||||
}),
|
||||
None::<ratio>,
|
||||
),
|
||||
branch_misses: StatisticalSummary::from_values(
|
||||
values.iter().map(|m| {
|
||||
m.hardware_counters
|
||||
.branch_misses
|
||||
.map(|v| Ratio::new::<ratio>(v as f64))
|
||||
}),
|
||||
None::<ratio>,
|
||||
),
|
||||
memory_peak_resident: memory_values
|
||||
.peek()
|
||||
.is_some()
|
||||
.then(|| StatisticalSummary::from_values(memory_values, byte)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StatisticalSummary {
|
||||
unit: &'static str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
unit: Option<&'static str>,
|
||||
count: usize,
|
||||
mean: f64,
|
||||
stddev: Option<f64>,
|
||||
@@ -79,7 +135,10 @@ struct StatisticalSummary {
|
||||
}
|
||||
|
||||
impl StatisticalSummary {
|
||||
fn from_values<Q, P, U>(values: impl IntoIterator<Item = Q>, _unit: U) -> Self
|
||||
fn from_values<Q, P, U>(
|
||||
values: impl IntoIterator<Item = Option<Q>>,
|
||||
unit: Option<U>,
|
||||
) -> Option<Self>
|
||||
where
|
||||
Q: Copy
|
||||
+ PartialOrd
|
||||
@@ -92,13 +151,13 @@ impl StatisticalSummary {
|
||||
P: Into<Q>,
|
||||
U: uom::si::Unit,
|
||||
{
|
||||
let values: Vec<_> = values.into_iter().collect();
|
||||
assert!(
|
||||
!values.is_empty(),
|
||||
"Statistical summaries require at least one measurement"
|
||||
);
|
||||
Self {
|
||||
unit: U::singular(),
|
||||
// Only summarize a metric when it is available for every supplied measurement.
|
||||
let values: Vec<_> = values.into_iter().collect::<Option<_>>()?;
|
||||
if values.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
unit: unit.map(|_| U::singular()),
|
||||
count: values.len(),
|
||||
mean: mean(values.iter().copied()).value_in_unit(),
|
||||
stddev: (values.len() > 1)
|
||||
@@ -106,7 +165,7 @@ impl StatisticalSummary {
|
||||
median: median(values.iter().copied()).value_in_unit(),
|
||||
min: min(values.iter().copied()).value_in_unit(),
|
||||
max: max(values.iter().copied()).value_in_unit(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,22 +208,25 @@ fn test_statistical_summary_unit_conversion() {
|
||||
use crate::quantity::{kibibyte, millisecond, Information, Time};
|
||||
|
||||
let time = StatisticalSummary::from_values(
|
||||
[Time::new::<second>(1.0), Time::new::<second>(3.0)],
|
||||
millisecond,
|
||||
);
|
||||
[Time::new::<second>(1.0), Time::new::<second>(3.0)].map(Some),
|
||||
Some(millisecond),
|
||||
)
|
||||
.unwrap();
|
||||
let information = StatisticalSummary::from_values(
|
||||
[
|
||||
Information::new::<byte>(1024.0),
|
||||
Information::new::<byte>(3072.0),
|
||||
],
|
||||
kibibyte,
|
||||
);
|
||||
]
|
||||
.map(Some),
|
||||
Some(kibibyte),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (summary, unit, scale) in [
|
||||
(time, "millisecond", 1000.0),
|
||||
(information, "kibibyte", 1.0),
|
||||
] {
|
||||
assert_eq!(summary.unit, unit);
|
||||
assert_eq!(summary.unit, Some(unit));
|
||||
assert_eq!(summary.count, 2);
|
||||
approx::assert_relative_eq!(summary.mean, 2.0 * scale);
|
||||
approx::assert_relative_eq!(summary.median, 2.0 * scale);
|
||||
|
||||
Reference in new issue
Block a user