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