Skip to main content

turbo_trace_server/reader/
nextjs.rs

1use std::{borrow::Cow, fmt::Display, sync::Arc};
2
3use rustc_hash::{FxHashMap, FxHashSet};
4use serde::Deserialize;
5
6use super::TraceFormat;
7use crate::{FxIndexMap, span::SpanIndex, store_container::StoreContainer, timestamp::Timestamp};
8
9pub struct NextJsFormat {
10    store: Arc<StoreContainer>,
11    id_mapping: FxHashMap<u64, SpanIndex>,
12    queued_children: FxHashMap<u64, Vec<SpanIndex>>,
13}
14
15impl NextJsFormat {
16    pub fn new(store: Arc<StoreContainer>) -> Self {
17        Self {
18            store,
19            id_mapping: FxHashMap::default(),
20            queued_children: FxHashMap::default(),
21        }
22    }
23}
24
25impl TraceFormat for NextJsFormat {
26    type Reused = ();
27
28    fn read(&mut self, mut buffer: &[u8], _reuse: &mut Self::Reused) -> anyhow::Result<usize> {
29        let mut bytes_read = 0;
30        let mut outdated_spans = FxHashSet::default();
31        while let Some(line_end) = buffer.iter().position(|b| *b == b'\n') {
32            let line = &buffer[..line_end];
33            buffer = &buffer[line_end + 1..];
34            bytes_read += line.len() + 1;
35
36            let spans: Vec<NextJsSpan> = serde_json::from_slice(line)?;
37
38            let mut store = self.store.write();
39
40            for span in spans {
41                let NextJsSpan {
42                    name,
43                    duration,
44                    timestamp,
45                    id,
46                    parent_id,
47                    tags,
48                    start_time: _,
49                    trace_id: _,
50                } = span;
51                let timestamp = Timestamp::from_micros(timestamp);
52                let duration = Timestamp::from_micros(duration);
53                let (parent, queue_parent) = if let Some(parent) = parent_id {
54                    if let Some(parent) = self.id_mapping.get(&parent) {
55                        (Some(*parent), None)
56                    } else {
57                        (None, Some(parent))
58                    }
59                } else {
60                    (None, None)
61                };
62                let index = store.add_span(
63                    parent,
64                    timestamp,
65                    "nextjs".to_string(),
66                    name.into_owned(),
67                    tags.iter()
68                        .map(|(k, v)| {
69                            (
70                                k.to_string(),
71                                v.as_ref().map(|v| v.to_string()).unwrap_or_default(),
72                            )
73                        })
74                        .collect(),
75                    &mut outdated_spans,
76                );
77                self.id_mapping.insert(id, index);
78                if let Some(parent) = queue_parent {
79                    self.queued_children.entry(parent).or_default().push(index);
80                }
81                if let Some(children) = self.queued_children.remove(&id) {
82                    for child in children {
83                        store.set_parent(child, index, &mut outdated_spans);
84                    }
85                }
86                store.set_total_time(index, timestamp, duration, &mut outdated_spans);
87                store.complete_span(index);
88            }
89            store.invalidate_outdated_spans(&outdated_spans);
90            drop(store);
91        }
92        Ok(bytes_read)
93    }
94}
95
96#[derive(Debug, Deserialize)]
97#[serde(untagged)]
98enum TagValue<'a> {
99    String(Cow<'a, str>),
100    Number(f64),
101    Bool(bool),
102    Array(Vec<TagValue<'a>>),
103}
104
105impl Display for TagValue<'_> {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match self {
108            TagValue::String(s) => write!(f, "{s}"),
109            TagValue::Number(n) => write!(f, "{n}"),
110            TagValue::Bool(b) => write!(f, "{b}"),
111            TagValue::Array(a) => {
112                write!(f, "[")?;
113                for (i, v) in a.iter().enumerate() {
114                    if i > 0 {
115                        write!(f, ", ")?;
116                    }
117                    write!(f, "{v}")?;
118                }
119                write!(f, "]")
120            }
121        }
122    }
123}
124
125#[derive(Debug, Deserialize)]
126#[serde(rename_all = "camelCase")]
127struct NextJsSpan<'a> {
128    name: Cow<'a, str>,
129    duration: u64,
130    timestamp: u64,
131    id: u64,
132    parent_id: Option<u64>,
133    tags: FxIndexMap<Cow<'a, str>, Option<TagValue<'a>>>,
134    #[allow(dead_code)]
135    start_time: u64,
136    #[allow(dead_code)]
137    trace_id: Cow<'a, str>,
138}