memscope_rs/metadata/stack_trace/
resolver.rs1use super::StackFrame;
2use std::collections::HashMap;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5#[derive(Debug, Clone)]
7pub struct ResolvedFrame {
8 pub instruction_pointer: usize,
10 pub symbol_name: String,
12 pub demangled_name: Option<String>,
14 pub filename: Option<String>,
16 pub line_number: Option<u32>,
18 pub column: Option<u32>,
20 pub module_name: Option<String>,
22 pub offset: Option<usize>,
24}
25
26pub struct SymbolResolver {
28 symbol_cache: HashMap<usize, ResolvedFrame>,
30 resolution_count: AtomicUsize,
32 cache_hits: AtomicUsize,
34 enable_demangling: bool,
36 enable_line_info: bool,
38}
39
40impl SymbolResolver {
41 pub fn new() -> Self {
43 Self {
44 symbol_cache: HashMap::new(),
45 resolution_count: AtomicUsize::new(0),
46 cache_hits: AtomicUsize::new(0),
47 enable_demangling: true,
48 enable_line_info: true,
49 }
50 }
51
52 pub fn with_options(enable_demangling: bool, enable_line_info: bool) -> Self {
54 Self {
55 symbol_cache: HashMap::new(),
56 resolution_count: AtomicUsize::new(0),
57 cache_hits: AtomicUsize::new(0),
58 enable_demangling,
59 enable_line_info,
60 }
61 }
62
63 pub fn resolve_frame(&mut self, frame: &StackFrame) -> Option<ResolvedFrame> {
66 self.resolution_count.fetch_add(1, Ordering::Relaxed);
67
68 if let Some(cached) = self.symbol_cache.get(&frame.instruction_pointer) {
70 self.cache_hits.fetch_add(1, Ordering::Relaxed);
71 return Some(cached.clone());
72 }
73
74 let resolved = self.perform_resolution(frame.instruction_pointer)?;
76
77 self.symbol_cache
79 .insert(frame.instruction_pointer, resolved.clone());
80
81 Some(resolved)
82 }
83
84 pub fn resolve_batch(&mut self, frames: &[StackFrame]) -> Vec<Option<ResolvedFrame>> {
85 frames
86 .iter()
87 .map(|frame| self.resolve_frame(frame))
88 .collect()
89 }
90
91 pub fn resolve_addresses(&mut self, addresses: &[usize]) -> Vec<Option<ResolvedFrame>> {
92 addresses
93 .iter()
94 .map(|&addr| {
95 let frame = StackFrame::new(addr);
96 self.resolve_frame(&frame)
97 })
98 .collect()
99 }
100
101 pub fn get_cache_stats(&self) -> (usize, usize, f64) {
102 let resolutions = self.resolution_count.load(Ordering::Relaxed);
103 let hits = self.cache_hits.load(Ordering::Relaxed);
104 let hit_ratio = if resolutions > 0 {
105 hits as f64 / resolutions as f64
106 } else {
107 0.0
108 };
109 (resolutions, hits, hit_ratio)
110 }
111
112 pub fn clear_cache(&mut self) {
113 self.symbol_cache.clear();
114 self.resolution_count.store(0, Ordering::Relaxed);
115 self.cache_hits.store(0, Ordering::Relaxed);
116 }
117
118 pub fn cache_size(&self) -> usize {
119 self.symbol_cache.len()
120 }
121
122 pub fn preload_symbols(&mut self, addresses: &[usize]) {
123 for &addr in addresses {
124 if !self.symbol_cache.contains_key(&addr) {
125 if let Some(resolved) = self.perform_resolution(addr) {
126 self.symbol_cache.insert(addr, resolved);
127 }
128 }
129 }
130 }
131
132 fn perform_resolution(&self, address: usize) -> Option<ResolvedFrame> {
133 let symbol_name = self.lookup_symbol_name(address)?;
137 let demangled_name = if self.enable_demangling {
138 self.demangle_symbol(&symbol_name)
139 } else {
140 None
141 };
142
143 let (filename, line_number, column) = if self.enable_line_info {
144 self.lookup_line_info(address)
145 } else {
146 (None, None, None)
147 };
148
149 let module_name = self.lookup_module_name(address);
150 let offset = self.calculate_offset(address, &symbol_name);
151
152 Some(ResolvedFrame {
153 instruction_pointer: address,
154 symbol_name,
155 demangled_name,
156 filename,
157 line_number,
158 column,
159 module_name,
160 offset,
161 })
162 }
163
164 fn lookup_symbol_name(&self, _address: usize) -> Option<String> {
165 None
168 }
169
170 fn demangle_symbol(&self, _mangled: &str) -> Option<String> {
171 None
174 }
175
176 fn lookup_line_info(&self, _address: usize) -> (Option<String>, Option<u32>, Option<u32>) {
177 (None, None, None)
180 }
181
182 fn lookup_module_name(&self, _address: usize) -> Option<String> {
183 None
186 }
187
188 fn calculate_offset(&self, _address: usize, _symbol_name: &str) -> Option<usize> {
189 None
192 }
193}
194
195impl Default for SymbolResolver {
196 fn default() -> Self {
197 Self::new()
198 }
199}
200
201impl ResolvedFrame {
202 pub fn display_name(&self) -> String {
203 let name = self.demangled_name.as_ref().unwrap_or(&self.symbol_name);
204
205 if let (Some(file), Some(line)) = (&self.filename, self.line_number) {
206 if let Some(col) = self.column {
207 format!("{} at {}:{}:{}", name, file, line, col)
208 } else {
209 format!("{} at {}:{}", name, file, line)
210 }
211 } else {
212 name.clone()
213 }
214 }
215
216 pub fn short_display(&self) -> String {
217 self.demangled_name
218 .as_ref()
219 .unwrap_or(&self.symbol_name)
220 .clone()
221 }
222
223 pub fn has_line_info(&self) -> bool {
224 self.filename.is_some() && self.line_number.is_some()
225 }
226
227 pub fn is_rust_symbol(&self) -> bool {
228 self.symbol_name.starts_with("_ZN")
229 || self.demangled_name.as_ref().is_some_and(|name| {
230 name.contains("::") || name.starts_with("std::") || name.starts_with("core::")
231 })
232 }
233
234 pub fn is_system_symbol(&self) -> bool {
235 self.module_name.as_ref().is_some_and(|module| {
236 module.contains("libc") || module.contains("libpthread") || module.contains("ld-")
237 })
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_resolved_frame_display() {
247 let frame = ResolvedFrame {
248 instruction_pointer: 0x1234,
249 symbol_name: "_ZN4main17h1234567890abcdefE".to_string(),
250 demangled_name: Some("main".to_string()),
251 filename: Some("src/main.rs".to_string()),
252 line_number: Some(42),
253 column: Some(10),
254 module_name: Some("main_executable".to_string()),
255 offset: Some(16),
256 };
257
258 assert_eq!(frame.display_name(), "main at src/main.rs:42:10");
259 assert_eq!(frame.short_display(), "main");
260 assert!(frame.has_line_info());
261 assert!(frame.is_rust_symbol());
262 assert!(!frame.is_system_symbol());
263 }
264}