chrometracer/
tracer.rs

1use crossbeam_channel::Sender;
2use crossbeam_queue::ArrayQueue;
3use derive_builder::Builder;
4use std::{
5    cell::RefCell,
6    fs::File,
7    io,
8    thread::{self, JoinHandle},
9    time::SystemTime,
10};
11use tracing_chrometrace::{ChromeEvent, ChromeEventBuilder, EventType};
12
13thread_local! {
14    static CURRENT: RefCell<Option<ChromeTracer>> = RefCell::new(None);
15}
16
17static mut GLOBAL: Option<ChromeTracer> = None;
18
19#[derive(Builder, Clone)]
20#[builder(custom_constructor, build_fn(private, name = "_build"))]
21pub struct ChromeTracer {
22    #[builder(default = "SystemTime::now()")]
23    pub start: SystemTime,
24
25    #[builder(setter(skip))]
26    sender: Option<Sender<ChromeTracerMessage>>,
27}
28
29#[allow(clippy::large_enum_variant)]
30enum ChromeTracerMessage {
31    ChromeEvent(ChromeEvent),
32    Terminate,
33}
34
35pub struct ChromeTracerGuard {
36    sender: Sender<ChromeTracerMessage>,
37    handle: Option<JoinHandle<()>>,
38}
39
40impl Drop for ChromeTracerGuard {
41    fn drop(&mut self) {
42        self.sender.send(ChromeTracerMessage::Terminate).unwrap();
43        self.handle.take().map(JoinHandle::join).unwrap().unwrap();
44    }
45}
46
47impl ChromeTracerBuilder {
48    pub fn init(&self) -> ChromeTracerGuard {
49        CURRENT.with(|c| {
50            if unsafe { GLOBAL.is_some() } {
51                panic!("Unable to intialize ChromeTracer. A chrometracer already been set");
52            } else {
53                let mut tracer = self._build().expect("All required fields were initialized");
54                let guard = tracer.init();
55
56                unsafe { GLOBAL = Some(tracer.clone()) };
57                *c.borrow_mut() = Some(tracer);
58
59                guard
60            }
61        })
62    }
63}
64
65pub fn builder() -> ChromeTracerBuilder {
66    ChromeTracerBuilder::create_empty()
67}
68
69impl ChromeTracer {
70    fn init(&mut self) -> ChromeTracerGuard {
71        let (sender, receiver) = crossbeam_channel::unbounded();
72        self.sender = Some(sender.clone());
73
74        let handle = Some(thread::spawn(move || {
75            let mut file = File::create("trace.json").unwrap();
76            let queue = ArrayQueue::new(1);
77
78            io::Write::write_all(&mut file, b"[\n").unwrap();
79
80            while let Ok(ChromeTracerMessage::ChromeEvent(event)) = receiver.recv() {
81                let s = serde_json::to_string(&event).unwrap();
82                if let Some(e) = queue.force_push(s) {
83                    io::Write::write_all(&mut file, e.as_bytes()).unwrap();
84                    io::Write::write_all(&mut file, b",\n").unwrap();
85                };
86            }
87
88            if let Some(e) = queue.pop() {
89                io::Write::write_all(&mut file, e.as_bytes()).unwrap();
90                io::Write::write_all(&mut file, b"\n").unwrap();
91            }
92
93            io::Write::write_all(&mut file, b"]").unwrap();
94        }));
95
96        ChromeTracerGuard { sender, handle }
97    }
98
99    pub fn trace(&self, event: ChromeEvent) {
100        let _ = self
101            .sender
102            .as_ref()
103            .map(|sender| sender.send(ChromeTracerMessage::ChromeEvent(event)));
104    }
105}
106
107pub fn current<T, F>(mut f: F) -> T
108where
109    F: FnMut(Option<&ChromeTracer>) -> T,
110{
111    CURRENT.with(|c| {
112        let mut tracer = c.borrow_mut();
113        if tracer.is_none() {
114            *tracer = unsafe { GLOBAL.clone() };
115        }
116
117        f(tracer.as_ref())
118    })
119}
120
121#[macro_export]
122macro_rules! event {
123    ($($key:ident = $value:expr),*) => {
124
125        $crate::current(|tracer| {
126            if let Some(tracer) = tracer {
127                use $crate::Recordable as _;
128
129                let mut builder = $crate::ChromeEvent::builder(tracer.start);
130
131                $(
132                    $value.record(&mut builder, stringify!($key));
133                )*
134
135                let event = builder.build().unwrap();
136                tracer.trace(event);
137            }
138        })
139    };
140}
141
142pub trait Recordable {
143    type Item;
144
145    fn record(self, builder: &mut ChromeEventBuilder, name: &'static str);
146}
147
148impl Recordable for u64 {
149    type Item = u64;
150
151    fn record(self, builder: &mut ChromeEventBuilder, name: &'static str) {
152        match name {
153            "tid" => builder.tid(self),
154            "pid" => builder.pid(self),
155            _ => builder.arg((name.to_string(), self.to_string())),
156        };
157    }
158}
159
160impl Recordable for &'static str {
161    type Item = &'static str;
162
163    fn record(self, builder: &mut ChromeEventBuilder, name: &'static str) {
164        match name {
165            "name" => builder.name(self),
166            "cat" => builder.cat(self),
167            "id" => builder.id(self),
168            _ => builder.arg((name.to_string(), self.to_string())),
169        };
170    }
171}
172
173impl Recordable for String {
174    type Item = String;
175
176    fn record(self, builder: &mut ChromeEventBuilder, name: &'static str) {
177        match name {
178            "name" => builder.name(self),
179            "cat" => builder.cat(self),
180            "id" => builder.id(self),
181            _ => builder.arg((name.to_string(), self)),
182        };
183    }
184}
185
186impl Recordable for f64 {
187    type Item = f64;
188
189    fn record(self, builder: &mut ChromeEventBuilder, name: &'static str) {
190        match name {
191            "ts" => builder.ts(self),
192            "dur" => builder.dur(Some(self)),
193            "tts" => builder.tts(Some(self)),
194            _ => builder.arg((name.to_string(), self.to_string())),
195        };
196    }
197}
198
199impl Recordable for EventType {
200    type Item = EventType;
201
202    fn record(self, builder: &mut ChromeEventBuilder, name: &'static str) {
203        match name {
204            "ph" => builder.ph(self),
205            _ => builder.arg((name.to_string(), self.as_ref().to_string())),
206        };
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    #[test]
213    fn event() {
214        crate::builder().init();
215
216        event!(name = "hello");
217    }
218
219    #[test]
220    fn without_init() {
221        event!(name = "hello");
222    }
223}