ext/pf2/src/profile_serializer.rs in pf2-0.4.0 vs ext/pf2/src/profile_serializer.rs in pf2-0.5.0

- old
+ new

@@ -4,10 +4,11 @@ use rb_sys::*; use crate::backtrace::Backtrace; use crate::profile::Profile; +use crate::util::RTEST; #[derive(Debug, Deserialize, Serialize)] pub struct ProfileSerializer { threads: HashMap<ThreadId, ThreadProfile>, } @@ -60,10 +61,14 @@ #[derive(Debug, Deserialize, Serialize)] struct FrameTableEntry { id: FrameTableId, entry_type: FrameTableEntryType, full_label: String, + file_name: Option<String>, + function_first_lineno: Option<i32>, + callsite_lineno: Option<i32>, + address: Option<usize>, } #[derive(Debug, Deserialize, Serialize)] enum FrameTableEntryType { Ruby, @@ -75,10 +80,15 @@ struct ProfileSample { elapsed_ns: u128, stack_tree_id: StackTreeNodeId, } +struct NativeFunctionFrame { + pub symbol_name: String, + pub address: Option<usize>, +} + impl ProfileSerializer { pub fn serialize(profile: &Profile) -> String { let mut sequence = 1; let mut serializer = ProfileSerializer { @@ -90,69 +100,103 @@ for sample in profile.samples.iter() { let mut merged_stack: Vec<FrameTableEntry> = vec![]; // Process C-level stack - // A vec to keep the "programmer's" C stack trace. - // A single PC may be mapped to multiple inlined frames, - // so we keep the expanded stack frame in this Vec. - let mut c_stack: Vec<String> = vec![]; + let mut c_stack: Vec<NativeFunctionFrame> = vec![]; + // Rebuild the original backtrace (including inlined functions) from the PC. for i in 0..sample.c_backtrace_pcs[0] { let pc = sample.c_backtrace_pcs[i + 1]; Backtrace::backtrace_syminfo( &profile.backtrace_state, pc, - |_pc: usize, symname: *const c_char, _symval: usize, _symsize: usize| { + |_pc: usize, symname: *const c_char, symval: usize, _symsize: usize| { if symname.is_null() { - c_stack.push("(no symbol information)".to_owned()); + c_stack.push(NativeFunctionFrame { + symbol_name: "(no symbol information)".to_owned(), + address: None, + }); } else { - c_stack.push(CStr::from_ptr(symname).to_str().unwrap().to_owned()); + c_stack.push(NativeFunctionFrame { + symbol_name: CStr::from_ptr(symname) + .to_str() + .unwrap() + .to_owned(), + address: Some(symval), + }); } }, Some(Backtrace::backtrace_error_callback), ); } - - // Strip the C stack trace: - // - Remove Pf2-related frames which are always captured - // - Remove frames below rb_vm_exec - let mut reached_ruby = false; - c_stack.retain(|frame| { - if reached_ruby { - return false; + for frame in c_stack.iter() { + if frame.symbol_name.contains("pf2") { + // Skip Pf2-related frames + continue; } - if frame.contains("pf2") { - return false; - } - if frame.contains("rb_vm_exec") || frame.contains("vm_call_cfunc_with_frame") { - reached_ruby = true; - return false; - } - true - }); - for frame in c_stack.iter() { merged_stack.push(FrameTableEntry { - id: calculate_id_for_c_frame(frame), + id: calculate_id_for_c_frame(&frame.symbol_name), entry_type: FrameTableEntryType::Native, - full_label: frame.to_string(), + full_label: frame.symbol_name.clone(), + file_name: None, + function_first_lineno: None, + callsite_lineno: None, + address: frame.address, }); } // Process Ruby-level stack let ruby_stack_depth = sample.line_count; for i in 0..ruby_stack_depth { let frame: VALUE = sample.frames[i as usize]; + let lineno: i32 = sample.linenos[i as usize]; + let address: Option<usize> = { + let cme = frame + as *mut crate::ruby_internal_apis::rb_callable_method_entry_struct; + let cme = &*cme; + + if (*(cme.def)).type_ == 1 { + // The cme is a Cfunc + Some((*(cme.def)).cfunc.func as usize) + } else { + // The cme is an ISeq (Ruby code) or some other type + None + } + }; + let mut frame_full_label: VALUE = rb_profile_frame_full_label(frame); + let frame_full_label: String = if RTEST(frame_full_label) { + CStr::from_ptr(rb_string_value_cstr(&mut frame_full_label)) + .to_str() + .unwrap() + .to_owned() + } else { + "(unknown)".to_owned() + }; + let mut frame_path: VALUE = rb_profile_frame_path(frame); + let frame_path: String = if RTEST(frame_path) { + CStr::from_ptr(rb_string_value_cstr(&mut frame_path)) + .to_str() + .unwrap() + .to_owned() + } else { + "(unknown)".to_owned() + }; + let frame_first_lineno: VALUE = rb_profile_frame_first_lineno(frame); + let frame_first_lineno: Option<i32> = if RTEST(frame_first_lineno) { + Some(rb_num2int(frame_first_lineno).try_into().unwrap()) + } else { + None + }; merged_stack.push(FrameTableEntry { id: frame, entry_type: FrameTableEntryType::Ruby, - full_label: CStr::from_ptr(rb_string_value_cstr( - &mut rb_profile_frame_full_label(frame), - )) - .to_str() - .unwrap() - .to_owned(), + full_label: frame_full_label, + file_name: Some(frame_path), + function_first_lineno: frame_first_lineno, + callsite_lineno: Some(lineno), + address, }); } // Find the Thread profile for this sample let thread_serializer = serializer