Skip to content

Commit 261327a

Browse files
committed
feat: add perf metadata; add python support
1 parent 4bac139 commit 261327a

5 files changed

Lines changed: 127 additions & 21 deletions

File tree

src/run/runner/valgrind/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
pub mod executor;
2-
mod helpers;
2+
pub mod helpers;
33
mod measure;
44
mod setup;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// !!!!!!!!!!!!!!!!!!!!!!!!
2+
// !! DO NOT TOUCH BELOW !!
3+
// !!!!!!!!!!!!!!!!!!!!!!!!
4+
// Has to be in sync with `perf-parser`.
5+
//
6+
7+
use std::{collections::HashMap, path::Path};
8+
9+
use serde::{Deserialize, Serialize};
10+
11+
#[derive(Serialize, Deserialize)]
12+
pub struct PerfMetadata {
13+
/// Name and version of the integration
14+
pub integration: (String, String),
15+
16+
/// The URIs of the benchmarks in the order they were executed.
17+
pub bench_order_by_pid: HashMap<u32, Vec<String>>,
18+
19+
/// Modules that should be ignored and removed from the folded trace and callgraph (e.g. python interpreter)
20+
pub ignored_modules: Vec<(String, u64, u64)>,
21+
}
22+
23+
impl PerfMetadata {
24+
pub fn save_to<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
25+
let file = std::fs::File::create(path.as_ref().join("perf.metadata"))?;
26+
serde_json::to_writer(file, self)?;
27+
Ok(())
28+
}
29+
}

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

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
use crate::prelude::*;
22
use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe_and_callback;
33
use crate::run::runner::helpers::setup::run_with_sudo;
4+
use crate::run::runner::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore;
5+
use crate::run::runner::valgrind::helpers::perf_maps::harvest_perf_maps_for_pids;
46
use anyhow::Context;
57
use fifo::{PerfFifo, RunnerFifo};
68
use futures::stream::FuturesUnordered;
9+
use metadata::PerfMetadata;
710
use perf_map::ProcessSymbols;
811
use procfs::process::MMPermissions;
912
use shared::Command as FifoCommand;
13+
use std::collections::HashSet;
1014
use std::path::PathBuf;
1115
use std::process::Command;
1216
use std::time::Duration;
1317
use std::{cell::OnceCell, collections::HashMap, process::ExitStatus};
1418
use tempfile::TempDir;
1519
use unwind_data::UnwindData;
1620

21+
mod metadata;
1722
mod shared;
1823
pub use shared::*;
1924

