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