perf: Create a per-process JIT dump file (#6024)

This commit is contained in:
Benjamin Bouvier
2023-03-15 15:04:15 +01:00
committed by GitHub
parent 68b937d965
commit 2e6c7bf994

View File

@@ -24,26 +24,22 @@ use object::elf;
/// Interface for driving the creation of jitdump files /// Interface for driving the creation of jitdump files
pub struct JitDumpAgent { pub struct JitDumpAgent {
// Note that we use a mutex internally to serialize writing out to our
// `jitdump_file` within this process, since multiple threads may be sharing
// this jit agent.
state: Mutex<State>,
}
struct State {
jitdump_file: JitDumpFile,
/// Flag for experimenting with dumping code load record /// Flag for experimenting with dumping code load record
/// after each function (true) or after each module. This /// after each function (true) or after each module. This
/// flag is currently set to true. /// flag is currently set to true.
dump_funcs: bool, dump_funcs: bool,
} }
impl JitDumpAgent { /// Process-wide JIT dump file. Perf only accepts a unique file per process, in the injection step.
/// Intialize a JitDumpAgent and write out the header static JITDUMP_FILE: Mutex<Option<JitDumpFile>> = Mutex::new(None);
pub fn new() -> Result<Self> {
let filename = format!("./jit-{}.dump", process::id());
impl JitDumpAgent {
/// Intialize a JitDumpAgent and write out the header.
pub fn new() -> Result<Self> {
let mut jitdump_file = JITDUMP_FILE.lock().unwrap();
if jitdump_file.is_none() {
let filename = format!("./jit-{}.dump", process::id());
let e_machine = match target_lexicon::HOST.architecture { let e_machine = match target_lexicon::HOST.architecture {
Architecture::X86_64 => elf::EM_X86_64 as u32, Architecture::X86_64 => elf::EM_X86_64 as u32,
Architecture::X86_32(_) => elf::EM_386 as u32, Architecture::X86_32(_) => elf::EM_386 as u32,
@@ -52,33 +48,19 @@ impl JitDumpAgent {
Architecture::S390x => elf::EM_S390 as u32, Architecture::S390x => elf::EM_S390 as u32,
_ => unimplemented!("unrecognized architecture"), _ => unimplemented!("unrecognized architecture"),
}; };
*jitdump_file = Some(JitDumpFile::new(filename, e_machine)?);
}
let jitdump_file = JitDumpFile::new(filename, e_machine)?; Ok(JitDumpAgent { dump_funcs: true })
Ok(JitDumpAgent {
state: Mutex::new(State {
jitdump_file,
dump_funcs: true,
}),
})
} }
} }
impl ProfilingAgent for JitDumpAgent { impl ProfilingAgent for JitDumpAgent {
fn module_load(&self, module: &CompiledModule, dbg_image: Option<&[u8]>) {
self.state.lock().unwrap().module_load(module, dbg_image);
}
fn load_single_trampoline(&self, name: &str, addr: *const u8, size: usize, pid: u32, tid: u32) {
self.state
.lock()
.unwrap()
.load_single_trampoline(name, addr, size, pid, tid);
}
}
impl State {
/// Sent when a method is compiled and loaded into memory by the VM. /// Sent when a method is compiled and loaded into memory by the VM.
pub fn module_load(&mut self, module: &CompiledModule, dbg_image: Option<&[u8]>) { fn module_load(&self, module: &CompiledModule, dbg_image: Option<&[u8]>) {
let mut jitdump_file = JITDUMP_FILE.lock().unwrap();
let jitdump_file = jitdump_file.as_mut().unwrap();
let pid = process::id(); let pid = process::id();
let tid = pid; // ThreadId does appear to track underlying thread. Using PID. let tid = pid; // ThreadId does appear to track underlying thread. Using PID.
@@ -86,18 +68,19 @@ impl State {
let addr = func.as_ptr(); let addr = func.as_ptr();
let len = func.len(); let len = func.len();
if let Some(img) = &dbg_image { if let Some(img) = &dbg_image {
if let Err(err) = self.dump_from_debug_image(img, "wasm", addr, len, pid, tid) { if let Err(err) =
self.dump_from_debug_image(jitdump_file, img, "wasm", addr, len, pid, tid)
{
println!( println!(
"Jitdump: module_load failed dumping from debug image: {:?}\n", "Jitdump: module_load failed dumping from debug image: {:?}\n",
err err
); );
} }
} else { } else {
let timestamp = self.jitdump_file.get_time_stamp(); let timestamp = jitdump_file.get_time_stamp();
let name = super::debug_name(module, idx); let name = super::debug_name(module, idx);
if let Err(err) = self if let Err(err) =
.jitdump_file jitdump_file.dump_code_load_record(&name, addr, len, timestamp, pid, tid)
.dump_code_load_record(&name, addr, len, timestamp, pid, tid)
{ {
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
} }
@@ -107,38 +90,34 @@ impl State {
// Note: these are the trampolines into exported functions. // Note: these are the trampolines into exported functions.
for (idx, func, len) in module.trampolines() { for (idx, func, len) in module.trampolines() {
let (addr, len) = (func as usize as *const u8, len); let (addr, len) = (func as usize as *const u8, len);
let timestamp = self.jitdump_file.get_time_stamp(); let timestamp = jitdump_file.get_time_stamp();
let name = format!("wasm::trampoline[{}]", idx.index()); let name = format!("wasm::trampoline[{}]", idx.index());
if let Err(err) = self if let Err(err) =
.jitdump_file jitdump_file.dump_code_load_record(&name, addr, len, timestamp, pid, tid)
.dump_code_load_record(&name, addr, len, timestamp, pid, tid)
{ {
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
} }
} }
} }
fn load_single_trampoline( fn load_single_trampoline(&self, name: &str, addr: *const u8, size: usize, pid: u32, tid: u32) {
&mut self, let mut jitdump_file = JITDUMP_FILE.lock().unwrap();
name: &str, let jitdump_file = jitdump_file.as_mut().unwrap();
addr: *const u8,
size: usize, let timestamp = jitdump_file.get_time_stamp();
pid: u32, if let Err(err) = jitdump_file.dump_code_load_record(&name, addr, size, timestamp, pid, tid)
tid: u32,
) {
let timestamp = self.jitdump_file.get_time_stamp();
if let Err(err) = self
.jitdump_file
.dump_code_load_record(&name, addr, size, timestamp, pid, tid)
{ {
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
} }
} }
}
impl JitDumpAgent {
/// Attempts to dump debuginfo data structures, adding method and line level /// Attempts to dump debuginfo data structures, adding method and line level
/// for the jitted function. /// for the jitted function.
pub fn dump_from_debug_image( pub fn dump_from_debug_image(
&mut self, &self,
jitdump_file: &mut JitDumpFile,
dbg_image: &[u8], dbg_image: &[u8],
module_name: &str, module_name: &str,
addr: *const u8, addr: *const u8,
@@ -178,16 +157,15 @@ impl State {
return Ok(()); return Ok(());
} }
}; };
self.dump_entries(unit, &dwarf, module_name, addr, len, pid, tid)?; self.dump_entries(jitdump_file, unit, &dwarf, module_name, addr, len, pid, tid)?;
// TODO: Temp exit to avoid duplicate addresses being covered by only // TODO: Temp exit to avoid duplicate addresses being covered by only
// processing the top unit // processing the top unit
break; break;
} }
if !self.dump_funcs { if !self.dump_funcs {
let timestamp = self.jitdump_file.get_time_stamp(); let timestamp = jitdump_file.get_time_stamp();
if let Err(err) = if let Err(err) =
self.jitdump_file jitdump_file.dump_code_load_record(module_name, addr, len, timestamp, pid, tid)
.dump_code_load_record(module_name, addr, len, timestamp, pid, tid)
{ {
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err); println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
} }
@@ -196,7 +174,8 @@ impl State {
} }
fn dump_entries<R: Reader>( fn dump_entries<R: Reader>(
&mut self, &self,
jitdump_file: &mut JitDumpFile,
unit: gimli::Unit<R>, unit: gimli::Unit<R>,
dwarf: &gimli::Dwarf<R>, dwarf: &gimli::Dwarf<R>,
module_name: &str, module_name: &str,
@@ -285,17 +264,15 @@ impl State {
clr.header.record_size = mem::size_of::<CodeLoadRecord>() as u32 clr.header.record_size = mem::size_of::<CodeLoadRecord>() as u32
+ (clr_name.len() + 1) as u32 + (clr_name.len() + 1) as u32
+ clr.size as u32; + clr.size as u32;
clr.index = self.jitdump_file.next_code_index(); clr.index = jitdump_file.next_code_index();
self.dump_debug_info(&unit, &dwarf, clr.address, clr.size, None)?; self.dump_debug_info(jitdump_file, &unit, &dwarf, clr.address, clr.size, None)?;
clr.header.timestamp = self.jitdump_file.get_time_stamp(); clr.header.timestamp = jitdump_file.get_time_stamp();
unsafe { unsafe {
let code_buffer: &[u8] = let code_buffer: &[u8] =
std::slice::from_raw_parts(clr.address as *const u8, clr.size as usize); std::slice::from_raw_parts(clr.address as *const u8, clr.size as usize);
let _ = let _ = jitdump_file.write_code_load_record(&clr_name, clr, code_buffer);
self.jitdump_file
.write_code_load_record(&clr_name, clr, code_buffer);
} }
} }
} else { } else {
@@ -353,6 +330,7 @@ impl State {
continue; continue;
} }
self.dump_debug_info( self.dump_debug_info(
jitdump_file,
&unit, &unit,
&dwarf, &dwarf,
func_addr, func_addr,
@@ -366,14 +344,15 @@ impl State {
} }
fn dump_debug_info<R: Reader>( fn dump_debug_info<R: Reader>(
&mut self, &self,
jitdump_file: &mut JitDumpFile,
unit: &gimli::Unit<R>, unit: &gimli::Unit<R>,
dwarf: &gimli::Dwarf<R>, dwarf: &gimli::Dwarf<R>,
address: u64, address: u64,
size: u64, size: u64,
file_suffix: Option<&str>, file_suffix: Option<&str>,
) -> Result<()> { ) -> Result<()> {
let timestamp = self.jitdump_file.get_time_stamp(); let timestamp = jitdump_file.get_time_stamp();
if let Some(program) = unit.line_program.clone() { if let Some(program) = unit.line_program.clone() {
let mut debug_info_record = DebugInfoRecord { let mut debug_info_record = DebugInfoRecord {
header: RecordHeader { header: RecordHeader {
@@ -430,8 +409,8 @@ impl State {
debug_info_record.header.record_size = debug_info_record.header.record_size =
mem::size_of::<DebugInfoRecord>() as u32 + debug_entries_size as u32; mem::size_of::<DebugInfoRecord>() as u32 + debug_entries_size as u32;
let _ = self.jitdump_file.write_debug_info_record(debug_info_record); let _ = jitdump_file.write_debug_info_record(debug_info_record);
let _ = self.jitdump_file.write_debug_info_entries(debug_entries); let _ = jitdump_file.write_debug_info_entries(debug_entries);
} }
Ok(()) Ok(())
} }