pprof/
frames.rs

1// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
2
3use std::borrow::Cow;
4use std::fmt::{self, Debug, Display, Formatter};
5use std::hash::{Hash, Hasher};
6use std::os::raw::c_void;
7use std::path::PathBuf;
8use std::time::SystemTime;
9
10use smallvec::SmallVec;
11use symbolic_demangle::demangle;
12
13use crate::backtrace::{Frame, Trace, TraceImpl};
14use crate::{MAX_DEPTH, MAX_THREAD_NAME};
15
16#[derive(Clone)]
17pub struct UnresolvedFrames {
18    pub frames: SmallVec<[<TraceImpl as Trace>::Frame; MAX_DEPTH]>,
19    pub thread_name: [u8; MAX_THREAD_NAME],
20    pub thread_name_length: usize,
21    pub thread_id: u64,
22    pub sample_timestamp: SystemTime,
23}
24
25impl Default for UnresolvedFrames {
26    fn default() -> Self {
27        let frames = SmallVec::with_capacity(MAX_DEPTH);
28        Self {
29            frames,
30            thread_name: [0; MAX_THREAD_NAME],
31            thread_name_length: 0,
32            thread_id: 0,
33            sample_timestamp: SystemTime::now(),
34        }
35    }
36}
37
38impl Debug for UnresolvedFrames {
39    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
40        self.frames.fmt(f)
41    }
42}
43
44impl UnresolvedFrames {
45    pub fn new(
46        frames: SmallVec<[<TraceImpl as Trace>::Frame; MAX_DEPTH]>,
47        tn: &[u8],
48        thread_id: u64,
49        sample_timestamp: SystemTime,
50    ) -> Self {
51        let thread_name_length = tn.len();
52        let mut thread_name = [0; MAX_THREAD_NAME];
53        thread_name[0..thread_name_length].clone_from_slice(tn);
54
55        Self {
56            frames,
57            thread_name,
58            thread_name_length,
59            thread_id,
60            sample_timestamp,
61        }
62    }
63}
64
65impl PartialEq for UnresolvedFrames {
66    fn eq(&self, other: &Self) -> bool {
67        let (frames1, frames2) = (&self.frames, &other.frames);
68        if self.thread_id != other.thread_id || frames1.len() != frames2.len() {
69            false
70        } else {
71            Iterator::zip(frames1.iter(), frames2.iter())
72                .map(|(s1, s2)| s1.symbol_address() == s2.symbol_address())
73                .all(|equal| equal)
74        }
75    }
76}
77
78impl Eq for UnresolvedFrames {}
79
80impl Hash for UnresolvedFrames {
81    fn hash<H: Hasher>(&self, state: &mut H) {
82        self.frames
83            .iter()
84            .for_each(|frame| frame.symbol_address().hash(state));
85        self.thread_id.hash(state);
86    }
87}
88
89/// Symbol is a representation of a function symbol. It contains name and addr of it. If built with
90/// debug message, it can also provide line number and filename. The name in it is not demangled.
91#[derive(Debug, Clone)]
92pub struct Symbol {
93    /// This name is raw name of a symbol (which hasn't been demangled).
94    pub name: Option<Vec<u8>>,
95
96    /// The address of the function. It is not 100% trustworthy.
97    pub addr: Option<*mut c_void>,
98
99    /// Line number of this symbol. If compiled with debug message, you can get it.
100    pub lineno: Option<u32>,
101
102    /// Filename of this symbol. If compiled with debug message, you can get it.
103    pub filename: Option<PathBuf>,
104}
105
106impl Symbol {
107    pub fn raw_name(&self) -> &[u8] {
108        self.name.as_deref().unwrap_or(b"Unknown")
109    }
110
111    pub fn name(&self) -> String {
112        demangle(&String::from_utf8_lossy(self.raw_name())).into_owned()
113    }
114
115    pub fn sys_name(&self) -> Cow<str> {
116        String::from_utf8_lossy(self.raw_name())
117    }
118
119    pub fn filename(&self) -> Cow<str> {
120        self.filename
121            .as_ref()
122            .map(|name| name.as_os_str().to_string_lossy())
123            .unwrap_or_else(|| Cow::Borrowed("Unknown"))
124    }
125
126    pub fn lineno(&self) -> u32 {
127        self.lineno.unwrap_or(0)
128    }
129}
130
131unsafe impl Send for Symbol {}
132
133impl<T> From<&T> for Symbol
134where
135    T: crate::backtrace::Symbol,
136{
137    fn from(symbol: &T) -> Self {
138        Symbol {
139            name: symbol.name(),
140            addr: symbol.addr(),
141            lineno: symbol.lineno(),
142            filename: symbol.filename(),
143        }
144    }
145}
146
147impl Display for Symbol {
148    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
149        f.write_str(&self.name())
150    }
151}
152
153impl PartialEq for Symbol {
154    fn eq(&self, other: &Self) -> bool {
155        self.raw_name() == other.raw_name()
156    }
157}
158
159impl Hash for Symbol {
160    fn hash<H: Hasher>(&self, state: &mut H) {
161        self.raw_name().hash(state)
162    }
163}
164
165/// A representation of a backtrace. `thread_name` and `thread_id` was got from `pthread_getname_np`
166/// and `pthread_self`. frames is a vector of symbols.
167#[derive(Clone, PartialEq, Hash)]
168pub struct Frames {
169    pub frames: Vec<Vec<Symbol>>,
170    pub thread_name: String,
171    pub thread_id: u64,
172    pub sample_timestamp: SystemTime,
173}
174
175impl Frames {
176    /// Returns a thread identifier (name or ID) as a string.
177    pub fn thread_name_or_id(&self) -> String {
178        if !self.thread_name.is_empty() {
179            self.thread_name.clone()
180        } else {
181            format!("{:?}", self.thread_id)
182        }
183    }
184}
185
186impl From<UnresolvedFrames> for Frames {
187    fn from(frames: UnresolvedFrames) -> Self {
188        let mut fs = Vec::new();
189
190        let mut frame_iter = frames.frames.iter();
191
192        while let Some(frame) = frame_iter.next() {
193            let mut symbols: Vec<Symbol> = Vec::new();
194
195            frame.resolve_symbol(|symbol| {
196                let symbol = Symbol::from(symbol);
197                symbols.push(symbol);
198            });
199
200            if symbols.iter().any(|symbol| {
201                // macOS prepends an underscore even with `#[no_mangle]`
202                matches!(
203                    &*symbol.name(),
204                    "perf_signal_handler" | "_perf_signal_handler"
205                )
206            }) {
207                // ignore frame itself and its next one
208                frame_iter.next();
209                continue;
210            }
211
212            if !symbols.is_empty() {
213                fs.push(symbols);
214            }
215        }
216
217        Self {
218            frames: fs,
219            thread_name: String::from_utf8_lossy(&frames.thread_name[0..frames.thread_name_length])
220                .into_owned(),
221            thread_id: frames.thread_id,
222            sample_timestamp: frames.sample_timestamp,
223        }
224    }
225}
226
227impl Eq for Frames {}
228
229impl Debug for Frames {
230    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
231        for frame in self.frames.iter() {
232            write!(f, "FRAME: ")?;
233            for symbol in frame.iter() {
234                write!(f, "{} -> ", symbol)?;
235            }
236        }
237        write!(f, "THREAD: ")?;
238        if !self.thread_name.is_empty() {
239            write!(f, "{}", self.thread_name)
240        } else {
241            write!(f, "ThreadId({})", self.thread_id)
242        }
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn demangle_rust() {
252        let symbol = Symbol {
253            name: Some(b"_ZN3foo3barE".to_vec()),
254            addr: None,
255            lineno: None,
256            filename: None,
257        };
258
259        assert_eq!(&symbol.name(), "foo::bar")
260    }
261
262    #[test]
263    fn demangle_cpp() {
264        let name =
265            b"_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_"
266                .to_vec();
267
268        let symbol = Symbol {
269            name: Some(name),
270            addr: None,
271            lineno: None,
272            filename: None,
273        };
274
275        assert_eq!(
276            &symbol.name(),
277            "Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) const"
278        )
279    }
280}