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);
25
26impl<'a> fmt::Display for HashedBacktraceShort<'a> {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        self.0.display_short(f)
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<'_>) -> 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                    {
99                        continue;
100                    }
101                }
102
103                f.frame().backtrace_symbol(frame, symbol)?;
104            }
105            if symbols.is_empty() {
106                f.frame().print_raw(frame.ip(), None, None, None)?;
107            }
108        }
109        f.finish()?;
110        Ok(())
111    }
112}
113
114impl PartialEq for HashedBacktrace {
115    fn eq(&self, other: &Self) -> bool {
116        self.hash == other.hash
117    }
118}
119
120impl Eq for HashedBacktrace {}
121
122impl Hash for HashedBacktrace {
123    fn hash<H: Hasher>(&self, state: &mut H) {
124        self.hash.hash(state);
125    }
126}
127
128/// Allocation information pertaining to a specific backtrace.
129#[derive(Debug, Clone, Default)]
130pub struct BacktraceMetric {
131    /// Number of bytes allocated
132    pub allocated: u64,
133    /// Number of bytes allocated here that have since been freed
134    pub freed: u64,
135    /// Number of actual allocations
136    pub allocations: u64,
137    /// `mode` as copied from `AllocTrack`
138    pub mode: BacktraceMode,
139}
140
141impl BacktraceMetric {
142    /// Number of bytes currently allocated and not freed
143    pub fn in_use(&self) -> u64 {
144        self.allocated.saturating_sub(self.freed)
145    }
146
147    /// Average number of bytes per allocation
148    pub fn avg_allocation(&self) -> f64 {
149        if self.allocations == 0 {
150            0.0
151        } else {
152            self.allocated as f64 / self.allocations as f64
153        }
154    }
155}
156
157impl fmt::Display for BacktraceMetric {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        writeln!(f, "allocated: {}", Size(self.allocated))?;
160        writeln!(f, "allocations: {}", self.allocations)?;
161        writeln!(f, "avg_allocation: {}", SizeF64(self.avg_allocation()))?;
162        writeln!(f, "freed: {}", Size(self.freed))?;
163        writeln!(f, "total_used: {}", Size(self.in_use()))?;
164        Ok(())
165    }
166}
167
168impl BacktraceMetric {
169    pub fn csv_write(&self, out: &mut impl Write) -> fmt::Result {
170        write!(
171            out,
172            "{},{},{},{},{}",
173            self.allocated,
174            self.allocations,
175            self.avg_allocation(),
176            self.freed,
177            self.in_use()
178        )?;
179        Ok(())
180    }
181}
182
183/// A report of all (post-filter) backtraces and their associated allocations metrics.
184pub struct BacktraceReport(pub Vec<(HashedBacktrace, BacktraceMetric)>);
185
186impl BacktraceReport {
187    pub fn csv(&self) -> String {
188        let mut out = String::new();
189        write!(
190            &mut out,
191            "allocated,allocations,avg_allocation,freed,total_used,backtrace\n"
192        )
193        .unwrap();
194        for (backtrace, metric) in &self.0 {
195            match metric.mode {
196                BacktraceMode::None => unreachable!(),
197                BacktraceMode::Short => {
198                    metric.csv_write(&mut out).unwrap();
199                    writeln!(
200                        &mut out,
201                        ",\"{}\"",
202                        HashedBacktraceShort(backtrace)
203                            .to_string()
204                            .replace("\\", "\\\\")
205                            .replace("\n", "\\n")
206                    )
207                    .unwrap();
208                }
209                BacktraceMode::Full => {
210                    metric.csv_write(&mut out).unwrap();
211                    writeln!(
212                        &mut out,
213                        ",\"{}\"",
214                        format!("{:?}", backtrace.inner())
215                            .replace("\\", "\\\\")
216                            .replace("\n", "\\n")
217                    )
218                    .unwrap();
219                }
220            }
221        }
222        out
223    }
224}
225
226impl fmt::Display for BacktraceReport {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        for (backtrace, metric) in &self.0 {
229            match metric.mode {
230                BacktraceMode::None => unreachable!(),
231                BacktraceMode::Short => {
232                    writeln!(f, "{}\n{metric}\n\n", HashedBacktraceShort(backtrace))?
233                }
234                BacktraceMode::Full => writeln!(f, "{:?}\n{metric}\n\n", backtrace.inner())?,
235            }
236        }
237        Ok(())
238    }
239}