perf: Create a per-process JIT dump file (#6024)
This commit is contained in:
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user