1use std::cell::RefCell;
10use std::collections::HashMap;
11use std::io::Write;
12use std::sync::{LazyLock, Mutex};
13use std::time::{Duration, Instant};
14
15static SETTING: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("CHARON_TIMINGS").ok());
17
18pub fn enabled() -> bool {
19 SETTING.is_some()
20}
21
22#[derive(Default, Clone, Copy)]
23pub struct Measure {
24 pub total: Duration,
25 pub own: Duration,
26 pub count: u64,
27}
28
29static MEASURES: LazyLock<Mutex<HashMap<String, Measure>>> = LazyLock::new(Default::default);
32
33thread_local! {
34 static NESTED: RefCell<Vec<Duration>> = const { RefCell::new(Vec::new()) };
36}
37
38pub struct Guard {
40 name: &'static str,
41 suffix: Option<String>,
43 start: Instant,
44}
45
46pub fn scope(name: &'static str) -> Option<Guard> {
48 scope_with(name, None)
49}
50
51pub fn scope_with(name: &'static str, suffix: Option<String>) -> Option<Guard> {
53 if !enabled() {
54 return None;
55 }
56 NESTED.with_borrow_mut(|stack| stack.push(Duration::ZERO));
57 Some(Guard {
58 name,
59 suffix,
60 start: Instant::now(),
61 })
62}
63
64impl Drop for Guard {
65 fn drop(&mut self) {
66 let elapsed = self.start.elapsed();
67 let nested = NESTED.with_borrow_mut(|stack| {
68 let nested = stack.pop().unwrap_or_default();
69 if let Some(parent) = stack.last_mut() {
70 *parent += elapsed;
71 }
72 nested
73 });
74 let long_key = self
75 .suffix
76 .as_ref()
77 .map(|suffix| format!("{}/{suffix}", self.name));
78 let key: &str = long_key.as_deref().unwrap_or(self.name);
79 let mut measures = MEASURES.lock().unwrap();
80 if !measures.contains_key(key) {
82 measures.insert(key.to_owned(), Measure::default());
83 }
84 let entry = measures.get_mut(key).unwrap();
85 entry.total += elapsed;
86 entry.own += elapsed.saturating_sub(nested);
87 entry.count += 1;
88 }
89}
90
91pub fn scope_lazy(name: &'static str, suffix: impl FnOnce() -> String) -> Option<Guard> {
93 if !enabled() {
94 return None;
95 }
96 scope_with(name, Some(suffix()))
97}
98
99pub fn time<T>(name: &'static str, f: impl FnOnce() -> T) -> T {
101 let _guard = scope(name);
102 f()
103}
104
105pub fn report(crate_name: &str) {
108 let Some(setting) = SETTING.as_ref() else {
109 return;
110 };
111 let measures = MEASURES.lock().unwrap();
112 let mut entries: Vec<(&String, &Measure)> = measures.iter().collect();
113 entries.sort_by_key(|(name, m)| (std::cmp::Reverse(m.own), (*name).clone()));
114
115 let mut out = String::new();
116 out += &format!("\n=== charon timings for crate `{crate_name}` ===\n");
117 out += &format!(
118 "{:<60} {:>12} {:>12} {:>10}\n",
119 "scope", "total (ms)", "self (ms)", "calls"
120 );
121 for (name, m) in &entries {
122 out += &format!(
123 "{:<60} {:>12.2} {:>12.2} {:>10}\n",
124 name,
125 m.total.as_secs_f64() * 1000.,
126 m.own.as_secs_f64() * 1000.,
127 m.count
128 );
129 }
130 eprint!("{out}");
131
132 if setting != "1"
133 && let Ok(mut file) = std::fs::OpenOptions::new()
134 .create(true)
135 .append(true)
136 .open(setting)
137 {
138 for (name, m) in &entries {
139 let _ = writeln!(
140 file,
141 "{crate_name},{name},{:.3},{:.3},{}",
142 m.total.as_secs_f64() * 1000.,
143 m.own.as_secs_f64() * 1000.,
144 m.count
145 );
146 }
147 }
148}