diff --git a/src/cli/shared.rs b/src/cli/shared.rs index 0267313a8..863dea98b 100644 --- a/src/cli/shared.rs +++ b/src/cli/shared.rs @@ -230,7 +230,7 @@ pub struct WalltimeRunArgs { /// - Java: `fp` /// - Go: `fp` /// - Python: `dwarf,32768` - /// - Others: `dwarf,8192` + /// - Others: `dwarf,32000` (samply), `dwarf,8192` (perf) #[arg( long, env = "CODSPEED_WALLTIME_UNWINDING_MODE", diff --git a/src/executor/wall_time/profiler/mod.rs b/src/executor/wall_time/profiler/mod.rs index 07258986a..b865c9cde 100644 --- a/src/executor/wall_time/profiler/mod.rs +++ b/src/executor/wall_time/profiler/mod.rs @@ -6,6 +6,7 @@ pub mod perf; pub mod samply; +mod unwinding; const WALLTIME_METADATA_CURRENT_VERSION: u64 = 1; diff --git a/src/executor/wall_time/profiler/perf/mod.rs b/src/executor/wall_time/profiler/perf/mod.rs index 9283836e4..4619ded43 100644 --- a/src/executor/wall_time/profiler/perf/mod.rs +++ b/src/executor/wall_time/profiler/perf/mod.rs @@ -4,7 +4,6 @@ use crate::executor::ExecutorConfig; use crate::executor::ToolStatus; use crate::executor::config::WalltimeUnwinding; use crate::executor::helpers::command::CommandBuilder; -use crate::executor::helpers::detect_executable::command_has_executable; use crate::executor::helpers::env::is_codspeed_debug_enabled; use crate::executor::helpers::env::suppress_go_perf_unwinding_warning; use crate::executor::helpers::harvest_perf_maps_for_pids::harvest_perf_maps_for_pids; @@ -16,6 +15,7 @@ use crate::executor::wall_time::profiler::Profiler; use crate::executor::wall_time::profiler::SAMPLING_RATE_HZ; use crate::executor::wall_time::profiler::WALLTIME_METADATA_CURRENT_VERSION; use crate::executor::wall_time::profiler::perf::perf_executable::get_working_perf_executable; +use crate::executor::wall_time::profiler::unwinding::resolve_unwinding; use crate::prelude::*; use crate::system::SystemInfo; use anyhow::Context; @@ -39,40 +39,6 @@ pub mod perf_executable; const PERF_PIPEDATA_FILE_NAME: &str = "perf.pipedata"; -/// Unwinding suggested for the benchmarked runtime, if it has a known best setting. -fn suggested_unwinding(command: &str) -> Option { - if command_has_executable( - command, - &["gradle", "gradlew", "java", "maven", "mvn", "mvnw"], - ) { - return Some(WalltimeUnwinding::FramePointer); - } - if command_has_executable(command, &["go"]) { - return Some(WalltimeUnwinding::FramePointer); - } - if command_has_executable(command, &["pytest", "uv", "python", "python3"]) { - return Some(WalltimeUnwinding::Dwarf { - stack_size: Some(32 * 1024), - }); - } - None -} - -fn resolve_unwinding(command: &str, explicit: Option) -> WalltimeUnwinding { - let suggested = suggested_unwinding(command); - let Some(explicit) = explicit else { - return suggested.unwrap_or(WalltimeUnwinding::Dwarf { stack_size: None }); - }; - if let Some(suggested) = suggested - && suggested != explicit - { - warn!( - "Overriding suggested unwinding `{suggested}` with `{explicit}`, call stacks may be incomplete" - ); - } - explicit -} - pub struct PerfProfiler { /// Set by [`Profiler::wrap_command`]; used by the FIFO hooks to control event /// recording on the live `perf record` process. @@ -325,32 +291,3 @@ impl BenchmarkData<'_> { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn explicit_unwinding_overrides_language_inference() { - let explicit = WalltimeUnwinding::Dwarf { - stack_size: Some(16 * 1024), - }; - for (command, inferred) in [ - ( - "python bench.py", - WalltimeUnwinding::Dwarf { - stack_size: Some(32 * 1024), - }, - ), - ("cargo bench", WalltimeUnwinding::Dwarf { stack_size: None }), - ("go test -bench=.", WalltimeUnwinding::FramePointer), - ] { - assert_eq!(resolve_unwinding(command, None), inferred); - assert_eq!(resolve_unwinding(command, Some(explicit)), explicit); - } - assert_eq!( - resolve_unwinding("python bench.py", Some(WalltimeUnwinding::FramePointer)), - WalltimeUnwinding::FramePointer, - ); - } -} diff --git a/src/executor/wall_time/profiler/samply/mod.rs b/src/executor/wall_time/profiler/samply/mod.rs index 23c2276f6..2eb9b9f76 100644 --- a/src/executor/wall_time/profiler/samply/mod.rs +++ b/src/executor/wall_time/profiler/samply/mod.rs @@ -3,10 +3,16 @@ use crate::cli::InternalCommands; use crate::cli::samply::SamplyArgs; use crate::executor::ExecutorConfig; +#[cfg(target_os = "linux")] +use crate::executor::config::WalltimeUnwinding; use crate::executor::helpers::command::CommandBuilder; +#[cfg(target_os = "linux")] +use crate::executor::helpers::env::suppress_go_perf_unwinding_warning; use crate::executor::helpers::run_with_sudo::wrap_with_sudo; use crate::executor::shared::fifo::FifoBenchmarkData; use crate::executor::wall_time::profiler::Profiler; +#[cfg(target_os = "linux")] +use crate::executor::wall_time::profiler::unwinding::resolve_unwinding; use crate::prelude::*; use crate::system::SystemInfo; use async_trait::async_trait; @@ -102,9 +108,11 @@ impl Profiler for SamplyProfiler { // Unwinding knobs are Linux-only in samply. #[cfg(target_os = "linux")] { - if let Some(unwinding) = config.walltime_unwinding { - args.extend(["--unwinding-mode".into(), unwinding.to_string().into()]); + let unwinding = resolve_unwinding(&config.command, config.walltime_unwinding); + if unwinding == WalltimeUnwinding::FramePointer { + suppress_go_perf_unwinding_warning(); } + args.extend(["--unwinding-mode".into(), unwinding.to_string().into()]); } #[cfg(not(target_os = "linux"))] if config.walltime_unwinding.is_some() { diff --git a/src/executor/wall_time/profiler/unwinding.rs b/src/executor/wall_time/profiler/unwinding.rs new file mode 100644 index 000000000..1bf60d11a --- /dev/null +++ b/src/executor/wall_time/profiler/unwinding.rs @@ -0,0 +1,70 @@ +use crate::executor::config::WalltimeUnwinding; +use crate::executor::helpers::detect_executable::command_has_executable; +use crate::prelude::*; + +pub(super) fn resolve_unwinding( + command: &str, + explicit: Option, +) -> WalltimeUnwinding { + let suggested = suggested_unwinding(command); + let Some(explicit) = explicit else { + return suggested.unwrap_or(WalltimeUnwinding::Dwarf { stack_size: None }); + }; + if let Some(suggested) = suggested + && suggested != explicit + { + warn!( + "Overriding suggested unwinding `{suggested}` with `{explicit}`, call stacks may be incomplete" + ); + } + explicit +} + +/// Unwinding suggested for the benchmarked runtime, if it has a known best setting. +fn suggested_unwinding(command: &str) -> Option { + if command_has_executable( + command, + &["gradle", "gradlew", "java", "maven", "mvn", "mvnw"], + ) { + return Some(WalltimeUnwinding::FramePointer); + } + if command_has_executable(command, &["go"]) { + return Some(WalltimeUnwinding::FramePointer); + } + if command_has_executable(command, &["pytest", "uv", "python", "python3"]) { + return Some(WalltimeUnwinding::Dwarf { + stack_size: Some(32 * 1024), + }); + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn explicit_unwinding_overrides_language_inference() { + let explicit = WalltimeUnwinding::Dwarf { + stack_size: Some(16 * 1024), + }; + for (command, inferred) in [ + ( + "python bench.py", + WalltimeUnwinding::Dwarf { + stack_size: Some(32 * 1024), + }, + ), + ("cargo bench", WalltimeUnwinding::Dwarf { stack_size: None }), + ("go test -bench=.", WalltimeUnwinding::FramePointer), + ("java -jar bench.jar", WalltimeUnwinding::FramePointer), + ] { + assert_eq!(resolve_unwinding(command, None), inferred); + assert_eq!(resolve_unwinding(command, Some(explicit)), explicit); + } + assert_eq!( + resolve_unwinding("python bench.py", Some(WalltimeUnwinding::FramePointer)), + WalltimeUnwinding::FramePointer, + ); + } +}