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
2 changes: 1 addition & 1 deletion src/cli/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/executor/wall_time/profiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

pub mod perf;
pub mod samply;
mod unwinding;

const WALLTIME_METADATA_CURRENT_VERSION: u64 = 1;

Expand Down
65 changes: 1 addition & 64 deletions src/executor/wall_time/profiler/perf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<WalltimeUnwinding> {
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>) -> 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.
Expand Down Expand Up @@ -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,
);
}
}
12 changes: 8 additions & 4 deletions src/executor/wall_time/profiler/samply/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ 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;
Expand Down Expand Up @@ -104,17 +108,17 @@ impl Profiler for SamplyProfiler {
// Unwinding knobs are Linux-only in samply.
#[cfg(target_os = "linux")]
{
match config.walltime_unwinding {
Some(WalltimeUnwinding::FramePointer) => {
match resolve_unwinding(&config.command, config.walltime_unwinding) {
WalltimeUnwinding::FramePointer => {
suppress_go_perf_unwinding_warning();
args.extend(["--unwinding-mode".into(), "fp".into()]);
}
Some(WalltimeUnwinding::Dwarf { stack_size }) => {
WalltimeUnwinding::Dwarf { stack_size } => {
args.extend(["--unwinding-mode".into(), "dwarf".into()]);
if let Some(size) = stack_size {
args.extend(["--unwinding-stack-size".into(), size.to_string().into()]);
}
}
None => {}
}
}
#[cfg(not(target_os = "linux"))]
Expand Down
70 changes: 70 additions & 0 deletions src/executor/wall_time/profiler/unwinding.rs
Original file line number Diff line number Diff line change
@@ -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>,
) -> 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<WalltimeUnwinding> {
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,
);
}
}