From 0db4a607c85a2eff4ab54c3c4588e748ea8963f3 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Wed, 25 Jun 2025 11:47:39 +0200 Subject: [PATCH] feat: add cli args for perf --- src/run/config.rs | 17 ++++++++++++- src/run/mod.rs | 30 +++++++++++++++++++++++ src/run/runner/wall_time/executor.rs | 24 +++++++------------ src/run/runner/wall_time/perf/mod.rs | 36 ++++++++++++++++++---------- 4 files changed, 79 insertions(+), 28 deletions(-) diff --git a/src/run/config.rs b/src/run/config.rs index 6af0b40a..a7c1763b 100644 --- a/src/run/config.rs +++ b/src/run/config.rs @@ -6,7 +6,7 @@ use url::Url; use crate::run::run_environment::RepositoryProvider; use crate::run::RunArgs; -use super::RunnerMode; +use super::{RunnerMode, UnwindingMode}; #[derive(Debug)] pub struct Config { @@ -18,6 +18,8 @@ pub struct Config { pub mode: RunnerMode, pub instruments: Instruments, + pub enable_perf: bool, + pub perf_unwinding_mode: Option, pub profile_folder: Option, pub skip_upload: bool, @@ -50,6 +52,8 @@ impl Config { command: "".into(), mode: RunnerMode::Instrumentation, instruments: Instruments::test(), + perf_unwinding_mode: None, + enable_perf: false, profile_folder: None, skip_upload: false, skip_run: false, @@ -86,6 +90,8 @@ impl TryFrom for Config { working_directory: args.working_directory, mode: args.mode, instruments, + perf_unwinding_mode: args.perf_run_args.perf_unwinding_mode, + enable_perf: args.perf_run_args.enable_perf, command: args.command.join(" "), profile_folder: args.profile_folder, skip_upload: args.skip_upload, @@ -105,6 +111,7 @@ fn extract_owner_and_repository_from_arg(owner_and_repository: &str) -> Result<( #[cfg(test)] mod tests { use crate::run::instruments::MongoDBConfig; + use crate::run::PerfRunArgs; use super::*; @@ -124,6 +131,10 @@ mod tests { skip_upload: false, skip_run: false, skip_setup: false, + perf_run_args: PerfRunArgs { + enable_perf: false, + perf_unwinding_mode: None, + }, command: vec!["cargo".into(), "codspeed".into(), "bench".into()], }) .unwrap(); @@ -154,6 +165,10 @@ mod tests { skip_upload: true, skip_run: true, skip_setup: true, + perf_run_args: PerfRunArgs { + enable_perf: false, + perf_unwinding_mode: Some(UnwindingMode::FramePointer), + }, command: vec!["cargo".into(), "codspeed".into(), "bench".into()], }) .unwrap(); diff --git a/src/run/mod.rs b/src/run/mod.rs index 9a32386e..edf19bae 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -38,6 +38,29 @@ fn show_banner() { debug!("codspeed v{}", VERSION); } +#[derive(Debug, Copy, Clone, PartialEq, ValueEnum, Default)] +pub enum UnwindingMode { + /// Use the frame pointer for unwinding. Requires the binary to be compiled with frame pointers enabled. + #[clap(name = "fp")] + FramePointer, + + /// Use DWARF unwinding. This does not require any special compilation flags and is enabled by default. + #[default] + Dwarf, +} + +#[derive(Args, Debug, Clone)] +pub struct PerfRunArgs { + /// Enable the performance runner, which uses `perf` to collect performance data. + /// This is only supported on Linux. + #[arg(long, env = "CODSPEED_PERF_ENABLED", default_value_t = false)] + enable_perf: bool, + + /// The unwinding mode that should be used with perf to collect the call stack. + #[arg(long, env = "CODSPEED_PERF_UNWINDING_MODE")] + perf_unwinding_mode: Option, +} + #[derive(Args, Debug)] pub struct RunArgs { /// The upload URL to use for uploading the results, useful for on-premises installations @@ -104,6 +127,9 @@ pub struct RunArgs { #[arg(long, default_value = "false", hide = true)] pub skip_setup: bool, + #[command(flatten)] + pub perf_run_args: PerfRunArgs, + /// The bench command to run pub command: Vec, } @@ -139,6 +165,10 @@ impl RunArgs { skip_upload: false, skip_run: false, skip_setup: false, + perf_run_args: PerfRunArgs { + enable_perf: false, + perf_unwinding_mode: None, + }, command: vec![], } } diff --git a/src/run/runner/wall_time/executor.rs b/src/run/runner/wall_time/executor.rs index ea0adbb6..eb607f44 100644 --- a/src/run/runner/wall_time/executor.rs +++ b/src/run/runner/wall_time/executor.rs @@ -17,15 +17,8 @@ pub struct WallTimeExecutor { impl WallTimeExecutor { pub fn new() -> Self { - let use_perf = if cfg!(target_os = "linux") { - std::env::var("CODSPEED_USE_PERF").is_ok() - } else { - false - }; - debug!("Running the cmd with perf: {}", use_perf); - Self { - perf: use_perf.then(PerfRunner::new), + perf: cfg!(target_os = "linux").then(PerfRunner::new), } } } @@ -66,13 +59,14 @@ impl Executor for WallTimeExecutor { } let bench_cmd = get_bench_command(config)?; - let status = if let Some(perf) = &self.perf { - perf.run(cmd, &bench_cmd).await - } else { - cmd.args(["-c", &bench_cmd]); - debug!("cmd: {:?}", cmd); - - run_command_with_log_pipe(cmd).await + let status = match (config.enable_perf, &self.perf) { + (true, Some(perf)) => perf.run(cmd, &bench_cmd, config).await, + _ => { + cmd.args(["-c", &bench_cmd]); + debug!("cmd: {:?}", cmd); + + run_command_with_log_pipe(cmd).await + } }; let status = diff --git a/src/run/runner/wall_time/perf/mod.rs b/src/run/runner/wall_time/perf/mod.rs index 8f8c2493..5253fdba 100644 --- a/src/run/runner/wall_time/perf/mod.rs +++ b/src/run/runner/wall_time/perf/mod.rs @@ -1,10 +1,12 @@ #![cfg_attr(not(unix), allow(dead_code, unused_mut))] use crate::prelude::*; +use crate::run::config::Config; use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe_and_callback; use crate::run::runner::helpers::setup::run_with_sudo; use crate::run::runner::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore; use crate::run::runner::valgrind::helpers::perf_maps::harvest_perf_maps_for_pids; +use crate::run::UnwindingMode; use anyhow::Context; use fifo::{PerfFifo, RunnerFifo}; use futures::stream::FuturesUnordered; @@ -72,7 +74,12 @@ impl PerfRunner { } } - pub async fn run(&self, mut cmd: Command, bench_cmd: &str) -> anyhow::Result { + pub async fn run( + &self, + mut cmd: Command, + bench_cmd: &str, + config: &Config, + ) -> anyhow::Result { let perf_fifo = PerfFifo::new()?; let runner_fifo = RunnerFifo::new()?; @@ -83,18 +90,23 @@ impl PerfRunner { .prefix(PERF_DATA_PREFIX) .tempfile_in(&self.perf_dir)?; - // Detect the mode based on the command to be executed - let cg_mode = if bench_cmd.contains("cargo") { - "dwarf" - } else if bench_cmd.contains("pytest") { - "fp" - } else { - panic!( - "Perf not supported. Failed to detect call graph mode for command: {}", - bench_cmd - ) + // Infer the unwinding mode from the benchmark cmd + let cg_mode = match (config.perf_unwinding_mode, &bench_cmd) { + (Some(mode), _) => mode, + (None, cmd) if cmd.contains("pytest") => UnwindingMode::FramePointer, + (None, cmd) if cmd.contains("cargo") => UnwindingMode::Dwarf, + (None, _) => { + // Default to dwarf unwinding since it works well with most binaries. + debug!("No call graph mode detected, defaulting to dwarf"); + UnwindingMode::Dwarf + } + }; + + let cg_mode = match cg_mode { + UnwindingMode::FramePointer => "fp", + UnwindingMode::Dwarf => "dwarf", }; - debug!("Using call graph mode: {}", cg_mode); + debug!("Using call graph mode: {:?}", cg_mode); let quiet_flag = { let log_level = std::env::var("CODSPEED_LOG")