@@ -74,10 +79,21 @@ impl PerfRunner {
7479
.prefix(PERF_DATA_PREFIX)
7580
.tempfile_in(&self.perf_dir)?;
7681

82+
// Detect the mode based on the command to be executed
83+
let cg_mode = if bench_cmd.contains("cargo") {
84+
"dwarf"
85+
} else if bench_cmd.contains("pytest") {
86+
"fp"
87+
} else {
88+
warn!("Couldn't detect call graph mode for command: {}", bench_cmd);
89+
"dwarf"
90+
};
91+
debug!("Using call graph mode: {}", cg_mode);
92+
7793
cmd.args([
7894
"-c",
7995
&format!(
80-
"perf record --quiet --user-callchains --freq=999 --switch-output --control=fifo:{},{} --delay=-1 -g --call-graph=dwarf --output={} -- {bench_cmd}",
96+
"perf record --quiet --user-callchains --freq=999 --switch-output --control=fifo:{},{} --delay=-1 -g --call-graph={cg_mode} --output={} -- {bench_cmd}",
8197
perf_fifo.ctl_fifo_path.to_string_lossy(),
8298
perf_fifo.ack_fifo_path.to_string_lossy(),
8399
perf_file.path().to_string_lossy()
@@ -109,12 +125,6 @@ impl PerfRunner {
109125
pub async fn save_files_to(&self, profile_folder: &PathBuf) -> anyhow::Result<()> {
110126
let start = std::time::Instant::now();
111127

112-
let bench_data = self
113-
.benchmark_data
114-
.get()
115-
.expect("Benchmark order is not available");
116-
bench_data.save_to(profile_folder).unwrap();
117-
118128
// Copy the perf data files to the profile folder
119129
let copy_tasks = std::fs::read_dir(&self.perf_dir)?
120130
.filter_map(|entry| entry.ok())
@@ -141,16 +151,33 @@ impl PerfRunner {
141151
let dst_path = profile_folder.join(dst_file_name);
142152
tokio::fs::copy(src_path, dst_path).await?;
143153

144-
Ok::<_, anyhow::Error>(())
154+
Ok::<_, anyhow::Error>(pid)
145155
})
146156
})
147157
.collect::<FuturesUnordered<_>>();
158+
159+
let bench_data = self
160+
.benchmark_data
161+
.get()
162+
.expect("Benchmark order is not available");
148163
assert_eq!(
149164
copy_tasks.len(),
150165
bench_data.bench_count(),
151166
"Benchmark count mismatch"
152167
);
153-
futures::future::try_join_all(copy_tasks).await?;
168+
169+
// Harvest the perf maps generated by python. This will copy the perf
170+
// maps from /tmp to the profile folder. We have to write our own perf
171+
// maps to these files AFTERWARDS, otherwise it'll be overwritten!
172+
let perf_map_pids = futures::future::try_join_all(copy_tasks)
173+
.await?
174+
.into_iter()
175+
.filter_map(Result::ok)
176+
.collect::<HashSet<_>>();
177+
harvest_perf_maps_for_pids(profile_folder, &perf_map_pids).await?;
178+
179+
// Append perf maps, unwind info and other metadata
180+
bench_data.save_to(profile_folder).unwrap();
154181

155182
let elapsed = start.elapsed();
156183
debug!("Perf teardown took: {:?}", elapsed);
@@ -165,6 +192,7 @@ impl PerfRunner {
165192
let mut bench_order_by_pid = HashMap::<u32, Vec<String>>::new();
166193
let mut symbols_by_pid = HashMap::<u32, ProcessSymbols>::new();
167194
let mut unwind_data_by_pid = HashMap::<u32, Vec<UnwindData>>::new();
195+
let mut integration = None;
168196

169197
loop {
170198
let perf_ping = tokio::time::timeout(Duration::from_secs(1), perf_fifo.ping()).await;
@@ -230,11 +258,24 @@ impl PerfRunner {
230258
perf_fifo.stop_events().await?;
231259
runner_fifo.send_cmd(FifoCommand::Ack).await?;
232260
}
261+
FifoCommand::PingPerf => {
262+
if perf_fifo.ping().await.is_ok() {
263+
runner_fifo.send_cmd(FifoCommand::Ack).await?;
264+
} else {
265+
runner_fifo.send_cmd(FifoCommand::Err).await?;
266+
}
267+
}
268+
FifoCommand::SetIntegration { name, version } => {
269+
integration = Some((name, version));
270+
runner_fifo.send_cmd(FifoCommand::Ack).await?;
271+
}
233272
FifoCommand::Ack => unreachable!(),
273+
FifoCommand::Err => unreachable!(),
234274
}
235275
}
236276

237277
Ok(BenchmarkData {
278+
integration: integration.context("Couldn't find integration metadata")?,
238279
bench_order_by_pid,
239280
symbols_by_pid,
240281
unwind_data_by_pid,
@@ -243,6 +284,9 @@ impl PerfRunner {
243284
}
244285

245286
pub struct BenchmarkData {
287+
/// Name and version of the integration
288+
integration: (String, String),
289+
246290
bench_order_by_pid: HashMap<u32, Vec<String>>,
247291
symbols_by_pid: HashMap<u32, ProcessSymbols>,
248292
unwind_data_by_pid: HashMap<u32, Vec<UnwindData>>,
@@ -260,11 +304,32 @@ impl BenchmarkData {
260304
}
261305
}
262306

263-
for (pid, orders) in &self.bench_order_by_pid {
264-
let dst_file_name = format!("{}_.bench_order", pid);
265-
let dst_path = path.as_ref().join(dst_file_name);
266-
std::fs::write(dst_path, orders.join("\n"))?;
267-
}
307+
let metadata = PerfMetadata {
308+
integration: self.integration.clone(),
309+
bench_order_by_pid: self.bench_order_by_pid.clone(),
310+
ignored_modules: {
311+
let mut to_ignore = vec![];
312+
313+
// Check if any of the ignored modules has been loaded in the process
314+
for ignore_path in get_objects_path_to_ignore() {
315+
for proc in self.symbols_by_pid.values() {
316+
if let Some(mapping) = proc.module_mapping(&ignore_path) {
317+
let (Some((base_addr, _)), Some((_, end_addr))) = (
318+
mapping.iter().min_by_key(|(base_addr, _)| base_addr),
319+
mapping.iter().max_by_key(|(_, end_addr)| end_addr),
320+
) else {
321+
continue;
322+
};
323+
324+
to_ignore.push((ignore_path.clone(), *base_addr, *end_addr));
325+
}
326+
}
327+
}
328+
329+
to_ignore
330+
},
331+
};
332+
metadata.save_to(&path).unwrap();
268333

269334
Ok(())
270335
}

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,17 @@ impl ModuleSymbols {
7979

8080
/// Represents all the modules inside a process and their symbols.
8181
pub struct ProcessSymbols {
82-
modules_bounds: HashMap<PathBuf, Vec<(u64, u64)>>,
83-
modules: HashMap<PathBuf, ModuleSymbols>,
8482
pid: u32,
83+
module_mappings: HashMap<PathBuf, Vec<(u64, u64)>>,
84+
modules: HashMap<PathBuf, ModuleSymbols>,
8585
}
8686

8787
impl ProcessSymbols {
8888
pub fn new(pid: u32) -> Self {
8989
Self {
90-
modules_bounds: HashMap::new(),
91-
modules: HashMap::new(),
9290
pid,
91+
module_mappings: HashMap::new(),
92+
modules: HashMap::new(),
9393
}
9494
}
9595

@@ -119,12 +119,21 @@ impl ProcessSymbols {
119119
}
120120
}
121121

122-
self.modules_bounds
122+
self.module_mappings
123123
.entry(path.clone())
124124
.or_default()
125125
.push((start_addr, end_addr));
126126
}
127127

128+
pub fn module_mapping<P: AsRef<std::path::Path>>(
129+
&self,
130+
module_path: P,
131+
) -> Option<&[(u64, u64)]> {
132+
self.module_mappings
133+
.get(module_path.as_ref())
134+
.map(|bounds| bounds.as_slice())
135+
}
136+
128137
pub fn save_to<P: AsRef<std::path::Path>>(&self, folder: P) -> anyhow::Result<()> {
129138
if self.modules.is_empty() {
130139
return Ok(());
@@ -133,7 +142,7 @@ impl ProcessSymbols {
133142
let symbols_path = folder.as_ref().join(format!("perf-{}.map", self.pid));
134143
for module in self.modules.values() {
135144
let Some((base_addr, _)) = self
136-
.modules_bounds
145+
.module_mappings
137146
.get(&module.path)
138147
.and_then(|bounds| bounds.iter().min_by_key(|(start, _)| start))
139148
else {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ pub enum Command {
1212
StartBenchmark,
1313
StopBenchmark,
1414
Ack,
15+
PingPerf,
16+
SetIntegration { name: String, version: String },
17+
Err,
1518
}
1619
//
1720
// !!!!!!!!!!!!!!!!!!!!!!!!

0 commit comments

Comments
 (0)