tracing_chrometrace/
lib.rs

1//! A [Layer](https://docs.rs/tracing-subscriber/0.3.0/tracing_subscriber/layer/trait.Layer.html) that for logs formatted representations of `tracing` events viewed by the
2//! [Chrome Trace Viewer](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview) at `chrome://tracing`.
3//!
4//! # Usage
5//! ```rust
6//! use tracing_chrometrace::ChromeLayer;
7//! use tracing_subscriber::{Registry, prelude::*};
8//!
9//! let (writer, guard) = ChromeLayer::with_writer(std::io::stdout);
10//! tracing_subscriber::registry().with(writer).init();
11//! ```
12
13#![feature(thread_id_value)]
14
15use std::borrow::Cow;
16use std::marker::PhantomData;
17use std::str::FromStr;
18use std::sync::{Arc, Mutex};
19use std::{collections::HashMap, io, time::SystemTime};
20
21use crossbeam_queue::ArrayQueue;
22use derivative::Derivative;
23use derive_builder::Builder;
24use serde::{Deserialize, Serialize};
25use strum::AsRefStr;
26use strum_macros::EnumString;
27use tracing::Subscriber;
28use tracing::{span, Event};
29use tracing_subscriber::{fmt::MakeWriter, layer::Context, registry::LookupSpan, Layer};
30
31#[derive(Debug, Copy, Clone, Default, EnumString, AsRefStr, Serialize, Deserialize, PartialEq)]
32pub enum EventType {
33    #[serde(rename = "B")]
34    DurationBegin,
35    #[serde(rename = "E")]
36    DurationEnd,
37    #[serde(rename = "X")]
38    Complete,
39    #[default]
40    #[serde(rename = "i")]
41    Instant,
42    #[serde(rename = "C")]
43    Counter,
44    #[serde(rename = "b")]
45    AsyncStart,
46    #[serde(rename = "n")]
47    AsyncInstant,
48    #[serde(rename = "e")]
49    AsyncEnd,
50    #[serde(rename = "s")]
51    FlowStart,
52    #[serde(rename = "t")]
53    FlowStep,
54    #[serde(rename = "f")]
55    FlowEnd,
56    #[serde(rename = "p")]
57    Sample,
58    #[serde(rename = "N")]
59    ObjectCreated,
60    #[serde(rename = "O")]
61    ObjectSnapshot,
62    #[serde(rename = "D")]
63    ObjectDestroyed,
64    #[serde(rename = "M")]
65    Metadata,
66    #[serde(rename = "V")]
67    MemoryDumpGlobal,
68    #[serde(rename = "v")]
69    MemoryDumpProcess,
70    #[serde(rename = "R")]
71    Mark,
72    #[serde(rename = "c")]
73    ClockSync,
74    #[serde(rename = "(")]
75    ContextBegin,
76    #[serde(rename = ")")]
77    ContextEnd,
78}
79
80#[derive(Derivative, Serialize, Deserialize, Builder, Debug)]
81#[derivative(PartialEq)]
82#[builder(custom_constructor)]
83#[builder(derive(Debug))]
84pub struct ChromeEvent {
85    #[builder(setter(custom))]
86    #[serde(default = "SystemTime::now")]
87    #[serde(skip)]
88    #[allow(unused)]
89    #[derivative(PartialEq = "ignore")]
90    start: SystemTime,
91    #[builder(default)]
92    #[builder(setter(into))]
93    pub name: Cow<'static, str>,
94    #[builder(default)]
95    #[builder(setter(into))]
96    pub cat: Cow<'static, str>,
97    #[builder(default)]
98    pub ph: EventType,
99    #[builder(
100        default = "SystemTime::now().duration_since(self.start.unwrap()).unwrap().as_nanos() as f64 / 1000.0"
101    )]
102    pub ts: f64,
103    #[builder(default)]
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub dur: Option<f64>,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    #[builder(default)]
108    pub tts: Option<f64>,
109    #[builder(default)]
110    #[builder(setter(into))]
111    #[serde(default, skip_serializing_if = "str::is_empty")]
112    pub id: Cow<'static, str>,
113    #[builder(default = "std::process::id().into()")]
114    pub pid: u64,
115    #[builder(default = "std::thread::current().id().as_u64().into()")]
116    pub tid: u64,
117    #[builder(default, setter(each = "arg"))]
118    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
119    pub args: HashMap<String, String>,
120}
121
122impl ChromeEvent {
123    pub fn builder(start: SystemTime) -> ChromeEventBuilder {
124        ChromeEventBuilder {
125            start: Some(start),
126            ..ChromeEventBuilder::create_empty()
127        }
128    }
129}
130
131#[derive(Debug)]
132pub struct ChromeLayer<S, W = fn() -> std::io::Stdout> {
133    pub start: SystemTime,
134    make_writer: W,
135    events: Arc<Mutex<ArrayQueue<String>>>,
136    _inner: PhantomData<S>,
137}
138
139#[derive(Clone, Debug)]
140pub struct ChromeWriter<W>
141where
142    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
143{
144    make_writer: W,
145}
146
147impl<W> ChromeWriter<W>
148where
149    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
150{
151    fn new(make_writer: W, events: Arc<Mutex<ArrayQueue<String>>>) -> (Self, ChromeWriterGuard<W>) {
152        (
153            Self {
154                make_writer: make_writer.clone(),
155            },
156            ChromeWriterGuard::new(make_writer, events),
157        )
158    }
159}
160
161impl<W> io::Write for ChromeWriter<W>
162where
163    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
164{
165    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
166        let mut writer = self.make_writer.make_writer();
167        io::Write::write(&mut writer, buf)
168    }
169
170    fn flush(&mut self) -> io::Result<()> {
171        Ok(())
172    }
173}
174
175impl<'a, W> MakeWriter<'a> for ChromeWriter<W>
176where
177    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
178{
179    type Writer = ChromeWriter<W>;
180
181    fn make_writer(&'a self) -> Self::Writer {
182        self.clone()
183    }
184}
185
186pub struct ChromeWriterGuard<W>
187where
188    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
189{
190    make_writer: W,
191    events: Arc<Mutex<ArrayQueue<String>>>,
192}
193
194impl<W> ChromeWriterGuard<W>
195where
196    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
197{
198    fn new(make_writer: W, events: Arc<Mutex<ArrayQueue<String>>>) -> Self {
199        // Write JSON opening parenthesis
200        io::Write::write_all(&mut make_writer.make_writer(), b"[\n").unwrap();
201
202        Self {
203            make_writer,
204            events,
205        }
206    }
207}
208
209impl<W> Drop for ChromeWriterGuard<W>
210where
211    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
212{
213    fn drop(&mut self) {
214        let mut writer = self.make_writer.make_writer();
215
216        let mut write = |event: String, is_last: bool| {
217            let mut buf = String::with_capacity(event.len() + 1 /* Newline */ + 1 /* Null */);
218            buf.push_str(&event);
219            if !is_last {
220                buf.push(',');
221            }
222            buf.push('\n');
223
224            io::Write::write_all(&mut writer, buf.as_bytes()).unwrap();
225        };
226
227        if let Ok(lock) = self.events.lock() {
228            let events = &*lock;
229            // Write until last one left
230            while events.len() > 1 {
231                write(events.pop().unwrap(), false);
232            }
233            // Last one
234            if let Some(event) = events.pop() {
235                write(event, true);
236            }
237        }
238
239        // Write JSON closing parenthesis
240        io::Write::write_all(&mut writer, b"]\n").unwrap();
241    }
242}
243
244impl<S, W> ChromeLayer<S, W>
245where
246    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
247{
248    pub fn with_writer(make_writer: W) -> (ChromeLayer<S, ChromeWriter<W>>, ChromeWriterGuard<W>) {
249        let events = Arc::new(Mutex::new(ArrayQueue::new(1)));
250        let (make_writer, guard) = ChromeWriter::new(make_writer, events.clone());
251        (
252            ChromeLayer {
253                start: SystemTime::now(),
254                make_writer,
255                events,
256                _inner: PhantomData,
257            },
258            guard,
259        )
260    }
261
262    fn write(&self, writer: &mut dyn io::Write, event: ChromeEvent) -> io::Result<()> {
263        let current = serde_json::to_string(&event).unwrap();
264
265        // Proceed only when previous event exists
266        if let Some(event) = self.events.lock().unwrap().force_push(current) {
267            let mut buf = String::with_capacity(
268                event.len() + 1 /* Comma */ + 1 /* Newline */ + 1, /* Null */
269            );
270            buf.push_str(&event);
271            buf.push(',');
272            buf.push('\n');
273            io::Write::write_all(writer, buf.as_bytes())
274        } else {
275            Ok(())
276        }
277    }
278}
279
280struct ChromeEventVisitor {
281    builder: ChromeEventBuilder,
282    event: Option<String>,
283}
284
285impl tracing_subscriber::field::Visit for ChromeEventVisitor {
286    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
287        let value = format!("{value:?}").trim_matches('"').to_string();
288        let name = field.name();
289
290        match name {
291            "name" => {
292                self.builder.name(value);
293            }
294            "cat" => {
295                self.builder.cat(value);
296            }
297            "id" => {
298                self.builder.id(value);
299            }
300            "ph" => {
301                self.builder.ph(EventType::from_str(&value)
302                    .unwrap_or_else(|_| panic!("Invalid EventType: {}", value)));
303            }
304            "ts" => {
305                self.builder.ts(value.parse().expect("Invalid timestamp"));
306            }
307            "dur" => {
308                self.builder
309                    .dur(Some(value.parse().expect("Invalid timestamp")));
310            }
311            "tts" => {
312                self.builder
313                    .tts(Some(value.parse().expect("Invalid timestamp")));
314            }
315            "pid" => {
316                self.builder.pid(value.parse().unwrap());
317            }
318            "tid" => {
319                self.builder.tid(value.parse().unwrap());
320            }
321            "event" => {
322                // Special keyword to annotate event type
323                self.event = Some(value);
324            }
325            arg => {
326                self.builder.arg((arg.to_string(), value));
327            }
328        }
329    }
330}
331
332struct AsyncEntered(bool);
333
334impl<S, W> Layer<S> for ChromeLayer<S, W>
335where
336    S: Subscriber + for<'span> LookupSpan<'span>,
337    W: Clone + for<'writer> MakeWriter<'writer> + 'static,
338{
339    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
340        let span = ctx.span(id).expect("Span not found, this is a bug");
341
342        let mut visitor = ChromeEventVisitor {
343            builder: ChromeEvent::builder(self.start),
344            event: None,
345        };
346        attrs.record(&mut visitor);
347
348        span.extensions_mut().insert(visitor);
349    }
350
351    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
352        let mut visitor = ChromeEventVisitor {
353            builder: ChromeEvent::builder(self.start),
354            event: None,
355        };
356
357        // Default event type
358        visitor.builder.ph(EventType::Instant);
359
360        event.record(&mut visitor);
361
362        self.write(
363            &mut self.make_writer.make_writer(),
364            visitor.builder.build().unwrap(),
365        )
366        .expect("Failed to write event in tracing-chrometrace");
367    }
368
369    fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
370        let span = ctx.span(id).expect("Span not found, this is a bug");
371
372        let mut extensions = span.extensions_mut();
373
374        if extensions.get_mut::<AsyncEntered>().is_some() {
375            // If recoding of the span is already started (async case), skip it
376            return;
377        } else {
378            extensions.insert(AsyncEntered(true));
379        }
380
381        if let Some(visitor) = extensions.get_mut::<ChromeEventVisitor>() {
382            // Only "async" event suppported now
383            if visitor
384                .event
385                .as_ref()
386                .map(|event| event == "async")
387                .unwrap_or(false)
388            {
389                visitor.builder.ph(EventType::AsyncStart);
390            } else {
391                visitor.builder.ph(EventType::DurationBegin);
392            }
393
394            self.write(
395                &mut self.make_writer.make_writer(),
396                visitor.builder.build().unwrap(),
397            )
398            .expect("Failed to write event in tracing-chrometrace");
399        }
400    }
401
402    fn on_exit(&self, _id: &span::Id, _ctx: Context<'_, S>) {}
403
404    fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
405        let span = ctx.span(&id).expect("Span not found, this is a bug");
406
407        let mut extensions = span.extensions_mut();
408
409        if let Some(visitor) = extensions.get_mut::<ChromeEventVisitor>() {
410            if visitor
411                .event
412                .as_ref()
413                .map(|event| event == "async")
414                .unwrap_or(false)
415            {
416                visitor.builder.ph(EventType::AsyncEnd);
417            } else {
418                visitor.builder.ph(EventType::DurationEnd);
419            }
420
421            self.write(
422                &mut self.make_writer.make_writer(),
423                visitor.builder.build().unwrap(),
424            )
425            .expect("Failed to write event in tracing-chrometrace");
426        }
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433
434    #[test]
435    fn event_stringify() {
436        let event = EventType::from_str("DurationBegin").unwrap();
437        matches!(event, EventType::DurationBegin);
438    }
439
440    #[test]
441    fn test_serde() {
442        let event = ChromeEvent::builder(SystemTime::now())
443            .arg(("a".to_string(), "a".to_string()))
444            .ts(1.0)
445            .build()
446            .unwrap();
447
448        let serialized = serde_json::to_string(&event).unwrap();
449
450        let deserialized: ChromeEvent = serde_json::from_str(&serialized).unwrap();
451
452        assert_eq!(event, deserialized);
453    }
454}