Support hardware counters (instructions, cache misses, ...) (#983)

* Add hardware counters to JSON exports

* Clarify Linux hardware counter setup
This commit is contained in:
David Peter authored and GitHub committed 2026-10-06 08:59:40 +02:00
1 parent ab86cb99cc
commit bc3a3f0524
13 files changed
+432 -34

No files matched your search

+6 -1
View File
@@ -62,6 +62,7 @@ fn run_command_and_measure_common(
command_input_policy: &CommandInputPolicy,
command_output_policy: &CommandOutputPolicy,
command_name: &str,
collect_hardware_counters: bool,
) -> Result<Measurement> {
let stdin = command_input_policy.get_stdin()?;
let (stdout, stderr) = command_output_policy.get_stdout_stderr()?;
@@ -76,7 +77,7 @@ fn run_command_and_measure_common(
command.env("HYPERFINE_ITERATION", value);
}
let measurement = execute_and_measure(command)
let measurement = execute_and_measure(command, collect_hardware_counters)
.with_context(|| format!("Failed to run command '{command_name}'"))?;
if !measurement.exit_status.success() {
@@ -144,6 +145,7 @@ impl Executor for RawExecutor<'_> {
&self.options.command_input_policy,
output_policy,
&command.get_command_line(),
matches!(iteration, BenchmarkIteration::Benchmark(_)),
)
}
@@ -199,6 +201,7 @@ impl Executor for ShellExecutor<'_> {
&self.options.command_input_policy,
output_policy,
&command.get_command_line(),
false,
)?;
// Subtract shell spawning time
@@ -278,6 +281,7 @@ impl Executor for ShellExecutor<'_> {
time_user: measurements.time_user_mean(),
time_system: measurements.time_system_mean(),
memory_peak_resident: None,
hardware_counters: Default::default(),
exit_status: ExitStatus::default(),
});
@@ -336,6 +340,7 @@ impl Executor for MockExecutor {
time_user: Time::zero(),
time_system: Time::zero(),
memory_peak_resident: Some(Information::zero()),
hardware_counters: Default::default(),
exit_status,
})
}
+44
View File
@@ -29,6 +29,47 @@ where
}
}
fn serialize_optional_count<S>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
struct Count {
value: u64,
}
value.map(|value| Count { value }).serialize(serializer)
}
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct HardwareCounters {
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_optional_count"
)]
pub cpu_cycles: Option<u64>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_optional_count"
)]
pub instructions: Option<u64>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_optional_count"
)]
pub cache_references: Option<u64>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_optional_count"
)]
pub cache_misses: Option<u64>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_optional_count"
)]
pub branch_misses: Option<u64>,
}
/// Performance metric measurements and exit code for a single run
#[derive(Debug, Clone, Default, Serialize, PartialEq)]
pub struct Measurement {
@@ -51,6 +92,9 @@ pub struct Measurement {
)]
pub memory_peak_resident: Option<Information>,
#[serde(flatten)]
pub hardware_counters: HardwareCounters,
// The exit status of the process
#[serde(rename = "exit_code", serialize_with = "serialize_exit_status")]
pub exit_status: ExitStatus,