fix: Correct tutorial 12 profile folded stack format (#202)

- Reorder stack processing to place kernel frames at bottom and user frames on top
- Match expected flamegraph.pl input format
- Simplify output to standard folded format: "comm;stack1;stack2 count"
- Remove timestamp and CPU metadata
This commit is contained in:
KEROLT
2026-02-21 14:17:08 +08:00
committed by GitHub
parent bb994bf436
commit d06ec2f281

View File

@@ -1,7 +1,7 @@
use std::mem;
use std::time::{SystemTime, UNIX_EPOCH};
use blazesym::symbolize; use blazesym::symbolize;
use nix::sys::sysinfo; use nix::sys::sysinfo;
use std::mem;
use std::time::{SystemTime, UNIX_EPOCH};
pub const MAX_STACK_DEPTH: usize = 128; pub const MAX_STACK_DEPTH: usize = 128;
pub const TASK_COMM_LEN: usize = 16; pub const TASK_COMM_LEN: usize = 16;
@@ -104,8 +104,10 @@ impl EventHandler {
let unix_timestamp_ns = event.timestamp + self.boot_time_ns; let unix_timestamp_ns = event.timestamp + self.boot_time_ns;
let timestamp_sec = unix_timestamp_ns / 1_000_000_000; let timestamp_sec = unix_timestamp_ns / 1_000_000_000;
let timestamp_nsec = unix_timestamp_ns % 1_000_000_000; let timestamp_nsec = unix_timestamp_ns % 1_000_000_000;
println!("[{}.{:09}] COMM: {} (pid={}) @ CPU {}", println!(
timestamp_sec, timestamp_nsec, comm, event.pid, event.cpu_id); "[{}.{:09}] COMM: {} (pid={}) @ CPU {}",
timestamp_sec, timestamp_nsec, comm, event.pid, event.cpu_id
);
if event.kstack_size > 0 { if event.kstack_size > 0 {
println!("Kernel:"); println!("Kernel:");
@@ -128,48 +130,43 @@ impl EventHandler {
fn handle_folded_extended(&self, event: &StacktraceEvent) { fn handle_folded_extended(&self, event: &StacktraceEvent) {
let comm = Self::get_comm_str(&event.comm); let comm = Self::get_comm_str(&event.comm);
let tid = event.pid; // For single-threaded processes, TID = PID
let mut stack_frames = Vec::new(); let mut stack_frames = Vec::new();
// Process user stack (if present) // Process kernel stack first (bottom of stack, closer to root)
if event.ustack_size > 0 {
let ustack = Self::get_stack_slice(&event.ustack, event.ustack_size);
let user_frames = symbolize_stack_to_vec(&self.symbolizer, ustack, event.pid);
// Add user frames in reverse order (top to bottom)
for frame in user_frames.iter().rev() {
stack_frames.push(frame.clone());
}
}
// Process kernel stack (if present)
if event.kstack_size > 0 { if event.kstack_size > 0 {
let kstack = Self::get_stack_slice(&event.kstack, event.kstack_size); let kstack = Self::get_stack_slice(&event.kstack, event.kstack_size);
let kernel_frames = symbolize_stack_to_vec(&self.symbolizer, kstack, 0); let kernel_frames = symbolize_stack_to_vec(&self.symbolizer, kstack, 0);
// Add kernel frames with [k] suffix in reverse order (top to bottom) // Add kernel frames with [k] suffix (from bottom to top)
for frame in kernel_frames.iter().rev() { for frame in kernel_frames.iter() {
stack_frames.push(format!("{}_[k]", frame)); stack_frames.push(format!("{}_[k]", frame));
} }
} }
// Format: timestamp_ns comm pid tid cpu stack1;stack2;stack3 // Process user stack (on top of kernel stack)
// Convert kernel timestamp to Unix timestamp if event.ustack_size > 0 {
let unix_timestamp_ns = event.timestamp + self.boot_time_ns; let ustack = Self::get_stack_slice(&event.ustack, event.ustack_size);
println!( let user_frames = symbolize_stack_to_vec(&self.symbolizer, ustack, event.pid);
"{} {} {} {} {} {}",
unix_timestamp_ns, // Add user frames (from bottom to top)
comm, for frame in user_frames.iter() {
event.pid, stack_frames.push(frame.clone());
tid, }
event.cpu_id, }
stack_frames.join(";")
); // If no frames, skip this event
if stack_frames.is_empty() {
return;
}
// Format for flamegraph.pl folded format:
// comm;stack_frame1;stack_frame2;stack_frame3 count
// The count is 1 for each sample
println!("{};{} 1", comm, stack_frames.join(";"));
} }
} }
fn print_frame( fn print_frame(
name: &str, name: &str,
addr_info: Option<(blazesym::Addr, blazesym::Addr, usize)>, addr_info: Option<(blazesym::Addr, blazesym::Addr, usize)>,
@@ -217,7 +214,11 @@ fn convert_stack_addresses(stack: &[u64]) -> Vec<blazesym::Addr> {
.collect::<Vec<_>>() .collect::<Vec<_>>()
} else { } else {
// For same-sized types, still need to return owned data for consistency // For same-sized types, still need to return owned data for consistency
stack.iter().copied().map(|addr| addr as blazesym::Addr).collect() stack
.iter()
.copied()
.map(|addr| addr as blazesym::Addr)
.collect()
} }
} }
@@ -242,7 +243,11 @@ fn get_symbolize_source(pid: u32) -> symbolize::source::Source<'static> {
} }
// Symbolize stack and return as vector of strings for folded format // Symbolize stack and return as vector of strings for folded format
fn symbolize_stack_to_vec(symbolizer: &symbolize::Symbolizer, stack: &[u64], pid: u32) -> Vec<String> { fn symbolize_stack_to_vec(
symbolizer: &symbolize::Symbolizer,
stack: &[u64],
pid: u32,
) -> Vec<String> {
let converted = convert_stack_addresses(stack); let converted = convert_stack_addresses(stack);
let stack_addrs = get_stack_slice(stack, &converted); let stack_addrs = get_stack_slice(stack, &converted);
let src = get_symbolize_source(pid); let src = get_symbolize_source(pid);
@@ -251,17 +256,17 @@ fn symbolize_stack_to_vec(symbolizer: &symbolize::Symbolizer, stack: &[u64], pid
Ok(syms) => syms, Ok(syms) => syms,
Err(_) => { Err(_) => {
// Return addresses if symbolization fails // Return addresses if symbolization fails
return stack_addrs.iter().map(|addr| format!("{:#x}", addr)).collect(); return stack_addrs
.iter()
.map(|addr| format!("{:#x}", addr))
.collect();
} }
}; };
let mut result = Vec::new(); let mut result = Vec::new();
for (addr, sym) in stack_addrs.iter().copied().zip(syms) { for (addr, sym) in stack_addrs.iter().copied().zip(syms) {
match sym { match sym {
symbolize::Symbolized::Sym(symbolize::Sym { symbolize::Symbolized::Sym(symbolize::Sym { name, .. }) => {
name,
..
}) => {
result.push(name.to_string()); result.push(name.to_string());
} }
symbolize::Symbolized::Unknown(..) => { symbolize::Symbolized::Unknown(..) => {