1use std::collections::BTreeMap;
17use std::ffi::c_void;
18use std::io::Write;
19use std::sync::atomic::AtomicBool;
20use std::time::{SystemTime, UNIX_EPOCH};
21
22use flate2::Compression;
23use flate2::write::GzEncoder;
24use mz_ore::cast::{CastFrom, TryCastFrom};
25use pprof_util::{StackProfile, WeightedStack};
26use prost::Message;
27
28mod pprof_types;
29pub mod time;
30
31#[cfg(feature = "jemalloc")]
32pub mod jemalloc;
33
34pub trait StackProfileExt {
35 fn to_mzfg(&self, symbolize: bool, header_extra: &[(&str, &str)]) -> String;
37 fn to_pprof(
42 &self,
43 sample_type: (&str, &str),
44 period_type: (&str, &str),
45 anno_key: Option<String>,
46 ) -> Vec<u8>;
47}
48
49impl StackProfileExt for StackProfile {
50 fn to_mzfg(&self, symbolize: bool, header_extra: &[(&str, &str)]) -> String {
51 use std::fmt::Write;
54 let mut builder = r#"!!! COMMENT !!!: Open with bin/fgviz /path/to/mzfg
55mz_fg_version: 1
56"#
57 .to_owned();
58 for (k, v) in header_extra {
59 assert!(!(k.contains(':') || k.contains('\n') || v.contains('\n')));
60 writeln!(&mut builder, "{k}: {v}").unwrap();
61 }
62 writeln!(&mut builder, "").unwrap();
63
64 for (WeightedStack { addrs, weight }, anno) in &self.stacks {
65 let anno = anno.map(|i| &self.annotations[i]);
66 for &addr in addrs {
67 write!(&mut builder, "{addr:#x};").unwrap();
68 }
69 write!(&mut builder, " {weight}").unwrap();
70 if let Some(anno) = anno {
71 write!(&mut builder, " {anno}").unwrap()
72 }
73 writeln!(&mut builder, "").unwrap();
74 }
75
76 if symbolize {
77 let symbols = crate::symbolize(self);
78 writeln!(&mut builder, "").unwrap();
79
80 for (addr, names) in symbols {
81 if !names.is_empty() {
82 write!(&mut builder, "{addr:#x} ").unwrap();
83 for mut name in names {
84 name = name.replace('\\', "\\\\");
87 name = name.replace(';', "\\;");
88 write!(&mut builder, "{name};").unwrap();
89 }
90 writeln!(&mut builder, "").unwrap();
91 }
92 }
93 }
94
95 builder
96 }
97
98 fn to_pprof(
99 &self,
100 sample_type: (&str, &str),
101 period_type: (&str, &str),
102 anno_key: Option<String>,
103 ) -> Vec<u8> {
104 use crate::pprof_types as proto;
105
106 let mut profile = proto::Profile::default();
107 let mut strings = StringTable::new();
108
109 let anno_key = anno_key.unwrap_or_else(|| "annotation".into());
110
111 profile.sample_type = vec![proto::ValueType {
112 r#type: strings.insert(sample_type.0),
113 unit: strings.insert(sample_type.1),
114 }];
115 profile.period_type = Some(proto::ValueType {
116 r#type: strings.insert(period_type.0),
117 unit: strings.insert(period_type.1),
118 });
119
120 profile.time_nanos = SystemTime::now()
121 .duration_since(UNIX_EPOCH)
122 .expect("now is later than UNIX epoch")
123 .as_nanos()
124 .try_into()
125 .expect("the year 2554 is far away");
126
127 for (idx, mapping) in self.mappings.iter().enumerate() {
128 let mapping_id = u64::cast_from(idx + 1);
129 let pathname = mapping.pathname.to_string_lossy();
130 let filename_idx = strings.insert(&pathname);
131
132 let build_id_idx = match &mapping.build_id {
133 Some(build_id) => strings.insert(&build_id.to_string()),
134 None => 0,
135 };
136
137 profile.mapping.push(proto::Mapping {
138 id: mapping_id,
139 memory_start: u64::cast_from(mapping.memory_start),
140 memory_limit: u64::cast_from(mapping.memory_end),
141 file_offset: mapping.file_offset,
142 filename: filename_idx,
143 build_id: build_id_idx,
144 ..Default::default()
145 });
146
147 let elf_type = 3;
160
161 let comment = format!(
162 "executableInfo={:x};{:x};{:x}",
163 elf_type, mapping.file_offset, mapping.memory_offset
164 );
165 profile.comment.push(strings.insert(&comment));
166 }
167
168 let mut location_ids = BTreeMap::new();
169 for (stack, anno) in self.iter() {
170 let mut sample = proto::Sample::default();
171
172 let value = stack.weight.trunc();
173 let value = i64::try_cast_from(value).expect("no exabyte heap sizes");
174 sample.value.push(value);
175
176 for addr in stack.addrs.iter().rev() {
177 let addr = u64::cast_from(*addr) - 1;
189
190 let loc_id = *location_ids.entry(addr).or_insert_with(|| {
191 let id = u64::cast_from(profile.location.len()) + 1;
194 let mapping_id = profile
195 .mapping
196 .iter()
197 .find(|m| m.memory_start <= addr && m.memory_limit > addr)
198 .map_or(0, |m| m.id);
199 profile.location.push(proto::Location {
200 id,
201 mapping_id,
202 address: addr,
203 ..Default::default()
204 });
205 id
206 });
207
208 sample.location_id.push(loc_id);
209
210 if let Some(anno) = anno {
211 sample.label.push(proto::Label {
212 key: strings.insert(&anno_key),
213 str: strings.insert(anno),
214 ..Default::default()
215 })
216 }
217 }
218
219 profile.sample.push(sample);
220 }
221
222 profile.string_table = strings.finish();
223
224 let encoded = profile.encode_to_vec();
225
226 let mut gz = GzEncoder::new(Vec::new(), Compression::default());
227 gz.write_all(&encoded).unwrap();
228 gz.finish().unwrap()
229 }
230}
231
232#[derive(Default)]
234struct StringTable(BTreeMap<String, i64>);
235
236impl StringTable {
237 fn new() -> Self {
238 let inner = [("".into(), 0)].into();
240 Self(inner)
241 }
242
243 fn insert(&mut self, s: &str) -> i64 {
244 if let Some(idx) = self.0.get(s) {
245 *idx
246 } else {
247 let idx = i64::try_from(self.0.len()).expect("must fit");
248 self.0.insert(s.into(), idx);
249 idx
250 }
251 }
252
253 fn finish(self) -> Vec<String> {
254 let mut vec: Vec<_> = self.0.into_iter().collect();
255 vec.sort_by_key(|(_, idx)| *idx);
256 vec.into_iter().map(|(s, _)| s).collect()
257 }
258}
259
260static EVER_SYMBOLIZED: AtomicBool = AtomicBool::new(false);
261
262pub fn ever_symbolized() -> bool {
267 EVER_SYMBOLIZED.load(std::sync::atomic::Ordering::SeqCst)
268}
269
270pub fn symbolize(profile: &StackProfile) -> BTreeMap<usize, Vec<String>> {
276 EVER_SYMBOLIZED.store(true, std::sync::atomic::Ordering::SeqCst);
277 let mut all_addrs = vec![];
278 for (stack, _annotation) in profile.stacks.iter() {
279 all_addrs.extend(stack.addrs.iter().cloned());
280 }
281 all_addrs.sort_unstable();
285 all_addrs.dedup();
286 all_addrs
287 .into_iter()
288 .map(|addr| {
289 let mut syms = vec![];
290 #[allow(clippy::as_conversions)]
292 let addr_ptr = addr as *mut c_void;
293 backtrace::resolve(addr_ptr, |sym| {
294 let name = sym
295 .name()
296 .map(|sn| sn.to_string())
297 .unwrap_or_else(|| "???".to_string());
298 syms.push(name);
299 });
300 syms.reverse();
301 (addr, syms)
302 })
303 .collect()
304}