From 3c1fd29a5975081718307513f5f3049dd818ac8e Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Fri, 7 Nov 2025 11:52:37 +0100 Subject: [PATCH 1/3] feat: add more events to perf monitoring --- src/executor/wall_time/perf/mod.rs | 7 ++- .../wall_time/perf/perf_executable.rs | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/executor/wall_time/perf/mod.rs b/src/executor/wall_time/perf/mod.rs index cd0dd284..aced5192 100644 --- a/src/executor/wall_time/perf/mod.rs +++ b/src/executor/wall_time/perf/mod.rs @@ -18,6 +18,7 @@ use crate::run::UnwindingMode; use anyhow::Context; use fifo::PerfFifo; use libc::pid_t; +use perf_executable::get_event_flags; use perf_map::ProcessSymbols; use runner_shared::artifacts::ArtifactExt; use runner_shared::artifacts::ExecutionTimestamps; @@ -131,11 +132,15 @@ impl PerfRunner { let working_perf_executable = get_working_perf_executable().context("Failed to find a working perf executable")?; - let mut perf_wrapper_builder = CommandBuilder::new(working_perf_executable); + let mut perf_wrapper_builder = CommandBuilder::new(&working_perf_executable); perf_wrapper_builder.arg("record"); if !is_codspeed_debug_enabled() { perf_wrapper_builder.arg("--quiet"); } + // Add events flag if all required events are available + if let Some(events_flag) = get_event_flags(&working_perf_executable)? { + perf_wrapper_builder.arg(events_flag); + } perf_wrapper_builder.args([ "--timestamp", // Required for matching the markers and URIs to the samples. diff --git a/src/executor/wall_time/perf/perf_executable.rs b/src/executor/wall_time/perf/perf_executable.rs index 6da51cd6..0b233651 100644 --- a/src/executor/wall_time/perf/perf_executable.rs +++ b/src/executor/wall_time/perf/perf_executable.rs @@ -62,3 +62,49 @@ pub fn get_working_perf_executable() -> Option { debug!("perf is installed but not functioning correctly"); None } + +/// Detects if the required perf events are available on this system. +/// Returns the flags to pass to perf record command if they are available, otherwise returns None. +pub fn get_event_flags(perf_executable: &OsString) -> anyhow::Result> { + const CYCLES_EVENT_NAME: &str = "cycles"; + const CACHE_REFERENCES_EVENT_NAME: &str = "cache-references"; + const CACHE_MISSES_EVENT_NAME: &str = "cache-misses"; + + let perf_events = [ + CYCLES_EVENT_NAME, + CACHE_REFERENCES_EVENT_NAME, + CACHE_MISSES_EVENT_NAME, + ]; + + let output = Command::new(perf_executable) + .arg("list") + .output() + .context("Failed to run perf list")?; + + let stdout = String::from_utf8_lossy(&output.stdout); + + // Check if all required events are available + let missing_events: Vec<&str> = perf_events + .iter() + .filter(|&&event| { + !stdout + .lines() + .any(|line| line.split_whitespace().any(|word| word == event)) + }) + .copied() + .collect(); + + if !missing_events.is_empty() { + debug!( + "Not all required perf events available. Missing: [{}], using default events", + missing_events.join(", ") + ); + return Ok(None); + } + + debug!( + "All required perf events available: {}", + perf_events.join(", ") + ); + Ok(Some(format!("-e {{{}}}", perf_events.join(",")))) +} From dfa5a03b5b73fdb04a93722c9ac6c46424c0816e Mon Sep 17 00:00:00 2001 From: Arthur Pastel Date: Sat, 8 Nov 2025 13:47:38 +0100 Subject: [PATCH 2/3] feat: enable perf data compression --- src/executor/wall_time/perf/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/executor/wall_time/perf/mod.rs b/src/executor/wall_time/perf/mod.rs index aced5192..7d7a7d51 100644 --- a/src/executor/wall_time/perf/mod.rs +++ b/src/executor/wall_time/perf/mod.rs @@ -149,6 +149,7 @@ impl PerfRunner { "--freq=997", // Use a prime number to avoid synchronization with periodic tasks "--delay=-1", "-g", + "--compression-level=3", // 3 is a widely adopted default level (AWS Athena, Python, ...) "--user-callchains", &format!("--call-graph={cg_mode}"), &format!( From 17985a4dc6470724dba913c8e026d4d90db8a175 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Wed, 24 Dec 2025 10:27:53 +0100 Subject: [PATCH 3/3] feat: check for perf compression and enable multi-events conditionally --- src/executor/wall_time/perf/mod.rs | 13 ++++++--- .../wall_time/perf/perf_executable.rs | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/executor/wall_time/perf/mod.rs b/src/executor/wall_time/perf/mod.rs index 7d7a7d51..33a1ebf0 100644 --- a/src/executor/wall_time/perf/mod.rs +++ b/src/executor/wall_time/perf/mod.rs @@ -18,6 +18,7 @@ use crate::run::UnwindingMode; use anyhow::Context; use fifo::PerfFifo; use libc::pid_t; +use perf_executable::get_compression_flags; use perf_executable::get_event_flags; use perf_map::ProcessSymbols; use runner_shared::artifacts::ArtifactExt; @@ -137,10 +138,15 @@ impl PerfRunner { if !is_codspeed_debug_enabled() { perf_wrapper_builder.arg("--quiet"); } - // Add events flag if all required events are available - if let Some(events_flag) = get_event_flags(&working_perf_executable)? { - perf_wrapper_builder.arg(events_flag); + // Add compression if available + if let Some(compression_flags) = get_compression_flags(&working_perf_executable)? { + perf_wrapper_builder.arg(compression_flags); + // Add events flag if all required events are available + if let Some(events_flag) = get_event_flags(&working_perf_executable)? { + perf_wrapper_builder.arg(events_flag); + } } + perf_wrapper_builder.args([ "--timestamp", // Required for matching the markers and URIs to the samples. @@ -149,7 +155,6 @@ impl PerfRunner { "--freq=997", // Use a prime number to avoid synchronization with periodic tasks "--delay=-1", "-g", - "--compression-level=3", // 3 is a widely adopted default level (AWS Athena, Python, ...) "--user-callchains", &format!("--call-graph={cg_mode}"), &format!( diff --git a/src/executor/wall_time/perf/perf_executable.rs b/src/executor/wall_time/perf/perf_executable.rs index 0b233651..b67b8726 100644 --- a/src/executor/wall_time/perf/perf_executable.rs +++ b/src/executor/wall_time/perf/perf_executable.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use std::path::Path; use std::{ffi::OsString, process::Command}; @@ -108,3 +109,29 @@ pub fn get_event_flags(perf_executable: &OsString) -> anyhow::Result>(perf_executable: S) -> Result> { + let output = Command::new(perf_executable.as_ref()) + .arg("version") + .arg("--build-options") + .output() + .context("Failed to run perf version --build-options")?; + + let stdout = String::from_utf8_lossy(&output.stdout); + debug!("Perf version build options:\n{stdout}"); + + // Look for zstd compression support in the build options + // Expected format: " zstd: [ on ] # HAVE_ZSTD_SUPPORT" + let has_zstd = stdout + .lines() + .any(|line| line.to_lowercase().contains("zstd: [ on")); + + if has_zstd { + debug!("perf supports zstd compression"); + // 3 is a widely adopted default level (AWS Athena, Python, ...) + Ok(Some("--compression-level=3".to_string())) + } else { + debug!("perf does not support zstd compression"); + Ok(None) + } +}