turbo_trace_server/reader/
nextjs.rs1use 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 }