Skip to content

Commit f2276da

Browse files
committed
feat(walltime): dump kallsyms symbols
1 parent 16b26d3 commit f2276da

2 files changed

Lines changed: 147 additions & 2 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use crate::executor::wall_time::perf::module_symbols::{ModuleSymbols, Symbol};
2+
use crate::prelude::*;
3+
use libc::pid_t;
4+
use std::collections::HashSet;
5+
use std::path::Path;
6+
7+
const KALLSYMS_PATH: &str = "/proc/kallsyms";
8+
9+
pub fn dump_kallsyms_as_perf_map_for_pids(
10+
profile_folder: &Path,
11+
pids: &HashSet<pid_t>,
12+
) -> Result<()> {
13+
let content = std::fs::read_to_string(KALLSYMS_PATH)?;
14+
append_kallsyms_content_to_perf_maps_for_pids(profile_folder, pids, &content)
15+
}
16+
17+
fn append_kallsyms_content_to_perf_maps_for_pids(
18+
profile_folder: &Path,
19+
pids: &HashSet<pid_t>,
20+
content: &str,
21+
) -> Result<()> {
22+
let symbols = parse_kallsyms_content(content);
23+
if symbols.is_empty() {
24+
return Ok(());
25+
}
26+
let symbols = ModuleSymbols::new(symbols);
27+
28+
for pid in pids {
29+
symbols.append_to_file(profile_folder.join(format!("perf-{pid}.map")))?;
30+
}
31+
32+
Ok(())
33+
}
34+
35+
fn parse_kallsyms_content(content: &str) -> Vec<Symbol> {
36+
let mut raw_symbols = content
37+
.lines()
38+
.filter_map(parse_kallsyms_line)
39+
.collect::<Vec<_>>();
40+
raw_symbols.sort_by_key(|(addr, _)| *addr);
41+
42+
let mut symbols = Vec::with_capacity(raw_symbols.len());
43+
for index in 0..raw_symbols.len() {
44+
let (addr, name) = &raw_symbols[index];
45+
46+
let mut next_index = index + 1;
47+
while next_index < raw_symbols.len() && raw_symbols[next_index].0 <= *addr {
48+
next_index += 1;
49+
}
50+
51+
let size = if let Some((next_addr, _)) = raw_symbols.get(next_index) {
52+
next_addr - addr
53+
} else {
54+
const LAST_SYMBOL_SIZE: u64 = 4096;
55+
LAST_SYMBOL_SIZE
56+
};
57+
58+
symbols.push(Symbol {
59+
addr: *addr,
60+
size,
61+
name: name.clone(),
62+
});
63+
}
64+
65+
symbols
66+
}
67+
68+
fn parse_kallsyms_line(line: &str) -> Option<(u64, String)> {
69+
let mut parts = line.split_whitespace();
70+
let addr = u64::from_str_radix(parts.next()?, 16).ok()?;
71+
if addr == 0 {
72+
return None;
73+
}
74+
75+
let _symbol_type = parts.next()?;
76+
let name = parts.next()?.to_string();
77+
Some((addr, name))
78+
}
79+
80+
#[cfg(test)]
81+
mod tests {
82+
use super::*;
83+
84+
#[test]
85+
fn parses_and_sizes_kallsyms_symbols() {
86+
let content = r#"
87+
ffffffff81000000 T _stext
88+
ffffffff81000100 t secondary_startup_64
89+
0000000000000000 t should_be_skipped
90+
ffffffff81000300 T cpu_startup_entry
91+
"#;
92+
93+
let parsed = parse_kallsyms_content(content);
94+
95+
assert_eq!(parsed.len(), 3);
96+
assert_eq!(parsed[0].addr, 0xffffffff81000000);
97+
assert_eq!(parsed[0].size, 0x100);
98+
assert_eq!(parsed[0].name, "_stext");
99+
100+
assert_eq!(parsed[1].addr, 0xffffffff81000100);
101+
assert_eq!(parsed[1].size, 0x200);
102+
assert_eq!(parsed[1].name, "secondary_startup_64");
103+
104+
assert_eq!(parsed[2].addr, 0xffffffff81000300);
105+
assert_eq!(parsed[2].size, 0x1000);
106+
assert_eq!(parsed[2].name, "cpu_startup_entry");
107+
}
108+
109+
#[test]
110+
fn appends_kallsyms_to_each_pid_perf_map() {
111+
let dir = tempfile::tempdir().unwrap();
112+
let profile_folder = dir.path();
113+
114+
let mut pids = HashSet::new();
115+
pids.insert(1234);
116+
pids.insert(5678);
117+
118+
std::fs::write(profile_folder.join("perf-1234.map"), "existing\n").unwrap();
119+
120+
let content = r#"
121+
ffffffff81000000 T _stext
122+
ffffffff81000100 t secondary_startup_64
123+
"#;
124+
125+
append_kallsyms_content_to_perf_maps_for_pids(profile_folder, &pids, content).unwrap();
126+
127+
let first = std::fs::read_to_string(profile_folder.join("perf-1234.map")).unwrap();
128+
assert_eq!(
129+
first,
130+
"existing\nffffffff81000000 100 _stext\nffffffff81000100 1000 secondary_startup_64\n"
131+
);
132+
133+
let second = std::fs::read_to_string(profile_folder.join("perf-5678.map")).unwrap();
134+
assert_eq!(
135+
second,
136+
"ffffffff81000000 100 _stext\nffffffff81000100 1000 secondary_startup_64\n"
137+
);
138+
}
139+
}

src/executor/wall_time/perf/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use std::path::PathBuf;
2929
use std::{cell::OnceCell, process::ExitStatus};
3030

3131
mod jit_dump;
32+
mod kallsyms;
3233
mod naming;
3334
mod parse_perf_file;
3435
mod save_artifacts;
@@ -308,14 +309,19 @@ impl BenchmarkData {
308309

309310
// Harvest the perf maps generated by python. This will copy the perf
310311
// maps from /tmp to the profile folder. We have to write our own perf
311-
// maps to these files AFTERWARDS, otherwise it'll be overwritten!
312-
debug!("Harvesting perf maps and jit dumps for pids: {tracked_pids:?}");
312+
// maps (kallsyms / jit dump) afterwards, otherwise they'll be overwritten!
313+
debug!("Harvesting perf maps for pids: {tracked_pids:?}");
313314
harvest_perf_maps_for_pids(path_ref, &tracked_pids)
314315
.await
315316
.map_err(|e| {
316317
error!("Failed to harvest perf maps: {e}");
317318
BenchmarkDataSaveError::FailedToHarvestPerfMaps
318319
})?;
320+
321+
if let Err(error) = kallsyms::dump_kallsyms_as_perf_map_for_pids(path_ref, &tracked_pids) {
322+
warn!("Failed to dump kernel symbols from /proc/kallsyms: {error}");
323+
}
324+
319325
let jit_unwind_data_by_pid =
320326
jit_dump::save_symbols_and_harvest_unwind_data_for_pids(path_ref, &tracked_pids)
321327
.await

0 commit comments

Comments
 (0)