ext/pf2/src/profile_serializer.rs in pf2-0.2.0 vs ext/pf2/src/profile_serializer.rs in pf2-0.3.0

- old
+ new

@@ -1,9 +1,12 @@ -use std::{collections::HashMap, ffi::CStr}; +use std::collections::HashMap; +use std::ffi::{c_char, CStr}; +use std::hash::Hasher; use rb_sys::*; +use crate::backtrace::Backtrace; use crate::profile::Profile; #[derive(Debug, Deserialize, Serialize)] pub struct ProfileSerializer { threads: HashMap<ThreadId, ThreadProfile>, @@ -54,13 +57,21 @@ frame_id: FrameTableId, } #[derive(Debug, Deserialize, Serialize)] struct FrameTableEntry { + id: FrameTableId, + entry_type: FrameTableEntryType, full_label: String, } +#[derive(Debug, Deserialize, Serialize)] +enum FrameTableEntryType { + Ruby, + Native, +} + // Represents leaf (末端) #[derive(Debug, Deserialize, Serialize)] struct ProfileSample { elapsed_ns: u128, stack_tree_id: StackTreeNodeId, @@ -75,56 +86,119 @@ }; unsafe { // Process each sample 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![]; + 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| { + if symname.is_null() { + c_stack.push("(no symbol information)".to_owned()); + } else { + c_stack.push(CStr::from_ptr(symname).to_str().unwrap().to_owned()); + } + }, + 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; + } + 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), + entry_type: FrameTableEntryType::Native, + full_label: frame.to_string(), + }); + } + + // 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]; + 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(), + }); + } + // Find the Thread profile for this sample let thread_serializer = serializer .threads .entry(sample.ruby_thread) .or_insert(ThreadProfile::new(sample.ruby_thread)); // Stack frames, shallow to deep let mut stack_tree = &mut thread_serializer.stack_tree; - for i in (0..(sample.line_count - 1)).rev() { - let frame = sample.frames[i as usize]; - - // Register frame metadata to frame table, if not registered yet - let frame_table_id: FrameTableId = frame; - thread_serializer - .frame_table - .entry(frame_table_id) - .or_insert(FrameTableEntry { - full_label: CStr::from_ptr(rb_string_value_cstr( - &mut rb_profile_frame_full_label(frame), - )) - .to_str() - .unwrap() - .to_string(), - }); - - stack_tree = stack_tree.children.entry(frame_table_id).or_insert({ + while let Some(frame_table_entry) = merged_stack.pop() { + stack_tree = stack_tree.children.entry(frame_table_entry.id).or_insert({ let node = StackTreeNode { children: HashMap::new(), node_id: sequence, - frame_id: frame_table_id, + frame_id: frame_table_entry.id, }; sequence += 1; node }); - if i == 0 { + if merged_stack.is_empty() { // This is the leaf node, record a Sample let elapsed_ns = (sample.timestamp - profile.start_timestamp).as_nanos(); thread_serializer.samples.push(ProfileSample { elapsed_ns, stack_tree_id: stack_tree.node_id, }); } + + // Register frame metadata to frame table, if not registered yet + thread_serializer + .frame_table + .entry(frame_table_entry.id) + .or_insert(frame_table_entry); } } } serde_json::to_string(&serializer).unwrap() } +} + +fn calculate_id_for_c_frame<T: std::hash::Hash>(t: &T) -> FrameTableId { + let mut s = std::collections::hash_map::DefaultHasher::new(); + t.hash(&mut s); + s.finish() }