Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/executor/wall_time/perf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
73 changes: 73 additions & 0 deletions src/executor/wall_time/perf/perf_executable.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::prelude::*;
use std::path::Path;

use std::{ffi::OsString, process::Command};

Expand Down Expand Up @@ -62,3 +63,75 @@ pub fn get_working_perf_executable() -> Option<OsString> {
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<Option<String>> {
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<S: AsRef<Path>>(perf_executable: S) -> Result<Option<String>> {
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)
}
}
Loading