Skip to content

Commit 0e6e4ea

Browse files
committed
feat(walltime): add samply profiler for macOS
1 parent 02292c7 commit 0e6e4ea

3 files changed

Lines changed: 106 additions & 0 deletions

File tree

src/executor/wall_time/executor.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::helpers::validate_walltime_results;
22
use super::isolation::wrap_with_isolation;
33
use super::profiler::Profiler;
44
use super::profiler::perf::PerfProfiler;
5+
use super::profiler::samply::SamplyProfiler;
56
use crate::executor::Executor;
67
use crate::executor::ExecutorConfig;
78
use crate::executor::ToolStatus;
@@ -84,6 +85,8 @@ impl WallTimeExecutor {
8485
pub fn new() -> Self {
8586
let profiler: Option<Box<dyn Profiler>> = if cfg!(target_os = "linux") {
8687
Some(Box::new(PerfProfiler::new()))
88+
} else if cfg!(target_os = "macos") {
89+
Some(Box::new(SamplyProfiler::new()))
8790
} else {
8891
None
8992
};

src/executor/wall_time/profiler/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! in the profile folder.
66
77
pub mod perf;
8+
pub mod samply;
89

910
const WALLTIME_METADATA_CURRENT_VERSION: u64 = 1;
1011

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! Samply profiler integration.
2+
3+
use crate::executor::ExecutorConfig;
4+
use crate::executor::helpers::command::CommandBuilder;
5+
use crate::executor::shared::fifo::FifoBenchmarkData;
6+
use crate::executor::wall_time::profiler::Profiler;
7+
use crate::prelude::*;
8+
use async_trait::async_trait;
9+
use runner_shared::artifacts::ArtifactExt;
10+
use runner_shared::artifacts::ExecutionTimestamps;
11+
use runner_shared::metadata::WalltimeMetadata;
12+
use std::path::Path;
13+
use std::path::PathBuf;
14+
15+
use super::WALLTIME_METADATA_CURRENT_VERSION;
16+
17+
const SAMPLY_OUTPUT_FILE_NAME: &str = "samply-profile.json.gz";
18+
const SAMPLY_RATE_HZ: &str = "997";
19+
20+
pub struct SamplyProfiler {
21+
/// Set by [`Profiler::wrap`]. Currently unused after `wrap` returns —
22+
/// samply writes the file itself — but we hold onto it so future
23+
/// `finalize` work (e.g. validation, conversion) has the path on hand.
24+
output_path: Option<PathBuf>,
25+
}
26+
27+
impl SamplyProfiler {
28+
pub fn new() -> Self {
29+
Self { output_path: None }
30+
}
31+
}
32+
33+
#[async_trait(?Send)]
34+
impl Profiler for SamplyProfiler {
35+
async fn wrap(
36+
&mut self,
37+
mut cmd_builder: CommandBuilder,
38+
_config: &ExecutorConfig,
39+
profile_folder: &Path,
40+
) -> anyhow::Result<CommandBuilder> {
41+
let output_path = profile_folder.join(SAMPLY_OUTPUT_FILE_NAME);
42+
43+
let mut samply_builder = CommandBuilder::new("samply");
44+
samply_builder.args([
45+
"record",
46+
"--reuse-threads",
47+
"--unstable-presymbolicate",
48+
"--no-open",
49+
"--save-only",
50+
"--rate",
51+
SAMPLY_RATE_HZ,
52+
]);
53+
samply_builder.arg("-o");
54+
samply_builder.arg(&output_path);
55+
samply_builder.arg("--");
56+
57+
cmd_builder.wrap_with(samply_builder);
58+
self.output_path = Some(output_path);
59+
Ok(cmd_builder)
60+
}
61+
62+
async fn finalize(
63+
&mut self,
64+
fifo_data: FifoBenchmarkData,
65+
timestamps: ExecutionTimestamps,
66+
profile_folder: &Path,
67+
) -> anyhow::Result<()> {
68+
let Some(integration) = fifo_data.integration.clone() else {
69+
warn!(
70+
"Walltime profiling is enabled, but failed to detect benchmarks. If you wish to disable this warning, set CODSPEED_PROFILER_ENABLED=false"
71+
);
72+
return Ok(());
73+
};
74+
75+
#[allow(deprecated)]
76+
let metadata = WalltimeMetadata {
77+
version: WALLTIME_METADATA_CURRENT_VERSION,
78+
integration,
79+
uri_by_ts: timestamps.uri_by_ts.clone(),
80+
markers: timestamps.markers.clone(),
81+
82+
// These fields aren't required in samply, since we symbolicate client-side.
83+
ignored_modules_by_pid: Default::default(),
84+
debug_info: Default::default(),
85+
mapped_process_debug_info_by_pid: Default::default(),
86+
mapped_process_unwind_data_by_pid: Default::default(),
87+
mapped_process_module_symbols: Default::default(),
88+
path_key_to_path: Default::default(),
89+
90+
// Deprecated fields below are no longer used
91+
debug_info_by_pid: Default::default(),
92+
ignored_modules: Default::default(),
93+
};
94+
metadata.save_to(profile_folder).unwrap();
95+
96+
if let Err(e) = timestamps.save_to(profile_folder) {
97+
warn!("Failed to save execution timestamps: {e:?}");
98+
}
99+
100+
Ok(())
101+
}
102+
}

0 commit comments

Comments
 (0)