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 duration: Timestamp,
52 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 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 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 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 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 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}