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#[derive(Debug, Clone, Default)]
137pub struct BacktraceMetric {
138 pub allocated: u64,
140 pub freed: u64,
142 pub allocations: u64,
144 pub mode: BacktraceMode,
146}
147
148impl BacktraceMetric {
149 pub fn in_use(&self) -> u64 {
151 self.allocated.saturating_sub(self.freed)
152 }
153
154 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
190pub 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}