From c5e121028697d4ff0a4e9d60f3bfbebbb147ad86 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Tue, 21 Mar 2023 18:14:34 +0100 Subject: [PATCH] Move perf-map loading into the Process, keep track of it being loaded there --- samply/src/import/perf.rs | 32 +---- samply/src/linux_shared/mod.rs | 225 ++++++++++++++++++--------------- 2 files changed, 121 insertions(+), 136 deletions(-) diff --git a/samply/src/import/perf.rs b/samply/src/import/perf.rs index f9293ec04..7e74ba3ec 100644 --- a/samply/src/import/perf.rs +++ b/samply/src/import/perf.rs @@ -5,7 +5,7 @@ use linux_perf_data::linux_perf_event_reader; use linux_perf_data::{DsoInfo, DsoKey, PerfFileReader, PerfFileRecord}; use linux_perf_event_reader::EventRecord; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::io::{Read, Seek}; use std::path::{Path, PathBuf}; @@ -51,28 +51,6 @@ pub fn convert(cursor: C, extra_dir: Option<&Path>) -> Result) -> Option { - match r { - EventRecord::Sample(r) => r.pid, - EventRecord::Comm(r) => Some(r.pid), - EventRecord::Exit(r) => Some(r.pid), - EventRecord::Fork(r) => Some(r.pid), - EventRecord::Mmap(r) => Some(r.pid), - EventRecord::Mmap2(r) => Some(r.pid), - EventRecord::ContextSwitch(r) => match r { - linux_perf_event_reader::ContextSwitchRecord::In { prev_pid, .. } => *prev_pid, - linux_perf_event_reader::ContextSwitchRecord::Out { next_pid, .. } => *next_pid, - }, - EventRecord::Lost(_) - | EventRecord::Throttle(_) - | EventRecord::Unthrottle(_) - | EventRecord::Raw(_) => None, - // Necessary because non-exhaustive. - _ => None, - } -} - fn convert_impl( file: PerfFileReader, extra_dir: Option<&Path>, @@ -126,8 +104,6 @@ where interpretation.clone(), ); - let mut loaded_perf_maps = HashSet::new(); - let mut last_timestamp = 0; let mut jitdumps: Vec = Vec::new(); @@ -189,12 +165,6 @@ where } } - if let Some(pid) = event_record_pid(&parsed_record) { - if loaded_perf_maps.insert(pid) { - converter.try_load_perf_map(pid); - } - } - match parsed_record { EventRecord::Sample(e) => { if attr_index == interpretation.main_event_attr_index { diff --git a/samply/src/linux_shared/mod.rs b/samply/src/linux_shared/mod.rs index 89a6fbe62..c32f0d4f2 100644 --- a/samply/src/linux_shared/mod.rs +++ b/samply/src/linux_shared/mod.rs @@ -255,111 +255,6 @@ where } } - fn process_perf_map_line(line: &str) -> Option<(u64, u64, &str)> { - let mut split = line.splitn(3, ' '); - let addr = split.next()?; - let len = split.next()?; - let symbol_name = split.next()?; - if symbol_name.is_empty() { - return None; - } - let addr = u64::from_str_radix(addr.trim_start_matches("0x"), 16).ok()?; - let len = u64::from_str_radix(len.trim_start_matches("0x"), 16).ok()?; - Some((addr, len, symbol_name)) - } - - /// Tries to load a perf mapping file that could have been generated by the process during - /// execution. - pub fn try_load_perf_map(&mut self, pid: i32) { - let name = format!("perf-{pid}.map"); - let path = format!("/tmp/{name}"); - let Ok(content) = fs::read_to_string(&path) else { return }; - - let process = self.processes.get_by_pid(pid, &mut self.profile); - - // Read the map file and set everything up so that absolute addresses - // in JIT code get symbolicated to the right function name. - - // There are three ways to put function names into the profile: - // - // 1. Function name without address ("label frame"), - // 2. Address with after-the-fact symbolicated function name, and - // 3. Address with up-front symbolicated function name. - // - // Having the address on the frame allows the assembly view in the - // Firefox profiler to compute the right hitcount per instruction. - // However, with a perf.map file, we don't the code bytes of the jitted - // code, so we have no way of displaying the instructions. So the code - // address is not overly useful information, and we could just discard - // it and use label frames for perf.map JIT frames (approach 1). - // - // We'll be using approach 3 here anyway, so our JIT frames will have - // both a function name and a code address. - - // Create a fake "library" for the JIT code. - let lib_handle = self.profile.add_lib(LibraryInfo { - debug_name: name.clone(), - name, - debug_path: path.clone(), - path, - debug_id: DebugId::nil(), - code_id: None, - arch: None, - symbol_table: None, - }); - - let mut symbols = Vec::new(); - let mut cumulative_address = 0; - - for line in content.lines() { - let Some((addr, len, symbol_name)) = Self::process_perf_map_line(line) else { continue }; - - let start_address = addr; - let end_address = addr + len; - let (category, js_name) = self - .jit_category_manager - .classify_jit_symbol(symbol_name, &mut self.profile); - - // Add this function to process.jit_functions so that it can be consulted for - // category information and JS function prepending. - process.jit_functions.insert(JitFunction { - start_address, - end_address, - category, - js_name, - }); - - // Pretend that all JIT code is laid out consecutively in our fake library. - // This relative address is used for symbolication whenever we add a frame - // to the profile. - let relative_address = cumulative_address; - cumulative_address += len as u32; - - // Add this function to the "mappings" of the fake library so that the - // profile can translate an absolute frame address to a relative address - // in the fake JIT library. - self.profile.add_lib_mapping( - process.profile_process, - lib_handle, - start_address, - end_address, - relative_address, - ); - - // Add a symbol for this function to the fake library's symbol table. - // This symbol will be looked up when the address is added to the profile, - // based on the relative address. - symbols.push(Symbol { - address: relative_address, - size: Some(len as u32), - name: symbol_name.to_owned(), - }); - } - - self.profile - .set_lib_symbol_table(lib_handle, Arc::new(SymbolTable::new(symbols))); - } - pub fn finish(self) -> Profile { self.profile } @@ -377,6 +272,8 @@ where let is_main = pid == tid; let process = self.processes.get_by_pid(pid, &mut self.profile); + process.maybe_load_perf_map(&mut self.profile, &mut self.jit_category_manager); + let mut stack = Vec::new(); Self::get_sample_stack::(&e, &process.unwinder, &mut self.cache, &mut stack); @@ -683,6 +580,9 @@ where .expect("Can't handle context switch without time"); let is_main = pid == tid; let process = self.processes.get_by_pid(pid, &mut self.profile); + + process.maybe_load_perf_map(&mut self.profile, &mut self.jit_category_manager); + let process_handle = process.profile_process; let thread = self .threads @@ -1479,6 +1379,8 @@ where unwinder: U::default(), jit_functions: JitFunctions(Vec::new()), name: None, + has_looked_up_perf_map: false, + pid: pid as u32, } }) } @@ -1525,6 +1427,119 @@ struct Process { pub unwinder: U, pub jit_functions: JitFunctions, pub name: Option, + pid: u32, + has_looked_up_perf_map: bool, +} + +impl Process { + /// Tries to load a perf mapping file that could have been generated by the process during + /// execution. + fn maybe_load_perf_map( + &mut self, + profile: &mut Profile, + jit_category_manager: &mut JitCategoryManager, + ) { + if self.has_looked_up_perf_map { + return; + } + self.has_looked_up_perf_map = true; + + let name = format!("perf-{}.map", self.pid); + let path = format!("/tmp/{name}"); + let Ok(content) = fs::read_to_string(&path) else { return }; + + // Read the map file and set everything up so that absolute addresses + // in JIT code get symbolicated to the right function name. + + // There are three ways to put function names into the profile: + // + // 1. Function name without address ("label frame"), + // 2. Address with after-the-fact symbolicated function name, and + // 3. Address with up-front symbolicated function name. + // + // Having the address on the frame allows the assembly view in the + // Firefox profiler to compute the right hitcount per instruction. + // However, with a perf.map file, we don't have the code bytes of the jitted + // code, so we have no way of displaying the instructions. So the code + // address is not overly useful information, and we could just discard + // it and use label frames for perf.map JIT frames (approach 1). + // + // We'll be using approach 3 here anyway, so our JIT frames will have + // both a function name and a code address. + + // Create a fake "library" for the JIT code. + let lib_handle = profile.add_lib(LibraryInfo { + debug_name: name.clone(), + name, + debug_path: path.clone(), + path, + debug_id: DebugId::nil(), + code_id: None, + arch: None, + symbol_table: None, + }); + + let mut symbols = Vec::new(); + let mut cumulative_address = 0; + + for (addr, len, symbol_name) in content.lines().filter_map(process_perf_map_line) { + let start_address = addr; + let end_address = addr + len; + + let (category, js_name) = + jit_category_manager.classify_jit_symbol(symbol_name, profile); + + // Add this function to process.jit_functions so that it can be consulted for + // category information and JS function prepending. + self.jit_functions.insert(JitFunction { + start_address, + end_address, + category, + js_name, + }); + + // Pretend that all JIT code is laid out consecutively in our fake library. + // This relative address is used for symbolication whenever we add a frame + // to the profile. + let relative_address = cumulative_address; + cumulative_address += len as u32; + + // Add this function to the "mappings" of the fake library so that the + // profile can translate an absolute frame address to a relative address + // in the fake JIT library. + profile.add_lib_mapping( + self.profile_process, + lib_handle, + start_address, + end_address, + relative_address, + ); + + // Add a symbol for this function to the fake library's symbol table. + // This symbol will be looked up when the address is added to the profile, + // based on the relative address. + symbols.push(Symbol { + address: relative_address, + size: Some(len as u32), + name: symbol_name.to_owned(), + }); + } + + profile.set_lib_symbol_table(lib_handle, Arc::new(SymbolTable::new(symbols))); + } +} + +fn process_perf_map_line(line: &str) -> Option<(u64, u64, &str)> { + let mut split = line.splitn(3, ' '); + let addr = split.next()?; + let len = split.next()?; + let symbol_name = split.next()?; + if symbol_name.is_empty() { + return None; + } + let addr = u64::from_str_radix(addr.trim_start_matches("0x"), 16).ok()?; + let len = u64::from_str_radix(len.trim_start_matches("0x"), 16).ok()?; + Some((addr, len, symbol_name)) } struct JitFunctions(Vec);