1use rustc_hash::FxHashMap;
24use serde::Serialize;
25use std::io::Write;
26use std::path::Path;
27use std::time::{Duration, Instant};
28
29#[derive(Debug, Clone, Copy, Serialize)]
31pub enum Phase {
32 #[serde(rename = "B")]
34 Begin,
35 #[serde(rename = "E")]
37 End,
38 #[serde(rename = "X")]
40 Complete,
41 #[serde(rename = "i")]
43 Instant,
44 #[serde(rename = "M")]
46 Metadata,
47}
48
49#[derive(Debug, Clone, Serialize)]
51pub struct TraceEvent {
52 pub name: String,
54 pub cat: String,
56 pub ph: Phase,
58 pub ts: u64,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub dur: Option<u64>,
63 pub pid: u32,
65 pub tid: u32,
67 #[serde(skip_serializing_if = "FxHashMap::is_empty")]
69 pub args: FxHashMap<String, serde_json::Value>,
70}
71
72pub mod categories {
74 pub const PROGRAM: &str = "program";
75 pub const PARSE: &str = "parse";
76 pub const BIND: &str = "bind";
77 pub const CHECK: &str = "check";
78 pub const EMIT: &str = "emit";
79 pub const IO: &str = "io";
80 pub const MODULE_RESOLUTION: &str = "moduleResolution";
81}
82
83#[derive(Debug)]
85pub struct Tracer {
86 events: Vec<TraceEvent>,
87 start_time: Instant,
88 active_spans: FxHashMap<String, Instant>,
89 pid: u32,
90 tid: u32,
91}
92
93impl Tracer {
94 pub fn new() -> Self {
96 Self {
97 events: Vec::new(),
98 start_time: Instant::now(),
99 active_spans: FxHashMap::default(),
100 pid: std::process::id(),
101 tid: 1, }
103 }
104
105 fn timestamp(&self) -> u64 {
107 self.start_time.elapsed().as_micros() as u64
108 }
109
110 pub fn begin(&mut self, name: &str, category: &str) {
112 let ts = self.timestamp();
113 let key = format!("{category}:{name}");
114 self.active_spans.insert(key, Instant::now());
115
116 self.events.push(TraceEvent {
117 name: name.to_string(),
118 cat: category.to_string(),
119 ph: Phase::Begin,
120 ts,
121 dur: None,
122 pid: self.pid,
123 tid: self.tid,
124 args: FxHashMap::default(),
125 });
126 }
127
128 pub fn begin_with_args(
130 &mut self,
131 name: &str,
132 category: &str,
133 args: FxHashMap<String, serde_json::Value>,
134 ) {
135 let ts = self.timestamp();
136 let key = format!("{category}:{name}");
137 self.active_spans.insert(key, Instant::now());
138
139 self.events.push(TraceEvent {
140 name: name.to_string(),
141 cat: category.to_string(),
142 ph: Phase::Begin,
143 ts,
144 dur: None,
145 pid: self.pid,
146 tid: self.tid,
147 args,
148 });
149 }
150
151 pub fn end(&mut self, name: &str, category: &str) {
153 let ts = self.timestamp();
154 let key = format!("{category}:{name}");
155 self.active_spans.remove(&key);
156
157 self.events.push(TraceEvent {
158 name: name.to_string(),
159 cat: category.to_string(),
160 ph: Phase::End,
161 ts,
162 dur: None,
163 pid: self.pid,
164 tid: self.tid,
165 args: FxHashMap::default(),
166 });
167 }
168
169 pub fn complete(&mut self, name: &str, category: &str, start: Instant, duration: Duration) {
171 let ts = (start.duration_since(self.start_time)).as_micros() as u64;
172 let dur = duration.as_micros() as u64;
173
174 self.events.push(TraceEvent {
175 name: name.to_string(),
176 cat: category.to_string(),
177 ph: Phase::Complete,
178 ts,
179 dur: Some(dur),
180 pid: self.pid,
181 tid: self.tid,
182 args: FxHashMap::default(),
183 });
184 }
185
186 pub fn complete_with_args(
188 &mut self,
189 name: &str,
190 category: &str,
191 start: Instant,
192 duration: Duration,
193 args: FxHashMap<String, serde_json::Value>,
194 ) {
195 let ts = (start.duration_since(self.start_time)).as_micros() as u64;
196 let dur = duration.as_micros() as u64;
197
198 self.events.push(TraceEvent {
199 name: name.to_string(),
200 cat: category.to_string(),
201 ph: Phase::Complete,
202 ts,
203 dur: Some(dur),
204 pid: self.pid,
205 tid: self.tid,
206 args,
207 });
208 }
209
210 pub fn instant(&mut self, name: &str, category: &str) {
212 let ts = self.timestamp();
213
214 self.events.push(TraceEvent {
215 name: name.to_string(),
216 cat: category.to_string(),
217 ph: Phase::Instant,
218 ts,
219 dur: None,
220 pid: self.pid,
221 tid: self.tid,
222 args: FxHashMap::default(),
223 });
224 }
225
226 pub fn instant_with_args(
228 &mut self,
229 name: &str,
230 category: &str,
231 args: FxHashMap<String, serde_json::Value>,
232 ) {
233 let ts = self.timestamp();
234
235 self.events.push(TraceEvent {
236 name: name.to_string(),
237 cat: category.to_string(),
238 ph: Phase::Instant,
239 ts,
240 dur: None,
241 pid: self.pid,
242 tid: self.tid,
243 args,
244 });
245 }
246
247 pub fn metadata(&mut self, name: &str, args: FxHashMap<String, serde_json::Value>) {
249 self.events.push(TraceEvent {
250 name: name.to_string(),
251 cat: "__metadata".to_string(),
252 ph: Phase::Metadata,
253 ts: 0,
254 dur: None,
255 pid: self.pid,
256 tid: self.tid,
257 args,
258 });
259 }
260
261 pub fn write_to_file(&self, path: &Path) -> std::io::Result<()> {
263 if let Some(parent) = path.parent() {
265 std::fs::create_dir_all(parent)?;
266 }
267
268 let file = std::fs::File::create(path)?;
269 let mut writer = std::io::BufWriter::new(file);
270
271 serde_json::to_writer_pretty(&mut writer, &self.events)?;
273 writer.flush()?;
274
275 Ok(())
276 }
277
278 pub fn events(&self) -> &[TraceEvent] {
280 &self.events
281 }
282
283 pub fn clear(&mut self) {
285 self.events.clear();
286 self.active_spans.clear();
287 }
288}
289
290impl Default for Tracer {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296pub struct TraceSpan<'a> {
298 tracer: &'a mut Tracer,
299 name: String,
300 category: String,
301}
302
303impl<'a> TraceSpan<'a> {
304 pub fn new(tracer: &'a mut Tracer, name: &str, category: &str) -> Self {
306 tracer.begin(name, category);
307 TraceSpan {
308 tracer,
309 name: name.to_string(),
310 category: category.to_string(),
311 }
312 }
313}
314
315impl Drop for TraceSpan<'_> {
316 fn drop(&mut self) {
317 self.tracer.end(&self.name, &self.category);
318 }
319}
320
321#[macro_export]
323macro_rules! trace_span {
324 ($tracer:expr, $name:expr, $category:expr) => {
325 let _span = $crate::trace::TraceSpan::new($tracer, $name, $category);
326 };
327}
328
329#[cfg(test)]
330#[path = "trace_tests.rs"]
331mod tests;