alloc_track/
backtrace_support.rs

1use std::collections::hash_map::DefaultHasher;
2use std::fmt::{self, Write};
3use std::hash::{Hash, Hasher};
4
5pub use backtrace;
6use backtrace::{Backtrace, BacktraceFmt, BytesOrWideString, PrintFmt};
7
8use crate::{BacktraceMode, Size, SizeF64};
9
10#[derive(Clone)]
11pub struct HashedBacktrace {
12    inner: Option<Backtrace>,
13    hash: u64,
14}
15
16pub(super) struct TraceInfo {
17    pub backtrace: HashedBacktrace,
18    pub allocated: u64,
19    pub freed: u64,
20    pub allocations: u64,
21    pub mode: BacktraceMode,
22}
23
24struct HashedBacktraceShort<'a>(&'a HashedBacktrace, fn(&str) -> bool);
25
26impl<'a> fmt::Display for HashedBacktraceShort<'a> {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        self.0.display_short(f, self.1)
29    }
30}
31
32impl HashedBacktrace {
33    pub fn capture(mode: BacktraceMode) -> Self {
34        if matches!(mode, BacktraceMode::None) {
35            return Self {
36                inner: None,
37                hash: 0,
38            };
39        }
40        let backtrace = Backtrace::new_unresolved();
41        let mut hasher = DefaultHasher::new();
42        backtrace
43            .frames()
44            .iter()
45            .for_each(|x| hasher.write_u64(x.ip() as u64));
46        let hash = hasher.finish();
47        Self {
48            inner: Some(backtrace),
49            hash,
50        }
51    }
52
53    pub fn inner(&self) -> &Backtrace {
54        self.inner.as_ref().unwrap()
55    }
56
57    pub fn inner_mut(&mut self) -> &mut Backtrace {
58        self.inner.as_mut().unwrap()
59    }
60
61    pub fn hash(&self) -> u64 {
62        self.hash
63    }
64
65    fn display_short(&self, f: &mut fmt::Formatter<'_>, filter: fn(&str) -> bool) -> fmt::Result {
66        let full = f.alternate();
67        let frames = self.inner().frames();
68
69        let cwd = std::env::current_dir();
70        let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
71            let path = path.into_path_buf();
72            if !full {
73                if let Ok(cwd) = &cwd {
74                    if let Ok(suffix) = path.strip_prefix(cwd) {
75                        return fmt::Display::fmt(&suffix.display(), fmt);
76                    }
77                }
78            }
79            fmt::Display::fmt(&path.display(), fmt)
80        };
81
82        let mut f = BacktraceFmt::new(f, PrintFmt::Short, &mut print_path);
83        f.add_context()?;
84        for frame in frames {
85            let symbols = frame.symbols();
86            for symbol in symbols {
87                if let Some(name) = symbol.name().map(|x| x.to_string()) {
88                    let name = name.strip_prefix('<').unwrap_or(&name);
89                    if name.starts_with("alloc_track::")
90                        || name == "__rg_alloc"
91                        || name.starts_with("alloc::")
92                        || name.starts_with("std::panicking::")
93                        || name == "__rust_try"
94                        || name == "_start"
95                        || name == "__libc_start_main_impl"
96                        || name == "__libc_start_call_main"
97                        || name.starts_with("std::rt::")
98                        || name.starts_with("tokio::")
99                        || name.starts_with("__rustc")
100                        || name.starts_with("core::ops")
101                        || name.starts_with("std::sys")
102                        || name == "std::panic::catch_unwind"
103                        || name == "unknown>"
104                        || !filter(name)
105                    {
106                        continue;
107                    }
108                }
109
110                f.frame().backtrace_symbol(frame, symbol)?;
111            }
112            if symbols.is_empty() {
113                f.frame().print_raw(frame.ip(), None, None, None)?;
114            }
115        }
116        f.finish()?;
117        Ok(())
118    }
119}
120
121impl PartialEq for HashedBacktrace {
122    fn eq(&self, other: &Self) -> bool {
123        self.hash == other.hash
124    }
125}
126
127impl Eq for HashedBacktrace {}
128
129impl Hash for HashedBacktrace {
130    fn hash<H: Hasher>(&self, state: &mut H) {
131        self.hash.hash(state);
132    }
133}
134
135/// Allocation information pertaining to a specific backtrace.
136#[derive(Debug, Clone, Default)]
137pub struct BacktraceMetric {
138    /// Number of bytes allocated
139    pub allocated: u64,
140    /// Number of bytes allocated here that have since been freed
141    pub freed: u64,
142    /// Number of actual allocations
143    pub allocations: u64,
144    /// `mode` as copied from `AllocTrack`
145    pub mode: BacktraceMode,
146}
147
148impl BacktraceMetric {
149    /// Number of bytes currently allocated and not freed
150    pub fn in_use(&self) -> u64 {
151        self.allocated.saturating_sub(self.freed)
152    }
153
154    /// Average number of bytes per allocation
155    pub fn avg_allocation(&self) -> f64 {
156        if self.allocations == 0 {
157            0.0
158        } else {
159            self.allocated as f64 / self.allocations as f64
160        }
161    }
162}
163
164impl fmt::Display for BacktraceMetric {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        writeln!(f, "allocated: {}", Size(self.allocated))?;
167        writeln!(f, "allocations: {}", self.allocations)?;
168        writeln!(f, "avg_allocation: {}", SizeF64(self.avg_allocation()))?;
169        writeln!(f, "freed: {}", Size(self.freed))?;
170        writeln!(f, "total_used: {}", Size(self.in_use()))?;
171        Ok(())
172    }
173}
174
175impl BacktraceMetric {
176    pub fn csv_write(&self, out: &mut impl Write) -> fmt::Result {
177        write!(
178            out,
179            "{},{},{},{},{}",
180            self.allocated,
181            self.allocations,
182            self.avg_allocation(),
183            self.freed,
184            self.in_use()
185        )?;
186        Ok(())
187    }
188}
189
190/// A report of all (post-filter) backtraces and their associated allocations metrics.
191pub struct BacktraceReport(pub Vec<(HashedBacktrace, BacktraceMetric)>);
192
193impl BacktraceReport {
194    pub fn csv(&self) -> String {
195        let mut out = String::new();
196        writeln!(
197            &mut out,
198            "allocated,allocations,avg_allocation,freed,total_used,backtrace"
199        )
200        .unwrap();
201        for (backtrace, metric) in &self.0 {
202            match metric.mode {
203                BacktraceMode::None => unreachable!(),
204                BacktraceMode::Short(filter) => {
205                    metric.csv_write(&mut out).unwrap();
206                    writeln!(
207                        &mut out,
208                        ",\"{}\"",
209                        HashedBacktraceShort(backtrace, filter)
210                            .to_string()
211                            .replace("\\", "\\\\")
212                            .replace("\n", "\\n")
213                    )
214                    .unwrap();
215                }
216                BacktraceMode::Full => {
217                    metric.csv_write(&mut out).unwrap();
218                    writeln!(
219                        &mut out,
220                        ",\"{}\"",
221                        format!("{:?}", backtrace.inner())
222                            .replace("\\", "\\\\")
223                            .replace("\n", "\\n")
224                    )
225                    .unwrap();
226                }
227            }
228        }
229        out
230    }
231}
232
233impl fmt::Display for BacktraceReport {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        for (backtrace, metric) in &self.0 {
236            match metric.mode {
237                BacktraceMode::None => unreachable!(),
238                BacktraceMode::Short(filter) => writeln!(
239                    f,
240                    "{}\n{metric}\n\n",
241                    HashedBacktraceShort(backtrace, filter)
242                )?,
243                BacktraceMode::Full => writeln!(f, "{:?}\n{metric}\n\n", backtrace.inner())?,
244            }
245        }
246        Ok(())
247    }
248}