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