Skip to main content

turbo_trace_server/reader/
turbopack.rs

1use std::{
2    borrow::Cow,
3    collections::hash_map::Entry,
4    mem::transmute,
5    ops::{Deref, DerefMut},
6    sync::Arc,
7};
8
9use anyhow::Result;
10use rustc_hash::{FxHashMap, FxHashSet};
11use turbo_rcstr::{RcStr, RcStrInterning, rcstr};
12use turbopack_trace_utils::tracing::{TraceRow, TraceValue};
13
14use super::TraceFormat;
15use crate::{
16    span::{SpanArgs, SpanIndex},
17    store_container::{StoreContainer, StoreWriteGuard},
18    timestamp::Timestamp,
19};
20
21#[derive(Default)]
22struct AllocationInfo {
23    allocations: u64,
24    deallocations: u64,
25    allocation_count: u64,
26    deallocation_count: u64,
27}
28
29struct InternalRow {
30    id: Option<u64>,
31    ty: InternalRowType,
32}
33
34enum InternalRowType {
35    Start {
36        new_id: u64,
37        ts: Timestamp,
38        name: RcStr,
39        target: RcStr,
40        values: SpanArgs,
41    },
42    End,
43    SelfTime {
44        start: Timestamp,
45        end: Timestamp,
46    },
47    Event {
48        ts: Timestamp,
49        /// Pre-extracted at parse time from the typed `TraceValue::UInt`;
50        /// the runtime `values` vec no longer carries it.
51        duration: Timestamp,
52        /// Pre-extracted at parse time from the typed `TraceValue::String`;
53        /// defaults to `"event"` when not present.
54        name: RcStr,
55        values: SpanArgs,
56    },
57    Record {
58        values: SpanArgs,
59    },
60    Allocation {
61        allocations: u64,
62        allocation_count: u64,
63    },
64    Deallocation {
65        deallocations: u64,
66        deallocation_count: u64,
67    },
68}
69
70pub struct TurbopackFormat {
71    store: Arc<StoreContainer>,
72    id_mapping: FxHashMap<u64, SpanIndex>,
73    dropped_ids: FxHashSet<u64>,
74    remaining_ids_to_drop: usize,
75    queued_rows: FxHashMap<u64, Vec<InternalRow>>,
76    outdated_spans: FxHashSet<SpanIndex>,
77    thread_stacks: FxHashMap<u64, Vec<u64>>,
78    thread_allocation_counters: FxHashMap<u64, AllocationInfo>,
79    self_time_started: FxHashMap<(u64, u64), Timestamp>,
80    interner: RcStrInterning,
81}
82
83impl TurbopackFormat {
84    pub fn new(store: Arc<StoreContainer>) -> Self {
85        let drop_ids = std::env::var("DROP_SPANS")
86            .ok()
87            .and_then(|v| v.parse::<usize>().ok())
88            .unwrap_or_default();
89        Self {
90            store,
91            id_mapping: FxHashMap::with_capacity_and_hasher(131_072, Default::default()),
92            dropped_ids: FxHashSet::with_capacity_and_hasher(drop_ids, Default::default()),
93            remaining_ids_to_drop: drop_ids,
94            queued_rows: FxHashMap::with_capacity_and_hasher(1_024, Default::default()),
95            outdated_spans: FxHashSet::with_capacity_and_hasher(8_192, Default::default()),
96            thread_stacks: FxHashMap::with_capacity_and_hasher(64, Default::default()),
97            thread_allocation_counters: FxHashMap::with_capacity_and_hasher(64, Default::default()),
98            self_time_started: FxHashMap::with_capacity_and_hasher(256, Default::default()),
99            interner: RcStrInterning::new(),
100        }
101    }
102
103    fn intern_span_args(&mut self, values: Vec<(Cow<'_, str>, TraceValue<'_>)>) -> SpanArgs {
104        values
105            .into_iter()
106            .map(|(k, v)| {
107                let k = self.interner.intern_cow(k);
108                let v = match v {
109                    TraceValue::String(s) => self.interner.intern_cow(s),
110                    other => self.interner.intern_display(&other),
111                };
112                (k, v)
113            })
114            .collect()
115    }
116
117    fn process(&mut self, store: &mut StoreWriteGuard, row: TraceRow<'_>) {
118        match row {
119            TraceRow::Start {
120                ts,
121                id,
122                parent,
123                name,
124                target,
125                values,
126            } => {
127                let ts = Timestamp::from_micros(ts);
128                let name = self.interner.intern_cow(name);
129                let target = self.interner.intern_cow(target);
130                let values = self.intern_span_args(values);
131                self.process_internal_row(
132                    store,
133                    InternalRow {
134                        id: parent,
135                        ty: InternalRowType::Start {
136                            ts,
137                            new_id: id,
138                            name,
139                            target,
140                            values,
141                        },
142                    },
143                );
144            }
145            TraceRow::Record { id, values } => {
146                let values = self.intern_span_args(values);
147                self.process_internal_row(
148                    store,
149                    InternalRow {
150                        id: Some(id),
151                        ty: InternalRowType::Record { values },
152                    },
153                );
154            }
155            TraceRow::End { ts: _, id } => {
156                self.process_internal_row(
157                    store,
158                    InternalRow {
159                        id: Some(id),
160                        ty: InternalRowType::End,
161                    },
162                );
163            }
164            TraceRow::Enter { ts, id, thread_id } => {
165                let ts = Timestamp::from_micros(ts);
166                let stack = self.thread_stacks.entry(thread_id).or_default();
167                if let Some(&parent) = stack.last() {
168                    if let Some(parent_start) = self.self_time_started.remove(&(parent, thread_id))
169                    {
170                        stack.push(id);
171                        self.process_internal_row(
172                            store,
173                            InternalRow {
174                                id: Some(parent),
175                                ty: InternalRowType::SelfTime {
176                                    start: parent_start,
177                                    end: ts,
178                                },
179                            },
180                        );
181                    } else {
182                        stack.push(id);
183                    }
184                } else {
185                    stack.push(id);
186                }
187                self.self_time_started.insert((id, thread_id), ts);
188            }
189            TraceRow::Exit { ts, id, thread_id } => {
190                let ts = Timestamp::from_micros(ts);
191                let stack = self.thread_stacks.entry(thread_id).or_default();
192                if let Some(pos) = stack.iter().rev().position(|&x| x == id) {
193                    let stack_index = stack.len() - pos - 1;
194                    stack.remove(stack_index);
195                    if stack_index > 0 {
196                        let parent = stack[stack_index - 1];
197                        self.self_time_started.insert((parent, thread_id), ts);
198                    }
199                }
200                if let Some(start) = self.self_time_started.remove(&(id, thread_id)) {
201                    self.process_internal_row(
202                        store,
203                        InternalRow {
204                            id: Some(id),
205                            ty: InternalRowType::SelfTime { start, end: ts },
206                        },
207                    );
208                }
209            }
210            TraceRow::Event { ts, parent, values } => {
211                let ts = Timestamp::from_micros(ts);
212                // Pull `duration` and `name` out of the typed values during
213                // interning
214                let mut duration = Timestamp::ZERO;
215                let mut name = rcstr!("event");
216                let mut interned_values: SpanArgs = SpanArgs::with_capacity(values.len());
217                for (k, v) in values {
218                    match k.as_ref() {
219                        "duration" => {
220                            duration = Timestamp::from_micros(v.as_u64().unwrap_or(0));
221                        }
222                        "name" => {
223                            if let TraceValue::String(s) = v {
224                                name = self.interner.intern_cow(s);
225                            }
226                        }
227                        _ => {
228                            let k = self.interner.intern_cow(k);
229                            let v = match v {
230                                TraceValue::String(s) => self.interner.intern_cow(s),
231                                other => self.interner.intern_display(&other),
232                            };
233                            interned_values.push((k, v));
234                        }
235                    }
236                }
237                self.process_internal_row(
238                    store,
239                    InternalRow {
240                        id: parent,
241                        ty: InternalRowType::Event {
242                            ts,
243                            duration,
244                            name,
245                            values: interned_values,
246                        },
247                    },
248                );
249            }
250            TraceRow::Allocation {
251                ts: _,
252                thread_id,
253                allocations,
254                allocation_count,
255                deallocations,
256                deallocation_count,
257            } => {
258                let stack = self.thread_stacks.entry(thread_id).or_default();
259                if let Some(&id) = stack.last() {
260                    if allocations > 0 {
261                        self.process_internal_row(
262                            store,
263                            InternalRow {
264                                id: Some(id),
265                                ty: InternalRowType::Allocation {
266                                    allocations,
267                                    allocation_count,
268                                },
269                            },
270                        );
271                    }
272                    if deallocations > 0 {
273                        self.process_internal_row(
274                            store,
275                            InternalRow {
276                                id: Some(id),
277                                ty: InternalRowType::Deallocation {
278                                    deallocations,
279                                    deallocation_count,
280                                },
281                            },
282                        );
283                    }
284                }
285            }
286            TraceRow::MemorySample {
287                ts,
288                memory,
289                memory_pressure,
290            } => {
291                let ts = Timestamp::from_micros(ts);
292                store.add_memory_sample(ts, memory, memory_pressure);
293            }
294            TraceRow::AllocationCounters {
295                ts: _,
296                thread_id,
297                allocations,
298                allocation_count,
299                deallocations,
300                deallocation_count,
301            } => {
302                let info = AllocationInfo {
303                    allocations,
304                    deallocations,
305                    allocation_count,
306                    deallocation_count,
307                };
308                let mut diff = AllocationInfo::default();
309                match self.thread_allocation_counters.entry(thread_id) {
310                    Entry::Occupied(mut entry) => {
311                        let counter = entry.get_mut();
312                        diff.allocations = info.allocations - counter.allocations;
313                        diff.deallocations = info.deallocations - counter.deallocations;
314                        diff.allocation_count = info.allocation_count - counter.allocation_count;
315                        diff.deallocation_count =
316                            info.deallocation_count - counter.deallocation_count;
317                        counter.allocations = info.allocations;
318                        counter.deallocations = info.deallocations;
319                        counter.allocation_count = info.allocation_count;
320                        counter.deallocation_count = info.deallocation_count;
321                    }
322                    Entry::Vacant(entry) => {
323                        entry.insert(info);
324                    }
325                }
326                let stack = self.thread_stacks.entry(thread_id).or_default();
327                if let Some(&id) = stack.last() {
328                    if diff.allocations > 0 {
329                        self.process_internal_row(
330                            store,
331                            InternalRow {
332                                id: Some(id),
333                                ty: InternalRowType::Allocation {
334                                    allocations: diff.allocations,
335                                    allocation_count: diff.allocation_count,
336                                },
337                            },
338                        );
339                    }
340                    if diff.deallocations > 0 {
341                        self.process_internal_row(
342                            store,
343                            InternalRow {
344                                id: Some(id),
345                                ty: InternalRowType::Deallocation {
346                                    deallocations: diff.deallocations,
347                                    deallocation_count: diff.deallocation_count,
348                                },
349                            },
350                        );
351                    }
352                }
353            }
354        }
355    }
356
357    fn process_internal_row(&mut self, store: &mut StoreWriteGuard, row: InternalRow) {
358        let id = if let Some(id) = row.id {
359            if matches!(
360                row.ty,
361                InternalRowType::End
362                    | InternalRowType::Event { .. }
363                    | InternalRowType::Record { .. }
364                    | InternalRowType::SelfTime { .. }
365            ) && self.dropped_ids.contains(&id)
366            {
367                return;
368            }
369            if let Some(id) = self.id_mapping.get(&id) {
370                Some(*id)
371            } else {
372                // Parent hasn't been seen yet; queue this row to be processed
373                // when the parent arrives. The row is already lifetime-free
374                // (strings interned eagerly at parse time) so no allocation
375                // is needed to extend its lifetime here.
376                self.queued_rows.entry(id).or_default().push(row);
377                return;
378            }
379        } else {
380            None
381        };
382        match row.ty {
383            InternalRowType::Start {
384                ts,
385                new_id,
386                name,
387                target,
388                values,
389            } => {
390                if self.remaining_ids_to_drop > 0
391                    && let Some(id) = id
392                {
393                    self.remaining_ids_to_drop -= 1;
394                    self.dropped_ids.insert(new_id);
395                    self.id_mapping.insert(new_id, id);
396                } else {
397                    let span_id =
398                        store.add_span(id, ts, target, name, values, &mut self.outdated_spans);
399                    self.id_mapping.insert(new_id, span_id);
400                }
401                // Inline-flush any rows that were waiting on this parent.
402                // Processing them now keeps the just-added Span (and its
403                // surrounding Store state) hot in cache, and avoids the
404                // memcpy of a working-queue extend. Recursion depth is
405                // bounded by the depth of orphan chains in the trace.
406                if let Some(rows) = self.queued_rows.remove(&new_id) {
407                    for row in rows {
408                        self.process_internal_row(store, row);
409                    }
410                }
411            }
412            InternalRowType::Record { values } => {
413                store.add_args(id.unwrap(), values, &mut self.outdated_spans);
414            }
415            InternalRowType::End => {
416                store.complete_span(id.unwrap());
417            }
418            InternalRowType::SelfTime { start, end } => {
419                store.add_self_time(id.unwrap(), start, end, &mut self.outdated_spans);
420            }
421            InternalRowType::Event {
422                ts,
423                duration,
424                name,
425                values,
426            } => {
427                let start = ts.saturating_sub(duration);
428                let id = store.add_span(
429                    id,
430                    start,
431                    rcstr!("event"),
432                    name,
433                    values,
434                    &mut self.outdated_spans,
435                );
436                store.add_self_time(id, start, ts, &mut self.outdated_spans);
437                store.complete_span(id);
438            }
439            InternalRowType::Allocation {
440                allocations,
441                allocation_count,
442            } => {
443                store.add_allocation(
444                    id.unwrap(),
445                    allocations,
446                    allocation_count,
447                    &mut self.outdated_spans,
448                );
449            }
450            InternalRowType::Deallocation {
451                deallocations,
452                deallocation_count,
453            } => {
454                store.add_deallocation(
455                    id.unwrap(),
456                    deallocations,
457                    deallocation_count,
458                    &mut self.outdated_spans,
459                );
460            }
461        }
462    }
463}
464
465impl TraceFormat for TurbopackFormat {
466    type Reused = Vec<TraceRow<'static>>;
467
468    fn create_reused() -> Vec<TraceRow<'static>> {
469        // Pre-allocate for a typical batch size to avoid repeated doubling during initial read.
470        Vec::with_capacity(4_096)
471    }
472
473    fn stats(&self) -> String {
474        use std::fmt::Write;
475
476        let spans = self.id_mapping.len();
477        let mut stats = format!("{spans} spans");
478
479        let dropped_spans = self.dropped_ids.len();
480        if dropped_spans > 0 {
481            let total_drop = dropped_spans + self.remaining_ids_to_drop;
482            write!(stats, ", {dropped_spans}/{total_drop} dropped").unwrap();
483        }
484
485        let queued_spans = self.queued_rows.len();
486        if queued_spans > 0 {
487            write!(stats, ", {queued_spans} queued").unwrap();
488        }
489
490        stats
491    }
492
493    fn read(&mut self, mut buffer: &[u8], reuse: &mut Self::Reused) -> Result<usize> {
494        reuse.clear();
495        let mut reuse = ClearOnDrop(reuse);
496        // Safety: The Vec is empty and is cleared on leaving this scope, so it's safe to cast the
497        // lifetime of data, since there is no data and data can't leave this function.
498        let rows =
499            unsafe { transmute::<&mut Vec<TraceRow<'_>>, &mut Vec<TraceRow<'_>>>(&mut *reuse) };
500        let mut bytes_read = 0;
501        loop {
502            match postcard::take_from_bytes(buffer) {
503                Ok((row, remaining)) => {
504                    bytes_read += buffer.len() - remaining.len();
505                    buffer = remaining;
506                    rows.push(row);
507                }
508                Err(err) => {
509                    if matches!(err, postcard::Error::DeserializeUnexpectedEnd) {
510                        break;
511                    }
512                    return Err(err.into());
513                }
514            }
515        }
516        if !rows.is_empty() {
517            let store = self.store.clone();
518            let mut iter = rows.drain(..);
519            {
520                let mut store = store.write();
521                for row in iter.by_ref() {
522                    self.process(&mut store, row);
523                }
524                store.invalidate_outdated_spans(&self.outdated_spans);
525                self.outdated_spans.clear();
526            }
527        }
528        Ok(bytes_read)
529    }
530}
531
532struct ClearOnDrop<'l, T>(&'l mut Vec<T>);
533
534impl<T> Drop for ClearOnDrop<'_, T> {
535    fn drop(&mut self) {
536        self.0.clear();
537    }
538}
539
540impl<T> Deref for ClearOnDrop<'_, T> {
541    type Target = Vec<T>;
542
543    fn deref(&self) -> &Self::Target {
544        self.0
545    }
546}
547
548impl<T> DerefMut for ClearOnDrop<'_, T> {
549    fn deref_mut(&mut self) -> &mut Self::Target {
550        self.0
551    }
552}