1use std::collections::HashMap;
4use std::fmt::{Debug, Formatter};
5
6use parking_lot::RwLock;
7
8use crate::frames::{Frames, UnresolvedFrames};
9use crate::profiler::Profiler;
10use crate::timer::ReportTiming;
11
12use crate::{Error, Result};
13
14pub struct Report {
16 pub data: HashMap<Frames, isize>,
18
19 pub timing: ReportTiming,
21}
22
23pub struct UnresolvedReport {
25 pub data: HashMap<UnresolvedFrames, isize>,
27
28 pub timing: ReportTiming,
30}
31
32type FramesPostProcessor = Box<dyn Fn(&mut Frames)>;
33
34pub struct ReportBuilder<'a> {
36 frames_post_processor: Option<FramesPostProcessor>,
37 profiler: &'a RwLock<Result<Profiler>>,
38 timing: ReportTiming,
39}
40
41impl<'a> ReportBuilder<'a> {
42 pub(crate) fn new(profiler: &'a RwLock<Result<Profiler>>, timing: ReportTiming) -> Self {
43 Self {
44 frames_post_processor: None,
45 profiler,
46 timing,
47 }
48 }
49
50 pub fn frames_post_processor<T>(&mut self, frames_post_processor: T) -> &mut Self
53 where
54 T: Fn(&mut Frames) + 'static,
55 {
56 self.frames_post_processor
57 .replace(Box::new(frames_post_processor));
58
59 self
60 }
61
62 pub fn build_unresolved(&self) -> Result<UnresolvedReport> {
64 let mut hash_map = HashMap::new();
65
66 match self.profiler.read().as_ref() {
67 Err(err) => {
68 log::error!("Error in creating profiler: {}", err);
69 Err(Error::CreatingError)
70 }
71 Ok(profiler) => {
72 profiler.data.try_iter()?.for_each(|entry| {
73 let count = entry.count;
74 if count > 0 {
75 let key = &entry.item;
76 match hash_map.get_mut(key) {
77 Some(value) => {
78 *value += count;
79 }
80 None => {
81 match hash_map.insert(key.clone(), count) {
82 None => {}
83 Some(_) => {
84 unreachable!();
85 }
86 };
87 }
88 }
89 }
90 });
91
92 Ok(UnresolvedReport {
93 data: hash_map,
94 timing: self.timing.clone(),
95 })
96 }
97 }
98 }
99
100 pub fn build(&self) -> Result<Report> {
102 let mut hash_map = HashMap::new();
103
104 match self.profiler.write().as_mut() {
105 Err(err) => {
106 log::error!("Error in creating profiler: {}", err);
107 Err(Error::CreatingError)
108 }
109 Ok(profiler) => {
110 profiler.data.try_iter()?.for_each(|entry| {
111 let count = entry.count;
112 if count > 0 {
113 let mut key = Frames::from(entry.item.clone());
114 if let Some(processor) = &self.frames_post_processor {
115 processor(&mut key);
116 }
117
118 match hash_map.get_mut(&key) {
119 Some(value) => {
120 *value += count;
121 }
122 None => {
123 match hash_map.insert(key, count) {
124 None => {}
125 Some(_) => {
126 unreachable!();
127 }
128 };
129 }
130 }
131 }
132 });
133
134 Ok(Report {
135 data: hash_map,
136 timing: self.timing.clone(),
137 })
138 }
139 }
140 }
141}
142
143impl Debug for Report {
150 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
151 for (key, val) in self.data.iter() {
152 write!(f, "{:?} {}", key, val)?;
153 writeln!(f)?;
154 }
155
156 Ok(())
157 }
158}
159
160#[cfg(feature = "flamegraph")]
161mod flamegraph {
162 use super::*;
163 use inferno::flamegraph;
164 use std::fmt::Write;
165
166 impl Report {
167 pub fn flamegraph<W>(&self, writer: W) -> Result<()>
169 where
170 W: std::io::Write,
171 {
172 self.flamegraph_with_options(writer, &mut flamegraph::Options::default())
173 }
174
175 pub fn flamegraph_with_options<W>(
177 &self,
178 writer: W,
179 options: &mut flamegraph::Options,
180 ) -> Result<()>
181 where
182 W: std::io::Write,
183 {
184 let lines: Vec<String> = self
185 .data
186 .iter()
187 .map(|(key, value)| {
188 let mut line = key.thread_name_or_id();
189 line.push(';');
190
191 for frame in key.frames.iter().rev() {
192 for symbol in frame.iter().rev() {
193 write!(&mut line, "{};", symbol).unwrap();
194 }
195 }
196
197 line.pop().unwrap_or_default();
198 write!(&mut line, " {}", value).unwrap();
199
200 line
201 })
202 .collect();
203 if !lines.is_empty() {
204 flamegraph::from_lines(options, lines.iter().map(|s| &**s), writer).unwrap();
205 }
207
208 Ok(())
209 }
210 }
211}
212
213#[cfg(feature = "_protobuf")]
214#[allow(clippy::useless_conversion)]
215#[allow(clippy::needless_update)]
216mod protobuf {
217 use super::*;
218 use crate::protos;
219 use std::collections::HashSet;
220 use std::time::SystemTime;
221
222 const SAMPLES: &str = "samples";
223 const COUNT: &str = "count";
224 const CPU: &str = "cpu";
225 const NANOSECONDS: &str = "nanoseconds";
226 const THREAD: &str = "thread";
227
228 impl Report {
229 pub fn pprof(&self) -> crate::Result<protos::Profile> {
231 let mut dedup_str = HashSet::new();
232 for key in self.data.keys() {
233 dedup_str.insert(key.thread_name_or_id());
234 for frame in key.frames.iter() {
235 for symbol in frame {
236 dedup_str.insert(symbol.name());
237 dedup_str.insert(symbol.sys_name().into_owned());
238 dedup_str.insert(symbol.filename().into_owned());
239 }
240 }
241 }
242 dedup_str.insert(SAMPLES.into());
243 dedup_str.insert(COUNT.into());
244 dedup_str.insert(CPU.into());
245 dedup_str.insert(NANOSECONDS.into());
246 dedup_str.insert(THREAD.into());
247 let mut str_tbl = vec!["".to_owned()];
249 str_tbl.extend(dedup_str.into_iter());
250
251 let mut strings = HashMap::new();
252 for (index, name) in str_tbl.iter().enumerate() {
253 strings.insert(name.as_str(), index);
254 }
255
256 let mut samples = vec![];
257 let mut loc_tbl = vec![];
258 let mut fn_tbl = vec![];
259 let mut functions = HashMap::new();
260 for (key, count) in self.data.iter() {
261 let mut locs = vec![];
262 for frame in key.frames.iter() {
263 for symbol in frame {
264 let name = symbol.name();
265 if let Some(loc_idx) = functions.get(&name) {
266 locs.push(*loc_idx);
267 continue;
268 }
269 let sys_name = symbol.sys_name();
270 let filename = symbol.filename();
271 let lineno = symbol.lineno();
272 let function_id = fn_tbl.len() as u64 + 1;
273 let function = protos::Function {
274 id: function_id,
275 name: *strings.get(name.as_str()).unwrap() as i64,
276 system_name: *strings.get(sys_name.as_ref()).unwrap() as i64,
277 filename: *strings.get(filename.as_ref()).unwrap() as i64,
278 ..protos::Function::default()
279 };
280 functions.insert(name, function_id);
281 let line = protos::Line {
282 function_id,
283 line: lineno as i64,
284 ..protos::Line::default()
285 };
286 let loc = protos::Location {
287 id: function_id,
288 line: vec![line].into(),
289 ..protos::Location::default()
290 };
291 fn_tbl.push(function);
293 loc_tbl.push(loc);
294 locs.push(function_id);
296 }
297 }
298 let thread_name = protos::Label {
299 key: *strings.get(THREAD).unwrap() as i64,
300 str: *strings.get(&key.thread_name_or_id().as_str()).unwrap() as i64,
301 ..protos::Label::default()
302 };
303 let sample = protos::Sample {
304 location_id: locs,
305 value: vec![
306 *count as i64,
307 *count as i64 * 1_000_000_000 / self.timing.frequency as i64,
308 ],
309 label: vec![thread_name].into(),
310 ..Default::default()
311 };
312 samples.push(sample);
313 }
314 let samples_value = protos::ValueType {
315 ty: *strings.get(SAMPLES).unwrap() as i64,
316 unit: *strings.get(COUNT).unwrap() as i64,
317 ..Default::default()
318 };
319 let time_value = protos::ValueType {
320 ty: *strings.get(CPU).unwrap() as i64,
321 unit: *strings.get(NANOSECONDS).unwrap() as i64,
322 ..Default::default()
323 };
324 let profile = protos::Profile {
325 sample_type: vec![samples_value, time_value.clone()].into(),
326 sample: samples.into(),
327 string_table: str_tbl.into(),
328 function: fn_tbl.into(),
329 location: loc_tbl.into(),
330 time_nanos: self
331 .timing
332 .start_time
333 .duration_since(SystemTime::UNIX_EPOCH)
334 .unwrap_or_default()
335 .as_nanos() as i64,
336 duration_nanos: self.timing.duration.as_nanos() as i64,
337 period_type: Some(time_value).into(),
338 period: 1_000_000_000 / self.timing.frequency as i64,
339 ..protos::Profile::default()
340 };
341 Ok(profile)
342 }
343 }
344}