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#[derive(Debug, Clone, Default)]
130pub struct BacktraceMetric {
131 pub allocated: u64,
133 pub freed: u64,
135 pub allocations: u64,
137 pub mode: BacktraceMode,
139}
140
141impl BacktraceMetric {
142 pub fn in_use(&self) -> u64 {
144 self.allocated.saturating_sub(self.freed)
145 }
146
147 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
183pub 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}