1use std::{collections::HashMap, time::Duration};
9
10use cli_table::{format::Justify, Cell, Row, RowStruct, Style};
11
12#[derive(Default)]
13pub struct Timings {
14 entries: HashMap<&'static str, Vec<ProfileEntry>>,
15 pending: HashMap<&'static str, Vec<std::time::Instant>>,
16}
17
18pub struct ProfileEntry {
19 time: Duration,
20}
21
22impl Timings {
23 pub fn start(
24 &mut self,
25 key: &'static str,
26 ) {
27 self.pending.entry(key).or_default().push(std::time::Instant::now());
28 }
29
30 pub fn end(
31 &mut self,
32 key: &'static str,
33 ) {
34 let Some(entry) = self.pending.get_mut(key) else {
35 eprintln!("Profiling error: tried to end event {key} which didn't exist");
36 return;
37 };
38 let Some(start) = entry.pop() else {
39 eprintln!("Profiling error: tried to end event {key} which didn't exist");
40 return;
41 };
42 self.entries.entry(key).or_default().push(ProfileEntry { time: start.elapsed() });
43 }
44
45 pub fn render(&self) -> String {
46 let unended = self.pending.iter().filter(|(_k, v)| !v.is_empty()).collect::<Vec<_>>();
47 if !unended.is_empty() {
48 eprintln!("Profiling error: some events were not ended");
49 eprintln!("{:?}", unended);
50 }
51
52 use cli_table::Table;
55 use num_format::{Locale, ToFormattedString};
56
57 let mut table = vec![];
58
59 for (key, entries) in &self.entries {
60 let total_duration: Duration = entries.iter().map(|e| e.time).sum();
61 let duration = match total_duration.as_millis() {
62 x if x < 10 => format!("{} ns", total_duration.as_nanos().to_formatted_string(&Locale::en)),
63 otherwise => format!("{} ms", otherwise.to_formatted_string(&Locale::en)),
64 };
65 let duration = duration.cell().justify(Justify::Right);
66
67 let row: RowStruct = vec![key.to_string().cell().bold(true), duration].row();
68 table.push(row);
69 }
70
71 let table = table
72 .table()
73 .title(vec!["Event".cell().bold(true), "Total Duration".cell().bold(true)])
74 .display()
75 .expect("failed to render profiles table");
76
77 format!("{}", table)
78 }
79}