diff --git a/src/executor/wall_time/perf/mod.rs b/src/executor/wall_time/perf/mod.rs index cd0dd284..33a1ebf0 100644 --- a/src/executor/wall_time/perf/mod.rs +++ b/src/executor/wall_time/perf/mod.rs @@ -18,6 +18,8 @@ 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; use runner_shared::artifacts::ExecutionTimestamps; @@ -131,11 +133,20 @@ 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 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. diff --git a/src/executor/wall_time/perf/perf_executable.rs b/src/executor/wall_time/perf/perf_executable.rs index 6da51cd6..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}; @@ -62,3 +63,75 @@ 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(",")))) +} + +pub fn get_compression_flags>(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) + } +}