Skip to content

Commit 03cfd8a

Browse files
committed
feat: run python with perf jit dump
1 parent c83c0d9 commit 03cfd8a

File tree

4 files changed

+116
-3
lines changed

4 files changed

+116
-3
lines changed

src/run/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ pub struct RunArgs {
133133
pub command: Vec<String>,
134134
}
135135

136-
#[derive(ValueEnum, Clone, Debug, Serialize)]
136+
#[derive(ValueEnum, Clone, Debug, Serialize, PartialEq)]
137137
#[serde(rename_all = "lowercase")]
138138
pub enum RunnerMode {
139139
Instrumentation,

src/run/runner/helpers/env.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ pub fn get_base_injected_env(
88
) -> HashMap<&'static str, String> {
99
HashMap::from([
1010
("PYTHONHASHSEED", "0".into()),
11+
(
12+
"PYTHON_PERF_JIT_SUPPORT",
13+
if mode == RunnerMode::Walltime {
14+
"1".into()
15+
} else {
16+
"0".into()
17+
},
18+
),
1119
("ARCH", ARCH.into()),
1220
("CODSPEED_ENV", "runner".into()),
1321
("CODSPEED_RUNNER_MODE", mode.to_string()),
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use crate::{prelude::*, run::runner::wall_time::perf::unwind_data::UnwindData};
2+
use linux_perf_data::jitdump::{JitDumpReader, JitDumpRecord};
3+
use std::{
4+
collections::HashSet,
5+
path::{Path, PathBuf},
6+
};
7+
8+
struct JitDump {
9+
path: PathBuf,
10+
}
11+
12+
impl JitDump {
13+
pub fn new(path: PathBuf) -> Self {
14+
Self { path }
15+
}
16+
17+
/// Parses the JIT dump file and converts it into a list of `UnwindData`.
18+
///
19+
/// The JIT dump file contains synthetic `eh_frame` data for jitted functions. This can be parsed and
20+
/// then converted to `UnwindData` which is used for stack unwinding.
21+
///
22+
/// See: https://github.com/python/cpython/blob/main/Python/perf_jit_trampoline.c
23+
pub fn into_unwind_data(self) -> Result<Vec<UnwindData>> {
24+
let file = std::fs::File::open(self.path)?;
25+
26+
let mut jit_unwind_data = Vec::new();
27+
let mut current_unwind_info: Option<(Vec<u8>, Vec<u8>)> = None;
28+
29+
let mut reader = JitDumpReader::new(file)?;
30+
while let Some(raw_record) = reader.next_record()? {
31+
// The first recording is always the unwind info, followed by the code load event
32+
// (see `perf_map_jit_write_entry` in https://github.com/python/cpython/blob/9743d069bd53e9d3a8f09df899ec1c906a79da24/Python/perf_jit_trampoline.c#L1163C13-L1163C37)
33+
match raw_record.parse()? {
34+
JitDumpRecord::CodeLoad(record) => {
35+
let name = record.function_name.as_slice();
36+
let name = String::from_utf8_lossy(&name);
37+
38+
let avma_start = record.vma;
39+
let code_size = record.code_bytes.len() as u64;
40+
let avma_end = avma_start + code_size;
41+
42+
let Some((eh_frame, eh_frame_hdr)) = current_unwind_info.take() else {
43+
warn!("No unwind info available for JIT code load: {name}");
44+
continue;
45+
};
46+
47+
jit_unwind_data.push(UnwindData {
48+
path: format!("jit_{name}"),
49+
avma_range: avma_start..avma_end,
50+
base_avma: avma_start,
51+
eh_frame_hdr,
52+
eh_frame_hdr_svma: 0..0, // TODO: Do we need this?
53+
eh_frame,
54+
eh_frame_svma: 0..0, // TODO: Do we need this?
55+
});
56+
}
57+
JitDumpRecord::CodeUnwindingInfo(record) => {
58+
// Store unwind info for the next code loads
59+
current_unwind_info = Some((
60+
record.eh_frame.as_slice().to_vec(),
61+
record.eh_frame_hdr.as_slice().to_vec(),
62+
));
63+
}
64+
JitDumpRecord::CodeMove(_) => {
65+
panic!("CodeMove not implemented yet");
66+
}
67+
_ => {
68+
warn!("Unhandled JIT dump record: {raw_record:?}");
69+
}
70+
}
71+
}
72+
73+
Ok(jit_unwind_data)
74+
}
75+
}
76+
77+
/// Converts all the `jit-<pid>.dump` into unwind data and copies it to the profile folder.
78+
pub async fn harvest_perf_jit_for_pids(profile_folder: &Path, pids: &HashSet<i32>) -> Result<()> {
79+
for pid in pids {
80+
let name = format!("jit-{pid}.dump");
81+
let path = PathBuf::from("/tmp").join(&name);
82+
83+
if !path.exists() {
84+
continue;
85+
}
86+
debug!("Found JIT dump file: {path:?}");
87+
88+
let unwind_data = match JitDump::new(path).into_unwind_data() {
89+
Ok(unwind_data) => unwind_data,
90+
Err(error) => {
91+
warn!("Failed to convert jit dump into unwind data: {error:?}");
92+
continue;
93+
}
94+
};
95+
96+
for module in unwind_data {
97+
module.save_to(profile_folder, *pid as _)?;
98+
}
99+
}
100+
101+
Ok(())
102+
}

src/run/runner/wall_time/perf/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log
88
use crate::run::runner::helpers::setup::run_with_sudo;
99
use crate::run::runner::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore;
1010
use crate::run::runner::valgrind::helpers::perf_maps::harvest_perf_maps_for_pids;
11+
use crate::run::runner::wall_time::perf::jit_dump::harvest_perf_jit_for_pids;
1112
use anyhow::Context;
1213
use fifo::{PerfFifo, RunnerFifo};
1314
use futures::stream::FuturesUnordered;
@@ -22,6 +23,7 @@ use std::{cell::OnceCell, collections::HashMap, process::ExitStatus};
2223
use tempfile::TempDir;
2324
use unwind_data::UnwindData;
2425

26+
mod jit_dump;
2527
mod metadata;
2628
mod setup;
2729
mod shared;
@@ -195,15 +197,16 @@ impl PerfRunner {
195197
// Harvest the perf maps generated by python. This will copy the perf
196198
// maps from /tmp to the profile folder. We have to write our own perf
197199
// maps to these files AFTERWARDS, otherwise it'll be overwritten!
198-
let perf_map_pids = futures::future::try_join_all(copy_tasks)
200+
let bench_pids = futures::future::try_join_all(copy_tasks)
199201
.await?
200202
.into_iter()
201203
.filter_map(|result| {
202204
debug!("Copy task result: {result:?}");
203205
result.ok()
204206
})
205207
.collect::<HashSet<_>>();
206-
harvest_perf_maps_for_pids(profile_folder, &perf_map_pids).await?;
208+
harvest_perf_maps_for_pids(profile_folder, &bench_pids).await?;
209+
harvest_perf_jit_for_pids(profile_folder, &bench_pids).await?;
207210

208211
// Append perf maps, unwind info and other metadata
209212
if let Err(BenchmarkDataSaveError::MissingIntegration) = bench_data.save_to(profile_folder)

0 commit comments

Comments
 (0)