diff --git a/src/executor/helpers/env.rs b/src/executor/helpers/env.rs index 07283b43..c8bfcd07 100644 --- a/src/executor/helpers/env.rs +++ b/src/executor/helpers/env.rs @@ -41,6 +41,18 @@ pub fn get_base_injected_env( ), ]); + // Java: Enable frame pointers and perf map generation for flamegraph profiling. + // - UnlockDiagnosticVMOptions must come before DumpPerfMapAtExit (diagnostic option). + // - PreserveFramePointer: Preserves frame pointers for profiling. + // - DumpPerfMapAtExit: Writes /tmp/perf-.map on JVM exit for symbol resolution. + // - DebugNonSafepoints: Enables debug info for JIT-compiled non-safepoint code. + if mode == RunnerMode::Walltime { + env.insert( + "JAVA_TOOL_OPTIONS", + "-XX:+PreserveFramePointer -XX:+UnlockDiagnosticVMOptions -XX:+DumpPerfMapAtExit -XX:+DebugNonSafepoints".into(), + ); + } + if let Some(version) = &config.go_runner_version { env.insert("CODSPEED_GO_RUNNER_VERSION", version.to_string()); } diff --git a/src/executor/helpers/run_with_env.rs b/src/executor/helpers/run_with_env.rs index a1b1aa61..5bfd7f5a 100644 --- a/src/executor/helpers/run_with_env.rs +++ b/src/executor/helpers/run_with_env.rs @@ -54,7 +54,7 @@ fn create_env_file(extra_env: &HashMap<&'static str, String>) -> Result>() .join("\n"); diff --git a/src/executor/wall_time/perf/mod.rs b/src/executor/wall_time/perf/mod.rs index bfbfc9aa..a0aedbf8 100644 --- a/src/executor/wall_time/perf/mod.rs +++ b/src/executor/wall_time/perf/mod.rs @@ -96,6 +96,12 @@ impl PerfRunner { // Infer the unwinding mode from the benchmark cmd let (cg_mode, stack_size) = if let Some(mode) = config.perf_unwinding_mode { (mode, None) + } else if config.command.contains("gradle") + || config.command.contains("java") + || config.command.contains("maven") + { + // In Java, we must use FP unwinding otherwise we'll have broken call stacks. + (UnwindingMode::FramePointer, None) } else if config.command.contains("cargo") { (UnwindingMode::Dwarf, None) } else if config.command.contains("pytest") @@ -216,6 +222,16 @@ impl PerfRunner { let on_cmd = async |cmd: &FifoCommand| { #[allow(deprecated)] match cmd { + // Print /proc/{pid}/maps for the benchmark process. This helps with debugging missing symbols, as + // it allows finding the module in which an address is located. + FifoCommand::CurrentBenchmark { pid, .. } if is_codspeed_debug_enabled() => { + let maps_path = format!("/proc/{pid}/maps"); + match std::fs::read_to_string(&maps_path) { + Ok(maps) => debug!("/proc/{pid}/maps:\n{maps}"), + Err(e) => debug!("Failed to read {maps_path}: {e}"), + } + return Ok(None); + } FifoCommand::StartBenchmark => { perf_fifo.start_events().await?; }