reference_query/
profile.rs1use std::sync::Mutex;
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::time::{Duration, Instant};
19
20static ENABLED: AtomicBool = AtomicBool::new(false);
21static PHASES: Mutex<Vec<Phase>> = Mutex::new(Vec::new());
22
23pub struct Phase {
25 pub name: &'static str,
26 pub elapsed: Duration,
27 pub note: Option<String>,
29}
30
31pub fn enable_from(flag: bool) {
35 let on = flag || std::env::var_os("RQ_PROFILE").is_some();
36 ENABLED.store(on, Ordering::Relaxed);
37}
38
39pub fn enabled() -> bool {
40 ENABLED.load(Ordering::Relaxed)
41}
42
43pub fn span(name: &'static str) -> Span {
46 Span {
47 name,
48 start: enabled().then(Instant::now),
49 note: None,
50 }
51}
52
53pub fn record(name: &'static str, elapsed: Duration, note: impl FnOnce() -> String) {
56 if !enabled() {
57 return;
58 }
59 if let Ok(mut phases) = PHASES.lock() {
60 phases.push(Phase {
61 name,
62 elapsed,
63 note: Some(note()),
64 });
65 }
66}
67
68pub struct Span {
69 name: &'static str,
70 start: Option<Instant>,
71 note: Option<String>,
72}
73
74impl Span {
75 pub fn note(&mut self, f: impl FnOnce() -> String) {
78 if self.start.is_some() {
79 self.note = Some(f());
80 }
81 }
82}
83
84impl Drop for Span {
85 fn drop(&mut self) {
86 let Some(start) = self.start else { return };
87 if let Ok(mut phases) = PHASES.lock() {
88 phases.push(Phase {
89 name: self.name,
90 elapsed: start.elapsed(),
91 note: self.note.take(),
92 });
93 }
94 }
95}
96
97pub fn phases() -> Vec<Phase> {
99 PHASES
100 .lock()
101 .map(|mut p| std::mem::take(&mut *p))
102 .unwrap_or_default()
103}
104
105pub fn report(total: Duration) -> Vec<String> {
107 let phases = phases();
108 if phases.is_empty() {
109 return Vec::new();
110 }
111 let w = phases
112 .iter()
113 .map(|p| p.name.len())
114 .max()
115 .unwrap_or(5)
116 .max(5);
117 let mut out: Vec<String> = phases
118 .iter()
119 .map(|p| {
120 let note = p.note.as_deref().unwrap_or_default();
121 format!(" {:<w$} {:>8} {note}", p.name, ms(p.elapsed), w = w)
122 .trim_end()
123 .to_string()
124 })
125 .collect();
126 out.push(format!(" {:<w$} {:>8}", "─".repeat(w.min(20)), "", w = w));
127 out.push(format!(" {:<w$} {:>8}", "total", ms(total), w = w));
128 out
129}
130
131pub fn json(total: Duration) -> String {
133 let phases = phases();
134 let body: Vec<String> = phases
135 .iter()
136 .map(|p| {
137 let note = match &p.note {
138 Some(n) => format!("\"{}\"", n.replace('"', "'")),
139 None => "null".to_string(),
140 };
141 format!(
142 "{{\"name\":\"{}\",\"ms\":{:.3},\"note\":{note}}}",
143 p.name,
144 p.elapsed.as_secs_f64() * 1000.0
145 )
146 })
147 .collect();
148 format!(
149 "{{\"total_ms\":{:.3},\"phases\":[{}]}}",
150 total.as_secs_f64() * 1000.0,
151 body.join(",")
152 )
153}
154
155fn ms(d: Duration) -> String {
156 format!("{:.1}ms", d.as_secs_f64() * 1000.0)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn a_span_is_inert_when_profiling_is_off() {
165 let mut s = span("off");
166 s.note(|| panic!("the note closure must not run when disabled"));
167 drop(s);
168 record("also off", Duration::from_millis(1), || {
169 panic!("nor this one")
170 });
171 assert!(phases().is_empty());
172 }
173
174 #[test]
175 fn an_enabled_span_records_its_name_and_note() {
176 enable_from(true);
177 {
178 let mut s = span("on");
179 s.note(|| "9 candidates".to_string());
180 }
181 let recorded = phases();
182 assert_eq!(recorded.len(), 1);
183 assert_eq!(recorded[0].name, "on");
184 assert_eq!(recorded[0].note.as_deref(), Some("9 candidates"));
185 assert!(phases().is_empty(), "phases() drains");
186 ENABLED.store(false, Ordering::Relaxed);
187 }
188}