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}