1use once_cell::sync::Lazy;
2use std::collections::HashMap;
3use std::sync::Mutex;
4use std::time::Instant;
5
6use crate::vscode::{Envelope, Timing};
7
8static REC: Lazy<Mutex<Recorder>> = Lazy::new(|| Mutex::new(Recorder::default()));
10
11#[derive(Default)]
12struct Recorder {
13 markers: Vec<Marker>,
15 name_to_id: HashMap<String, u32>,
17 next_id: u32,
19
20 spans: Vec<(String, u128)>,
22}
23
24#[derive(Clone)]
25struct Marker {
26 #[allow(unused)]
27 id: u32,
28 name: String,
29 t: Instant,
30}
31
32impl Recorder {
33 fn assign_id(&mut self, name: &str) -> u32 {
34 if let Some(&id) = self.name_to_id.get(name) {
35 return id;
36 }
37 let id = self.next_id;
38 self.next_id += 1;
39 self.name_to_id.insert(name.to_string(), id);
40 id
41 }
42
43 fn find_marker_index_by_name(&self, name: &str) -> Option<usize> {
44 self.markers.iter().position(|m| m.name == name)
45 }
46}
47
48pub fn mark(name: impl Into<String>) {
53 let name = name.into();
54 let mut r = REC.lock().unwrap();
55 let id = r.assign_id(&name);
56 r.markers.push(Marker { id, name, t: Instant::now() });
57}
58
59pub fn span(label: impl Into<String>) -> SpanGuard {
61 SpanGuard {
62 label: label.into(),
63 start: Instant::now(),
64 committed: false,
65 }
66}
67
68pub fn span_between(start_name: &str, end_name: &str, record_phase: bool) -> Option<u128> {
71 let mut r = REC.lock().unwrap();
72 let si = r.find_marker_index_by_name(start_name)?;
73 let ei = r.find_marker_index_by_name(end_name)?;
74 if ei <= si {
75 return None;
76 }
77 let s = r.markers[si].t;
78 let e = r.markers[ei].t;
79 let nanos = (e - s).as_nanos();
80 if record_phase {
81 r.spans.push((format!("span:{start_name}->{end_name}"), nanos));
82 }
83 Some(nanos)
84}
85
86pub fn list_markers() -> Vec<String> {
88 let r = REC.lock().unwrap();
89 r.markers.iter().map(|m| m.name.clone()).collect()
90}
91
92pub fn span_between_markers(
95 start_name: &str,
96 end_name: &str,
97 label: impl Into<String>,
98) -> Option<u128> {
99 let mut r = REC.lock().unwrap();
100 let si = r.find_marker_index_by_name(start_name)?;
101 let ei = r.find_marker_index_by_name(end_name)?;
102 if ei <= si { return None; }
103 let nanos = (r.markers[ei].t - r.markers[si].t).as_nanos();
104 r.spans.push((label.into(), nanos));
105 Some(nanos)
106}
107
108pub fn span_between_indices(
111 start_idx: usize,
112 end_idx: usize,
113 label: impl Into<String>,
114) -> Option<u128> {
115 let mut r = REC.lock().unwrap();
116 if start_idx >= r.markers.len() || end_idx >= r.markers.len() || end_idx <= start_idx {
117 return None;
118 }
119 let nanos = (r.markers[end_idx].t - r.markers[start_idx].t).as_nanos();
120 r.spans.push((label.into(), nanos));
121 Some(nanos)
122}
123
124pub fn take_as_timings() -> Vec<Timing> {
130 let mut r = REC.lock().unwrap();
131
132 let mut out: Vec<Timing> = Vec::new();
133
134 if !r.markers.is_empty() {
135 let first = &r.markers[0];
137 for (i, m) in r.markers.iter().enumerate() {
138 let nanos = (m.t - first.t).as_nanos();
139 let name = if i == 0 {
140 format!("cum:{}->{}", m.name, m.name)
141 } else {
142 format!("cum:{}->{}", first.name, m.name)
143 };
144 out.push(Timing { name, nanos });
145 }
146
147 for w in r.markers.windows(2) {
149 let a = &w[0];
150 let b = &w[1];
151 let nanos = (b.t - a.t).as_nanos();
152 out.push(Timing {
153 name: format!("inc:{}->{}", a.name, b.name),
154 nanos,
155 });
156 }
157 }
158
159 for (label, nanos) in r.spans.drain(..) {
161 out.push(Timing { name: label, nanos });
162 }
163
164 r.markers.clear();
166 r.name_to_id.clear();
167 r.next_id = 0;
168
169 out
170}
171
172pub fn cd <T>(mut env: Envelope<T>) -> Envelope<T> {
174 let timings = take_as_timings();
175 if !timings.is_empty() {
176 if env.timings.is_empty() {
177 env.timings = timings;
178 } else {
179 env.timings.extend(timings);
180 }
181 }
182 env
183}
184
185pub struct SpanGuard {
188 label: String,
189 start: Instant,
190 committed: bool,
191}
192
193impl SpanGuard {
194 pub fn end(mut self) {
196 if !self.committed {
197 let nanos = self.start.elapsed().as_nanos();
198 REC.lock().unwrap().spans.push((format!("span:{}", self.label), nanos));
199 self.committed = true;
200 }
201 }
202}
203
204impl Drop for SpanGuard {
205 fn drop(&mut self) {
206 if !self.committed {
207 let nanos = self.start.elapsed().as_nanos();
208 REC.lock().unwrap().spans.push((format!("span:{}", self.label), nanos));
209 self.committed = true;
210 }
211 }
212}