Skip to main content

profile_bee/
trace_handler.rs

1use crate::ebpf::{FramePointersPod, StackInfoPod};
2use crate::{cache::PointerStackFramesCache, types::StackFrameInfo, types::StackInfoExt};
3use aya::maps::MapData;
4use aya::maps::StackTraceMap;
5use blazesym::symbolize::source::Kernel;
6use blazesym::symbolize::source::Process;
7use blazesym::symbolize::source::Source;
8use blazesym::symbolize::Input;
9use blazesym::symbolize::Symbolized;
10use blazesym::symbolize::Symbolizer;
11use blazesym::Addr;
12use blazesym::Pid;
13use profile_bee_common::StackInfo;
14
15pub struct SymbolFormatter;
16
17impl SymbolFormatter {
18    /// Simple symbol kernel name only
19    fn map_kernel_sym_to_stack(sym: Symbolized) -> StackFrameInfo {
20        let sym = match sym {
21            Symbolized::Sym(sym) => sym,
22            Symbolized::Unknown(_reason) => {
23                return StackFrameInfo {
24                    symbol: Some(format!("[unknown]")), // {reason}
25                    ..Default::default()
26                };
27            }
28        };
29
30        StackFrameInfo {
31            symbol: Some(format!("{}_k", sym.name)),
32            ..Default::default()
33        }
34    }
35
36    /// Simple symbol name only
37    fn map_user_sym_to_stack(sym: Symbolized) -> StackFrameInfo {
38        let sym = match sym {
39            Symbolized::Sym(sym) => sym,
40            Symbolized::Unknown(_reason) => {
41                return StackFrameInfo {
42                    symbol: Some(format!("[unknown]")), // {reason}
43                    ..Default::default()
44                };
45            }
46        };
47
48        StackFrameInfo {
49            symbol: Some(format!("{}", sym.name)),
50            ..Default::default()
51        }
52    }
53}
54
55/// Trace Handler convert address into proper stacktraces, apply necessary caching
56///
57/// Main entry point for the trace handler that manages symbol resolution and caching
58/// for efficient stack trace processing and visualization.
59pub struct TraceHandler {
60    /// blazesym Symbolizer that internally handles caching
61    symbolizer: Symbolizer,
62    /// Simple Cache
63    cache: PointerStackFramesCache,
64}
65
66impl TraceHandler {
67    pub fn new() -> Self {
68        TraceHandler {
69            symbolizer: Symbolizer::new(),
70            cache: Default::default(),
71        }
72    }
73
74    pub fn print_stats(&self) {
75        println!("{}", self.cache.stats());
76    }
77
78    /// converts kernel stacked frames into symbols
79    fn symbolize_kernel_stack(&self, addrs: &[Addr]) -> Result<Vec<StackFrameInfo>, &str> {
80        let src = Source::Kernel(Kernel::default());
81        let syms = self
82            .symbolizer
83            .symbolize(&src, Input::AbsAddr(&addrs))
84            .map_err(|e| {
85                tracing::error!("Failed to symbolize {:?}", e);
86                "failed to run symbolize"
87            })?
88            .into_iter()
89            .map(SymbolFormatter::map_kernel_sym_to_stack)
90            .collect::<Vec<_>>();
91
92        Ok(syms)
93    }
94
95    /// convert user mode stacked frames into symbols
96    fn symbolize_user_stack(&self, pid: u32, addrs: &[Addr]) -> Result<Vec<StackFrameInfo>, &str> {
97        let src: Source<'_> = Source::Process(Process::new(Pid::from(pid)));
98
99        let syms = self
100            .symbolizer
101            .symbolize(&src, Input::AbsAddr(addrs))
102            .map_err(|e| {
103                tracing::trace!("Failed to symbolize {:?}", e);
104                "failed to run symbolize"
105            })?
106            .into_iter()
107            .map(SymbolFormatter::map_user_sym_to_stack)
108            .collect::<Vec<_>>();
109
110        Ok(syms)
111    }
112
113    /// Converts stacks traces into StackFrameInfo structs
114    /// Uses DWARF-unwound frame pointers from eBPF when available
115    pub fn get_exp_stacked_frames(
116        &mut self,
117        stack_info: &StackInfo,
118        stack_traces: &StackTraceMap<MapData>,
119        group_by_cpu: bool,
120        stacked_pointers: &aya::maps::HashMap<MapData, StackInfoPod, FramePointersPod>,
121    ) -> Vec<StackFrameInfo> {
122        let (kernel_stack, fp_user_stack) = self.get_instruction_pointers(stack_info, stack_traces);
123
124        let key = StackInfoPod(stack_info.clone());
125
126        // Try to use DWARF-unwound frame pointers from eBPF
127        let user_stack = if let Ok(pointers) = stacked_pointers.get(&key, 0) {
128            let pointers = pointers.0;
129            let len = (pointers.len as usize).min(pointers.pointers.len());
130            let fp_len = fp_user_stack.as_ref().map_or(0, |v| v.len());
131            if len > fp_len {
132                let addrs: Vec<u64> = pointers.pointers[..len].to_vec();
133                tracing::debug!(
134                    "Using DWARF-unwound stack ({} frames) for pid {}",
135                    addrs.len(),
136                    stack_info.tgid,
137                );
138                Some(addrs)
139            } else {
140                fp_user_stack
141            }
142        } else {
143            fp_user_stack
144        };
145
146        let stacks = self.format_stack_trace(stack_info, kernel_stack, user_stack, group_by_cpu);
147
148        stacks
149    }
150
151    /// Converts stacks traces into StackFrameInfo structs
152    pub fn get_stacked_frames(
153        &mut self,
154        stack_info: &StackInfo,
155        stack_traces: &StackTraceMap<MapData>,
156        group_by_cpu: bool,
157    ) -> Vec<StackFrameInfo> {
158        let (kernel_stack, user_stack) = self.get_instruction_pointers(stack_info, stack_traces);
159        let stacks = self.format_stack_trace(stack_info, kernel_stack, user_stack, group_by_cpu);
160        stacks
161    }
162
163    // /// Converts stacks traces into StackFrameInfo structs
164    // pub fn cached_stacked_frames(
165    //     &mut self,
166    //     stack_info: &StackInfo,
167    //     stack_traces: &StackTraceMap<MapData>,
168    //     group_by_cpu: bool,
169    // ) -> Vec<StackFrameInfo> {
170    //     let ktrace_id = stack_info.kernel_stack_id;
171    //     let utrace_id = stack_info.user_stack_id;
172    //     if let Some(stacks) = self.cache.get(ktrace_id, utrace_id) {
173    //         return stacks;
174    //     }
175
176    //     let stacks = self.get_stacked_frames(stack_info, stack_traces, group_by_cpu);
177
178    //     self.cache.insert(ktrace_id, utrace_id, stacks.clone());
179
180    //     stacks
181    // }
182
183    /// Extract stacks from StackTraceMaps (kernel's implementation only support FP unwinding)
184    pub fn get_instruction_pointers(
185        &mut self,
186        stack_info: &StackInfo,
187        stack_traces: &StackTraceMap<MapData>,
188    ) -> (Option<Vec<u64>>, Option<Vec<u64>>) {
189        let ktrace_id = stack_info.kernel_stack_id;
190        let utrace_id = stack_info.user_stack_id;
191
192        let kernel_stack = if ktrace_id > -1 {
193            stack_traces.get(&(ktrace_id as u32), 0).ok().map(|stack| {
194                let addrs: Vec<Addr> = stack
195                    .frames()
196                    .iter()
197                    .map(|frame| {
198                        let instruction_pointer = frame.ip;
199                        instruction_pointer
200                    })
201                    .collect();
202
203                addrs
204            })
205        } else {
206            None
207        };
208
209        let user_stack = if utrace_id > -1 {
210            stack_traces.get(&(utrace_id as u32), 0).ok().map(|stack| {
211                let addrs: Vec<Addr> = stack
212                    .frames()
213                    .iter()
214                    .map(|frame| frame.ip)
215                    .collect();
216                addrs
217            })
218        } else {
219            None
220        };
221
222        (kernel_stack, user_stack)
223    }
224
225    /// converts pointers from bpf to usable, symbol resolved stack information
226    /// Return is an array sorted from the bottom (root) to the top (inner most function)
227    /// Looks up symbolization
228    fn format_stack_trace(
229        &self,
230        stack_info: &StackInfo,
231        kernel_stack: Option<Vec<u64>>,
232        user_stack: Option<Vec<u64>>,
233        // stacked_pointers: &StackFrameInfo
234        group_by_cpu: bool,
235    ) -> Vec<StackFrameInfo> {
236        if stack_info.tgid == 0 {
237            let mut idle = StackFrameInfo::prepare(stack_info);
238            idle.symbol = Some("idle".into());
239            let mut idle_cpu = StackFrameInfo::process_only(stack_info);
240
241            if let Some(cpu_id) = stack_info.get_cpu_id() {
242                idle_cpu.symbol = Some(format!("cpu_{:02}", cpu_id));
243            } else {
244                idle_cpu.symbol = idle_cpu.symbol.map(|s| s.replace("swapper/", "cpu_"));
245            }
246
247            if group_by_cpu {
248                if let Some(cpu_id) = stack_info.get_cpu_id() {
249                    idle_cpu.symbol = Some(format!("cpu_{:02}", cpu_id));
250                    return vec![idle_cpu, idle];
251                }
252            }
253
254            return vec![idle, idle_cpu];
255        }
256
257        let pid = stack_info.tgid;
258
259        let addrs = user_stack.unwrap_or_default();
260        let user_syms = self
261            .symbolize_user_stack(pid, &addrs)
262            .ok()
263            .unwrap_or_default();
264
265        let kernel_addrs = kernel_stack.unwrap_or_default();
266        let kernel_syms = self
267            .symbolize_kernel_stack(&kernel_addrs)
268            .ok()
269            .unwrap_or_default();
270
271        let mut combined = kernel_syms
272            .into_iter()
273            .chain(user_syms.into_iter())
274            .collect::<Vec<_>>();
275
276        let pid_info = StackFrameInfo::process_only(stack_info);
277        combined.push(pid_info);
278
279        if group_by_cpu {
280            if let Some(cpu_id) = stack_info.get_cpu_id() {
281                let frame = StackFrameInfo {
282                    symbol: Some(format!("cpu_{:02}", cpu_id)),
283                    ..Default::default()
284                };
285
286                combined.push(frame);
287            }
288        }
289
290        combined.reverse();
291
292        combined
293    }
294}