1use std::{
2 borrow::Cow,
3 cell::{Cell, RefCell, RefMut},
4 fmt::Write,
5 marker::PhantomData,
6 sync::atomic::{AtomicU64, Ordering},
7 thread,
8 time::Instant,
9};
10
11use thread_local::ThreadLocal;
12use tracing::{
13 Subscriber,
14 field::{Visit, display},
15 span,
16};
17use tracing_subscriber::{Layer, registry::LookupSpan};
18use turbo_tasks_malloc::TurboMalloc;
19
20use crate::{
21 flavor::WriteGuardFlavor,
22 tokio_workers::active_worker_threads,
23 trace_writer::{TraceWriter, WriteGuard},
24 tracing::{Allocations, TraceRow, TraceValue},
25};
26
27const MEMORY_SAMPLE_INTERVAL_US: u64 = 10_000;
29
30const MEMORY_FOOTPRINT_SAMPLE_INTERVAL_US: u64 = 1_000_000;
34
35static GLOBAL_LAST_MEMORY_SAMPLE: AtomicU64 = AtomicU64::new(0);
36
37static MEMORY_FOOTPRINT_THROTTLE: FootprintThrottle = FootprintThrottle::new();
38
39struct FootprintThrottle {
42 last_read_ts: AtomicU64,
44 value: AtomicU64,
46}
47
48impl FootprintThrottle {
49 const NEVER: u64 = u64::MAX;
50
51 const fn new() -> Self {
52 Self {
53 last_read_ts: AtomicU64::new(Self::NEVER),
54 value: AtomicU64::new(0),
55 }
56 }
57
58 fn get(&self, ts: u64, read: impl FnOnce() -> u64) -> u64 {
64 let last = self.last_read_ts.load(Ordering::Relaxed);
65 if last != Self::NEVER && ts.saturating_sub(last) < MEMORY_FOOTPRINT_SAMPLE_INTERVAL_US {
66 return self.value.load(Ordering::Relaxed);
67 }
68 if self
69 .last_read_ts
70 .compare_exchange(last, ts, Ordering::Relaxed, Ordering::Relaxed)
71 .is_err()
72 {
73 return self.value.load(Ordering::Relaxed);
75 }
76 let value = read();
77 self.value.store(value, Ordering::Relaxed);
78 value
79 }
80}
81
82thread_local! {
83 static THREAD_LOCAL_LAST_MEMORY_SAMPLE: Cell<u64> = const { Cell::new(0) };
84}
85
86#[derive(Clone, Debug)]
88pub struct RawTraceLayerOptions {
89 pub memory: bool,
94}
95
96impl Default for RawTraceLayerOptions {
97 fn default() -> Self {
98 Self { memory: true }
99 }
100}
101
102struct RawTraceLayerExtension {
103 id: u64,
104}
105
106fn get_id<S: Subscriber + for<'a> LookupSpan<'a>>(
107 ctx: tracing_subscriber::layer::Context<'_, S>,
108 id: &span::Id,
109) -> u64 {
110 ctx.span(id)
111 .unwrap()
112 .extensions()
113 .get::<RawTraceLayerExtension>()
114 .unwrap()
115 .id
116}
117
118fn max_exit_enter_gap(entered: u64) -> u64 {
128 (entered / 1000).max(1)
129}
130
131pub struct RawTraceLayer<S: Subscriber + for<'a> LookupSpan<'a>> {
135 trace_writer: TraceWriter,
136 start: Instant,
137 next_id: AtomicU64,
138 memory: bool,
139 allocation_counters: fn() -> Allocations,
141 clock: fn(Instant) -> u64,
143 threads: ThreadLocal<RefCell<ThreadState>>,
144 _phantom: PhantomData<fn(S)>,
145}
146
147#[derive(Default)]
149struct ThreadState {
150 thread_id: u64,
153 reported_allocations: Option<Allocations>,
155 last_enter_ts: Option<u64>,
158 last_exit: Option<LastExit>,
162}
163
164#[derive(Clone, Copy)]
165struct LastExit {
166 id: u64,
167 ts: u64,
168 reported_allocations_before: Option<Allocations>,
170}
171
172fn thread_allocation_counters() -> Allocations {
173 let counters = TurboMalloc::allocation_counters();
174 Allocations {
175 allocations: counters.allocations as u64,
176 allocation_count: counters.allocation_count as u64,
177 deallocations: counters.deallocations as u64,
178 deallocation_count: counters.deallocation_count as u64,
179 }
180}
181
182impl<S: Subscriber + for<'a> LookupSpan<'a>> RawTraceLayer<S> {
183 pub fn new(trace_writer: TraceWriter) -> Self {
185 Self::with_options(trace_writer, RawTraceLayerOptions::default())
186 }
187
188 pub fn with_options(trace_writer: TraceWriter, options: RawTraceLayerOptions) -> Self {
189 let RawTraceLayerOptions { memory } = options;
190 Self {
191 trace_writer,
192 start: Instant::now(),
193 next_id: AtomicU64::new(1),
194 memory,
195 allocation_counters: thread_allocation_counters,
196 clock: |start| start.elapsed().as_micros() as u64,
197 threads: ThreadLocal::new(),
198 _phantom: PhantomData,
199 }
200 }
201
202 fn now(&self) -> u64 {
203 (self.clock)(self.start)
204 }
205
206 fn thread_state(&self, thread_id: u64) -> RefMut<'_, ThreadState> {
208 let mut state = self.threads.get_or_default().borrow_mut();
209 if state.thread_id != thread_id {
210 *state = ThreadState {
211 thread_id,
212 ..Default::default()
213 };
214 }
215 state
216 }
217
218 fn write(&self, data: TraceRow<'_, u64>) {
221 let start = TurboMalloc::allocation_counters();
222 let mut guard = self.trace_writer.start_write();
223 let data = Self::encode_timestamp(&mut guard, data);
224 postcard::serialize_with_flavor(&data, WriteGuardFlavor { guard: &mut guard }).unwrap();
225 drop(guard);
226 TurboMalloc::reset_allocation_counters(start);
227 }
228
229 fn write_marked(&self, data: TraceRow<'_, u64>, marker: u64) {
232 let start = TurboMalloc::allocation_counters();
233 let mut guard = self.trace_writer.start_write();
234 guard.mark(marker, |guard| {
236 let data = Self::encode_timestamp(guard, data);
237 postcard::serialize_with_flavor(&data, WriteGuardFlavor { guard }).unwrap()
238 });
239 drop(guard);
240 TurboMalloc::reset_allocation_counters(start);
241 }
242
243 fn encode_timestamp<'a>(guard: &mut WriteGuard<'_>, data: TraceRow<'a, u64>) -> TraceRow<'a> {
246 data.map_timestamp(|ts| guard.encode_timestamp(ts))
247 }
248
249 fn remove_last_row(&self, marker: u64) -> bool {
252 let start = TurboMalloc::allocation_counters();
253 let mut guard = self.trace_writer.start_write();
254 let removed = guard.remove_last_row(marker);
255 drop(guard);
256 TurboMalloc::reset_allocation_counters(start);
257 removed
258 }
259
260 fn maybe_report_memory_sample(&self, ts: u64) {
261 let skip = THREAD_LOCAL_LAST_MEMORY_SAMPLE
263 .with(|tl| ts.saturating_sub(tl.get()) < MEMORY_SAMPLE_INTERVAL_US);
264 if skip {
265 return;
266 }
267
268 let global_last = GLOBAL_LAST_MEMORY_SAMPLE.load(Ordering::Relaxed);
270 if ts.saturating_sub(global_last) < MEMORY_SAMPLE_INTERVAL_US {
271 THREAD_LOCAL_LAST_MEMORY_SAMPLE.with(|tl| tl.set(global_last));
273 return;
274 }
275
276 match GLOBAL_LAST_MEMORY_SAMPLE.compare_exchange(
278 global_last,
279 ts,
280 Ordering::Relaxed,
281 Ordering::Relaxed,
282 ) {
283 Ok(_) => {
284 THREAD_LOCAL_LAST_MEMORY_SAMPLE.with(|tl| tl.set(ts));
286 let memory = TurboMalloc::memory_usage() as u64;
287 let memory_pressure = TurboMalloc::memory_pressure().unwrap_or(0);
288 let memory_footprint = MEMORY_FOOTPRINT_THROTTLE.get(ts, || {
289 TurboMalloc::memory_footprint()
290 .map(|v| v as u64)
291 .unwrap_or(0)
292 });
293 self.write(TraceRow::MemorySample {
294 ts,
295 memory,
296 memory_pressure,
297 memory_footprint,
298 active_worker_threads: active_worker_threads() as u64,
299 });
300 }
301 Err(actual) => {
302 THREAD_LOCAL_LAST_MEMORY_SAMPLE.with(|tl| tl.set(actual));
304 }
305 }
306 }
307
308 fn allocations(state: &mut ThreadState, current: Option<Allocations>) -> Option<Allocations> {
313 let current = current?;
314 let previous = state.reported_allocations.replace(current)?;
315 let delta = Allocations {
316 allocations: current.allocations.saturating_sub(previous.allocations),
317 allocation_count: current
318 .allocation_count
319 .saturating_sub(previous.allocation_count),
320 deallocations: current.deallocations.saturating_sub(previous.deallocations),
321 deallocation_count: current
322 .deallocation_count
323 .saturating_sub(previous.deallocation_count),
324 };
325 (delta != Allocations::default()).then_some(delta)
326 }
327}
328
329impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RawTraceLayer<S> {
330 fn on_new_span(
331 &self,
332 attrs: &span::Attributes<'_>,
333 id: &span::Id,
334 ctx: tracing_subscriber::layer::Context<'_, S>,
335 ) {
336 let ts = self.now();
337 let mut values = ValuesVisitor::new();
338 attrs.values().record(&mut values);
339 let external_id = self
340 .next_id
341 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
342 ctx.span(id)
343 .unwrap()
344 .extensions_mut()
345 .insert(RawTraceLayerExtension { id: external_id });
346 self.write(TraceRow::Start {
347 ts,
348 id: external_id,
349 parent: if attrs.is_contextual() {
350 ctx.current_span().id().map(|p| get_id(ctx, p))
351 } else {
352 attrs.parent().map(|p| get_id(ctx, p))
353 },
354 name: attrs.metadata().name().into(),
355 target: attrs.metadata().target().into(),
356 values: values.values,
357 });
358 }
359
360 fn on_close(&self, id: span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
361 let ts = self.now();
362 self.write(TraceRow::End {
363 ts,
364 id: get_id(ctx, &id),
365 });
366 }
367
368 fn on_enter(&self, id: &span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
369 let malloc_counters = TurboMalloc::allocation_counters();
371 let current = self.memory.then(self.allocation_counters);
372 let ts = self.now();
373 let thread_id = thread::current().id().as_u64().into();
374 let id = get_id(ctx, id);
375 let enter_allocations = {
376 let mut state = self.thread_state(thread_id);
377 if let Some(last_exit) = state.last_exit.take()
381 && last_exit.id == id
382 && last_exit.ts <= ts
383 && ts
384 <= last_exit.ts.saturating_add(max_exit_enter_gap(
385 last_exit
386 .ts
387 .saturating_sub(state.last_enter_ts.unwrap_or(last_exit.ts)),
388 ))
389 && state.reported_allocations == current
392 && self.remove_last_row(id)
394 {
395 state.reported_allocations = last_exit.reported_allocations_before;
399 None
400 } else {
401 state.last_enter_ts = Some(ts);
402 Some(Self::allocations(&mut state, current))
403 }
404 };
405 if self.memory {
407 self.maybe_report_memory_sample(ts);
408 }
409 if let Some(allocations) = enter_allocations {
411 self.write(TraceRow::Enter {
412 ts,
413 id,
414 thread_id,
415 allocations,
416 });
417 }
418 TurboMalloc::reset_allocation_counters(malloc_counters);
419 }
420
421 fn on_exit(&self, id: &span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
422 let malloc_counters = TurboMalloc::allocation_counters();
424 let current = self.memory.then(self.allocation_counters);
425 let ts = self.now();
426 let thread_id = thread::current().id().as_u64().into();
427 let id = get_id(ctx, id);
428 let allocations = {
429 let mut state = self.thread_state(thread_id);
430 let reported_allocations_before = state.reported_allocations;
431 let allocations = Self::allocations(&mut state, current);
432 state.last_exit = Some(LastExit {
433 id,
434 ts,
435 reported_allocations_before,
436 });
437 allocations
438 };
439 self.write_marked(
440 TraceRow::Exit {
441 ts,
442 id,
443 thread_id,
444 allocations,
445 },
446 id,
447 );
448 TurboMalloc::reset_allocation_counters(malloc_counters);
449 }
450
451 fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
452 let ts = self.now();
453 let mut values = ValuesVisitor::new();
454 event.record(&mut values);
455 self.write(TraceRow::Event {
456 ts,
457 parent: if event.is_contextual() {
458 ctx.current_span().id().map(|p| get_id(ctx, p))
459 } else {
460 event.parent().map(|p| get_id(ctx, p))
461 },
462 values: values.values,
463 });
464 }
465
466 fn on_record(
467 &self,
468 id: &span::Id,
469 record: &span::Record<'_>,
470 ctx: tracing_subscriber::layer::Context<'_, S>,
471 ) {
472 let mut values = ValuesVisitor::new();
473 record.record(&mut values);
474 self.write(TraceRow::Record {
475 id: get_id(ctx, id),
476 values: values.values,
477 });
478 }
479}
480
481struct ValuesVisitor {
482 values: Vec<(Cow<'static, str>, TraceValue<'static>)>,
483}
484
485impl ValuesVisitor {
486 fn new() -> Self {
487 Self { values: Vec::new() }
488 }
489}
490
491impl Visit for ValuesVisitor {
492 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
493 let mut str = String::new();
494 let _ = write!(str, "{value:?}");
495 self.values
496 .push((field.name().into(), TraceValue::String(str.into())));
497 }
498
499 fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
500 self.values
501 .push((field.name().into(), TraceValue::Float(value)));
502 }
503
504 fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
505 self.values
506 .push((field.name().into(), TraceValue::Int(value)));
507 }
508
509 fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
510 self.values
511 .push((field.name().into(), TraceValue::UInt(value)));
512 }
513
514 fn record_i128(&mut self, field: &tracing::field::Field, value: i128) {
515 self.record_debug(field, &value)
516 }
517
518 fn record_u128(&mut self, field: &tracing::field::Field, value: u128) {
519 self.record_debug(field, &value)
520 }
521
522 fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
523 self.values
524 .push((field.name().into(), TraceValue::Bool(value)));
525 }
526
527 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
528 self.values.push((
529 field.name().into(),
530 TraceValue::String(value.to_string().into()),
531 ));
532 }
533
534 fn record_error(
535 &mut self,
536 field: &tracing::field::Field,
537 value: &(dyn std::error::Error + 'static),
538 ) {
539 self.record_debug(field, &display(value))
540 }
541}
542
543#[cfg(test)]
544pub(crate) mod tests {
545 use std::{
546 cell::Cell,
547 io::{self, Write},
548 sync::{
549 Arc, Barrier, Mutex,
550 atomic::{AtomicUsize, Ordering},
551 },
552 thread,
553 time::Duration,
554 };
555
556 use tracing_subscriber::{Registry, layer::SubscriberExt};
557
558 use crate::{
559 raw_trace::{
560 FootprintThrottle, MEMORY_FOOTPRINT_SAMPLE_INTERVAL_US, RawTraceLayer,
561 RawTraceLayerOptions,
562 },
563 trace_writer::TraceWriter,
564 tracing::{Allocations, TRACE_HEADER, TimestampDecoder, TraceRow},
565 };
566
567 thread_local! {
568 static FAKE_ALLOCATIONS: Cell<Allocations> = const {
570 Cell::new(Allocations {
571 allocations: 0,
572 allocation_count: 0,
573 deallocations: 0,
574 deallocation_count: 0,
575 })
576 };
577 static FAKE_TIME: Cell<u64> = const { Cell::new(0) };
579 static FAKE_TIME_STEP: Cell<u64> = const { Cell::new(1) };
581 }
582
583 fn fake_clock(_start: std::time::Instant) -> u64 {
584 let step = FAKE_TIME_STEP.with(|s| s.get());
585 FAKE_TIME.with(|t| {
586 let time = t.get();
587 t.set(time + step);
588 time
589 })
590 }
591
592 fn freeze_time(time: u64) {
594 FAKE_TIME.with(|t| t.set(time));
595 FAKE_TIME_STEP.with(|s| s.set(0));
596 }
597
598 fn fake_allocation_counters() -> Allocations {
599 FAKE_ALLOCATIONS.with(|c| c.get())
600 }
601
602 pub(crate) fn fake_allocate(bytes: u64, count: u64) {
604 FAKE_ALLOCATIONS.with(|c| {
605 let mut counters = c.get();
606 counters.allocations += bytes * count;
607 counters.allocation_count += count;
608 c.set(counters);
609 });
610 }
611
612 pub(crate) fn fake_deallocate(bytes: u64, count: u64) {
614 FAKE_ALLOCATIONS.with(|c| {
615 let mut counters = c.get();
616 counters.deallocations += bytes * count;
617 counters.deallocation_count += count;
618 c.set(counters);
619 });
620 }
621
622 #[derive(Clone, Default)]
623 struct SharedBuffer(Arc<Mutex<Vec<u8>>>);
624
625 impl Write for SharedBuffer {
626 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
627 self.0.lock().unwrap().extend_from_slice(buf);
628 Ok(buf.len())
629 }
630
631 fn flush(&mut self) -> io::Result<()> {
632 Ok(())
633 }
634 }
635
636 pub(crate) fn capture(options: RawTraceLayerOptions, f: impl FnOnce()) -> Vec<u8> {
640 FAKE_TIME_STEP.with(|s| s.set(1));
641 let buffer = SharedBuffer::default();
642 let (trace_writer, guard) = TraceWriter::new(buffer.clone());
643 let mut layer = RawTraceLayer::with_options(trace_writer, options);
644 layer.allocation_counters = fake_allocation_counters;
645 layer.clock = fake_clock;
646 let subscriber = Registry::default().with(layer);
647 tracing::subscriber::with_default(subscriber, f);
648 drop(guard);
650 Arc::try_unwrap(buffer.0).unwrap().into_inner().unwrap()
651 }
652
653 pub(crate) fn decode(data: &[u8]) -> Vec<TraceRow<'_, u64>> {
656 decode_all(data)
657 .into_iter()
658 .filter(|row| !matches!(row, TraceRow::TimestampBase { .. }))
659 .collect()
660 }
661
662 pub(crate) fn decode_all(data: &[u8]) -> Vec<TraceRow<'_, u64>> {
665 let header = TRACE_HEADER;
666 assert!(data.starts_with(header), "missing trace header");
667 let mut remaining = &data[header.len()..];
668 let mut decoder = TimestampDecoder::default();
669 let mut rows = Vec::new();
670 while !remaining.is_empty() {
671 let (row, rest) = postcard::take_from_bytes(remaining).unwrap();
672 rows.push(decoder.decode(row).unwrap());
673 remaining = rest;
674 }
675 rows
676 }
677
678 fn enter_exit_span() {
679 let span = tracing::info_span!("test span");
680 let _guard = span.enter();
681 }
682
683 fn count(rows: &[TraceRow<'_, u64>], predicate: impl Fn(&TraceRow<'_, u64>) -> bool) -> usize {
684 rows.iter().filter(|row| predicate(row)).count()
685 }
686
687 pub(crate) fn enter_exit_allocations(
689 rows: &[TraceRow<'_, u64>],
690 ) -> Vec<(&'static str, Option<Allocations>)> {
691 rows.iter()
692 .filter_map(|row| match row {
693 TraceRow::Enter { allocations, .. } => Some(("enter", *allocations)),
694 TraceRow::Exit { allocations, .. } => Some(("exit", *allocations)),
695 _ => None,
696 })
697 .collect()
698 }
699
700 #[test]
701 fn timestamps_round_trip_across_threads_and_buffers() {
702 let data = capture(RawTraceLayerOptions::default(), || {
703 freeze_time(1000);
705 let span = tracing::info_span!("backwards");
706 let guard = span.enter();
707 freeze_time(400);
708 drop(guard);
709 drop(span);
710 let dispatch = tracing::dispatcher::get_default(|d| d.clone());
711 std::thread::scope(|scope| {
712 for _ in 0..2 {
713 scope.spawn(|| {
714 tracing::dispatcher::with_default(&dispatch, || {
715 for i in 0..40_000u64 {
717 let span = tracing::info_span!("loop", i);
718 let _guard = span.enter();
719 }
720 })
721 });
722 }
723 });
724 });
725 let all_rows = decode_all(&data);
726 let bases = count(&all_rows, |r| matches!(r, TraceRow::TimestampBase { .. }));
727 assert!(bases > 3, "expected multiple buffers, got {bases} bases");
728 let rows = decode(&data);
729
730 let backwards_id = rows
732 .iter()
733 .find_map(|row| match row {
734 TraceRow::Start { id, name, ts, .. } if name == "backwards" => {
735 assert_eq!(*ts, 1000);
736 Some(*id)
737 }
738 _ => None,
739 })
740 .unwrap();
741 let backwards: Vec<_> = rows
742 .iter()
743 .filter_map(|row| match row {
744 TraceRow::Enter { id, ts, .. } if *id == backwards_id => Some(("enter", *ts)),
745 TraceRow::Exit { id, ts, .. } if *id == backwards_id => Some(("exit", *ts)),
746 TraceRow::End { id, ts } if *id == backwards_id => Some(("end", *ts)),
747 _ => None,
748 })
749 .collect();
750 assert_eq!(backwards, [("enter", 1000), ("exit", 400), ("end", 400)]);
751
752 let mut per_thread: std::collections::HashMap<u64, (Vec<u64>, Vec<u64>)> =
756 Default::default();
757 for row in &rows {
758 match row {
759 TraceRow::Enter {
760 thread_id, ts, id, ..
761 } if *id != backwards_id => per_thread.entry(*thread_id).or_default().0.push(*ts),
762 TraceRow::Exit {
763 thread_id, ts, id, ..
764 } if *id != backwards_id => per_thread.entry(*thread_id).or_default().1.push(*ts),
765 _ => {}
766 }
767 }
768 assert_eq!(per_thread.len(), 2);
769 let expected_enter: Vec<u64> = (0..40_000).map(|i| 4 * i + 1).collect();
770 let expected_exit: Vec<u64> = (0..40_000).map(|i| 4 * i + 2).collect();
771 for (mut enter, mut exit) in per_thread.into_values() {
772 enter.sort_unstable();
774 exit.sort_unstable();
775 assert!(enter == expected_enter, "unexpected Enter timestamps");
776 assert!(exit == expected_exit, "unexpected Exit timestamps");
777 }
778 }
779
780 #[test]
781 fn buffer_starting_with_a_record_gets_a_base_before_its_first_timestamp() {
782 let fill = crate::trace_writer::THREAD_LOCAL_INITIAL_BUFFER_SIZE * 2 / 3;
784 let data = capture(RawTraceLayerOptions::default(), || {
785 let span = tracing::info_span!("recorded", value = tracing::field::Empty);
786 span.record("value", "x".repeat(fill));
788 span.record("value", "small");
790 });
791 let rows = decode_all(&data);
792 let kinds: Vec<_> = rows
793 .iter()
794 .map(|row| match row {
795 TraceRow::TimestampBase { .. } => "base",
796 TraceRow::Start { .. } => "start",
797 TraceRow::Record { .. } => "record",
798 TraceRow::End { .. } => "end",
799 _ => "other",
800 })
801 .collect();
802 assert_eq!(kinds, ["base", "start", "record", "record", "base", "end"]);
805 }
806
807 #[test]
808 fn attaches_allocation_deltas_to_enter_and_exit() {
809 let data = capture(RawTraceLayerOptions::default(), || {
810 fake_allocate(10, 1);
811 let span = tracing::info_span!("first");
812 let guard = span.enter();
813 fake_allocate(100, 2);
814 fake_deallocate(5, 1);
815 drop(guard);
816 fake_deallocate(1, 1);
817 let span = tracing::info_span!("second");
818 let guard = span.enter();
819 drop(guard);
820 fake_allocate(3, 1);
821 let guard = span.enter();
822 drop(guard);
823 });
824 let rows = decode(&data);
825 assert_eq!(
826 enter_exit_allocations(&rows),
827 vec![
828 ("enter", None),
830 (
831 "exit",
832 Some(Allocations {
833 allocations: 200,
834 allocation_count: 2,
835 deallocations: 5,
836 deallocation_count: 1,
837 })
838 ),
839 (
840 "enter",
841 Some(Allocations {
842 allocations: 0,
843 allocation_count: 0,
844 deallocations: 1,
845 deallocation_count: 1,
846 })
847 ),
848 ("exit", None),
850 (
851 "enter",
852 Some(Allocations {
853 allocations: 3,
854 allocation_count: 1,
855 deallocations: 0,
856 deallocation_count: 0,
857 })
858 ),
859 ("exit", None),
860 ]
861 );
862 }
863
864 #[test]
865 fn memory_tracking_can_be_disabled() {
866 let data = capture(RawTraceLayerOptions { memory: false }, enter_exit_span);
867 let rows = decode(&data);
868 assert_eq!(count(&rows, |r| matches!(r, TraceRow::Start { .. })), 1);
869 assert_eq!(
870 enter_exit_allocations(&rows),
871 vec![("enter", None), ("exit", None)]
872 );
873 assert_eq!(count(&rows, |r| matches!(r, TraceRow::End { .. })), 1);
874 assert_eq!(
875 count(&rows, |r| matches!(r, TraceRow::MemorySample { .. })),
876 0
877 );
878 assert_eq!(rows.len(), 4);
879 }
880
881 fn enter_exit_rows(rows: &[TraceRow<'_, u64>]) -> Vec<(&'static str, u64, u64)> {
883 rows.iter()
884 .filter_map(|row| match row {
885 TraceRow::Enter { id, ts, .. } => Some(("enter", *id, *ts)),
886 TraceRow::Exit { id, ts, .. } => Some(("exit", *id, *ts)),
887 _ => None,
888 })
889 .collect()
890 }
891
892 fn reenter_span(between: impl FnOnce()) {
894 let span = tracing::info_span!("span");
895 drop(span.enter());
896 between();
897 drop(span.enter());
898 }
899
900 fn assert_not_elided(rows: &[TraceRow<'_, u64>]) {
901 let kinds: Vec<_> = enter_exit_rows(rows).iter().map(|r| r.0).collect();
902 assert_eq!(kinds, vec!["enter", "exit", "enter", "exit"]);
903 }
904
905 #[test]
906 fn elides_exit_enter_pair_of_same_span_at_same_timestamp() {
907 for memory in [true, false] {
908 let data = capture(RawTraceLayerOptions { memory }, || {
909 freeze_time(10);
910 reenter_span(|| {});
911 });
912 let rows = decode(&data);
913 assert_eq!(
914 enter_exit_rows(&rows),
915 vec![("enter", 1, 10), ("exit", 1, 10)],
916 "memory: {memory}"
917 );
918 assert_eq!(rows.len(), 4, "memory: {memory}");
919 }
920 }
921
922 #[test]
923 fn keeps_exit_enter_pair_with_larger_gap() {
924 let data = capture(RawTraceLayerOptions::default(), || {
925 freeze_time(10);
926 reenter_span(|| freeze_time(12));
928 });
929 assert_not_elided(&decode(&data));
930 }
931
932 #[test]
933 fn elides_exit_enter_pair_with_1us_gap() {
934 let data = capture(RawTraceLayerOptions::default(), || {
935 freeze_time(10);
936 reenter_span(|| freeze_time(11));
937 });
938 assert_eq!(
939 enter_exit_rows(&decode(&data)),
940 vec![("enter", 1, 10), ("exit", 1, 11)]
941 );
942 }
943
944 fn reenter_at(enter: u64, exit: u64, reenter: u64) -> Vec<(&'static str, u64, u64)> {
947 let data = capture(RawTraceLayerOptions::default(), || {
948 freeze_time(enter);
949 let span = tracing::info_span!("span");
950 let guard = span.enter();
951 freeze_time(exit);
952 drop(guard);
953 freeze_time(reenter);
954 let guard = span.enter();
955 freeze_time(reenter + 1000);
956 drop(guard);
957 });
958 enter_exit_rows(&decode(&data))
959 }
960
961 #[test]
962 fn elides_exit_enter_pair_with_gap_up_to_a_thousandth_of_the_entered_duration() {
963 assert_eq!(
964 reenter_at(0, 3000, 3003),
965 vec![("enter", 1, 0), ("exit", 1, 4003)]
966 );
967 assert_eq!(
968 reenter_at(0, 3000, 3004),
969 vec![
970 ("enter", 1, 0),
971 ("exit", 1, 3000),
972 ("enter", 1, 3004),
973 ("exit", 1, 4004)
974 ]
975 );
976 }
977
978 #[test]
981 fn chained_elisions_measure_from_the_last_written_enter() {
982 let data = capture(RawTraceLayerOptions::default(), || {
983 freeze_time(0);
984 let span = tracing::info_span!("span");
985 for (enter, exit) in [(0, 2000), (2002, 3000), (3002, 4000)] {
986 freeze_time(enter);
987 let guard = span.enter();
988 freeze_time(exit);
989 drop(guard);
990 }
991 });
992 assert_eq!(
993 enter_exit_rows(&decode(&data)),
994 vec![
995 ("enter", 1, 0),
998 ("exit", 1, 4000)
999 ]
1000 );
1001 }
1002
1003 #[test]
1005 fn reports_memory_samples_when_the_enter_is_omitted() {
1006 const START: u64 = 1 << 50;
1008 let data = capture(RawTraceLayerOptions::default(), || {
1009 let span = tracing::info_span!("span");
1010 freeze_time(START);
1011 let guard = span.enter();
1012 freeze_time(START + 100_000);
1013 drop(guard);
1014 drop(span.enter());
1016 });
1017 let rows = decode(&data);
1018 assert_eq!(
1019 enter_exit_rows(&rows),
1020 vec![("enter", 1, START), ("exit", 1, START + 100_000)]
1021 );
1022 let samples: Vec<_> = rows
1023 .iter()
1024 .filter_map(|row| match row {
1025 TraceRow::MemorySample { ts, .. } => Some(*ts),
1026 _ => None,
1027 })
1028 .collect();
1029 assert_eq!(samples, vec![START, START + 100_000]);
1030 }
1031
1032 #[test]
1036 fn measures_the_gap_from_the_last_enter_on_the_thread() {
1037 let data = capture(RawTraceLayerOptions::default(), || {
1038 let outer = tracing::info_span!("outer");
1039 let inner = tracing::info_span!("inner");
1040 freeze_time(0);
1041 let outer_guard = outer.enter();
1042 freeze_time(2000);
1043 let inner_guard = inner.enter();
1044 freeze_time(2500);
1045 drop(inner_guard);
1046 freeze_time(3000);
1047 drop(outer_guard);
1048 freeze_time(3002);
1051 let outer_guard = outer.enter();
1052 freeze_time(4002);
1053 drop(outer_guard);
1054 });
1055 assert_eq!(
1056 enter_exit_rows(&decode(&data)),
1057 vec![
1058 ("enter", 1, 0),
1059 ("enter", 2, 2000),
1060 ("exit", 2, 2500),
1061 ("exit", 1, 3000),
1062 ("enter", 1, 3002),
1063 ("exit", 1, 4002)
1064 ]
1065 );
1066 }
1067
1068 #[test]
1069 fn keeps_exit_enter_pair_of_different_spans() {
1070 let data = capture(RawTraceLayerOptions::default(), || {
1071 freeze_time(10);
1072 let a = tracing::info_span!("a");
1073 let b = tracing::info_span!("b");
1074 drop(a.enter());
1075 drop(b.enter());
1076 });
1077 assert_not_elided(&decode(&data));
1078 }
1079
1080 #[test]
1081 fn keeps_exit_enter_pair_with_allocations_in_between() {
1082 let data = capture(RawTraceLayerOptions::default(), || {
1083 freeze_time(10);
1084 reenter_span(|| fake_allocate(8, 1));
1085 });
1086 let rows = decode(&data);
1087 assert_not_elided(&rows);
1088 assert_eq!(
1090 enter_exit_allocations(&rows)[2],
1091 (
1092 "enter",
1093 Some(Allocations {
1094 allocations: 8,
1095 allocation_count: 1,
1096 deallocations: 0,
1097 deallocation_count: 0,
1098 })
1099 )
1100 );
1101 }
1102
1103 #[test]
1104 fn keeps_exit_enter_pair_with_row_in_between() {
1105 let data = capture(RawTraceLayerOptions::default(), || {
1106 freeze_time(10);
1107 reenter_span(|| tracing::info!("event"));
1108 });
1109 let rows = decode(&data);
1110 assert_not_elided(&rows);
1111 assert_eq!(count(&rows, |r| matches!(r, TraceRow::Event { .. })), 1);
1112 }
1113
1114 #[test]
1115 fn keeps_exit_enter_pair_on_different_threads() {
1116 let data = capture(RawTraceLayerOptions::default(), || {
1117 freeze_time(10);
1118 let span = tracing::info_span!("span");
1119 drop(span.enter());
1120 let dispatch = tracing::dispatcher::get_default(|d| d.clone());
1121 std::thread::scope(|scope| {
1122 scope.spawn(|| {
1123 freeze_time(10);
1124 tracing::dispatcher::with_default(&dispatch, || drop(span.enter()));
1125 });
1126 });
1127 });
1128 assert_not_elided(&decode(&data));
1129 }
1130
1131 #[test]
1132 fn resets_state_when_a_thread_slot_is_reused() {
1133 let data = capture(RawTraceLayerOptions::default(), || {
1134 let span = tracing::info_span!("span");
1135 let dispatch = tracing::dispatcher::get_default(|d| d.clone());
1136 for i in 1..=2 {
1138 std::thread::scope(|scope| {
1139 scope.spawn(|| {
1140 freeze_time(10);
1141 fake_allocate(1000 * i, 1);
1142 tracing::dispatcher::with_default(&dispatch, || drop(span.enter()));
1143 });
1144 });
1145 }
1146 });
1147 let rows = decode(&data);
1148 assert_eq!(
1151 enter_exit_allocations(&rows),
1152 vec![
1153 ("enter", None),
1154 ("exit", None),
1155 ("enter", None),
1156 ("exit", None)
1157 ]
1158 );
1159 let threads: Vec<_> = rows
1160 .iter()
1161 .filter_map(|row| match row {
1162 TraceRow::Enter { thread_id, .. } => Some(*thread_id),
1163 _ => None,
1164 })
1165 .collect();
1166 assert_ne!(threads[0], threads[1]);
1167 }
1168
1169 #[test]
1170 fn uses_the_enter_of_the_same_thread_for_the_entered_duration() {
1171 let data = capture(RawTraceLayerOptions::default(), || {
1172 let span = tracing::info_span!("span");
1173 let dispatch = tracing::dispatcher::get_default(|d| d.clone());
1174 freeze_time(0);
1176 let guard = span.enter();
1177 std::thread::scope(|scope| {
1178 scope.spawn(|| {
1179 freeze_time(2900);
1180 tracing::dispatcher::with_default(&dispatch, || {
1181 let _guard = span.enter();
1182 freeze_time(2950);
1183 });
1184 });
1185 });
1186 freeze_time(3000);
1187 drop(guard);
1188 freeze_time(3003);
1190 drop(span.enter());
1191 });
1192 let rows = decode(&data);
1193 let mut by_thread: Vec<(u64, Vec<(&str, u64)>)> = Vec::new();
1194 for row in &rows {
1195 let (kind, thread, ts) = match row {
1196 TraceRow::Enter { thread_id, ts, .. } => ("enter", *thread_id, *ts),
1197 TraceRow::Exit { thread_id, ts, .. } => ("exit", *thread_id, *ts),
1198 _ => continue,
1199 };
1200 match by_thread.iter_mut().find(|(t, _)| *t == thread) {
1201 Some((_, rows)) => rows.push((kind, ts)),
1202 None => by_thread.push((thread, vec![(kind, ts)])),
1203 }
1204 }
1205 let mut by_thread: Vec<_> = by_thread.into_iter().map(|(_, rows)| rows).collect();
1206 by_thread.sort();
1207 assert_eq!(
1208 by_thread,
1209 vec![
1210 vec![("enter", 0), ("exit", 3003)],
1212 vec![("enter", 2900), ("exit", 2950)],
1214 ]
1215 );
1216 }
1217
1218 #[test]
1219 fn elision_preserves_allocations_of_the_exited_span() {
1220 let run = |freeze: bool| {
1221 let data = capture(RawTraceLayerOptions::default(), || {
1222 freeze_time(10);
1223 let span = tracing::info_span!("span");
1224 let guard = span.enter();
1225 fake_allocate(50, 1);
1226 drop(guard);
1227 if !freeze {
1228 freeze_time(12);
1230 }
1231 let guard = span.enter();
1232 fake_allocate(7, 1);
1233 fake_deallocate(3, 1);
1234 drop(guard);
1235 });
1236 let rows = decode(&data);
1237 let rows_count = enter_exit_rows(&rows).len();
1238 let total = enter_exit_allocations(&rows)
1241 .into_iter()
1242 .filter_map(|(_, allocations)| allocations)
1243 .fold(Allocations::default(), |a, b| Allocations {
1244 allocations: a.allocations + b.allocations,
1245 allocation_count: a.allocation_count + b.allocation_count,
1246 deallocations: a.deallocations + b.deallocations,
1247 deallocation_count: a.deallocation_count + b.deallocation_count,
1248 });
1249 (rows_count, total)
1250 };
1251 let (elided_rows, elided_total) = run(true);
1252 let (rows, total) = run(false);
1253 assert_eq!((elided_rows, rows), (2, 4));
1254 assert_eq!(
1255 elided_total,
1256 Allocations {
1257 allocations: 57,
1258 allocation_count: 2,
1259 deallocations: 3,
1260 deallocation_count: 1,
1261 }
1262 );
1263 assert_eq!(elided_total, total);
1264 }
1265
1266 #[test]
1267 fn footprint_is_read_at_most_once_per_interval() {
1268 let throttle = FootprintThrottle::new();
1269 let reads = Cell::new(0);
1270 let read = |value: u64| {
1271 reads.set(reads.get() + 1);
1272 value
1273 };
1274
1275 assert_eq!(throttle.get(0, || read(100)), 100);
1277 assert_eq!(reads.get(), 1);
1278
1279 assert_eq!(throttle.get(10_000, || read(200)), 100);
1281 assert_eq!(
1282 throttle.get(MEMORY_FOOTPRINT_SAMPLE_INTERVAL_US - 1, || read(300)),
1283 100
1284 );
1285 assert_eq!(reads.get(), 1);
1286
1287 assert_eq!(
1289 throttle.get(MEMORY_FOOTPRINT_SAMPLE_INTERVAL_US, || read(400)),
1290 400
1291 );
1292 assert_eq!(reads.get(), 2);
1293 assert_eq!(
1294 throttle.get(MEMORY_FOOTPRINT_SAMPLE_INTERVAL_US + 10_000, || read(500)),
1295 400
1296 );
1297 assert_eq!(reads.get(), 2);
1298 }
1299
1300 #[test]
1301 fn concurrent_caller_during_a_read_gets_cached_value() {
1302 let throttle = FootprintThrottle::new();
1303 assert_eq!(throttle.get(0, || 100), 100);
1304
1305 let ts = MEMORY_FOOTPRINT_SAMPLE_INTERVAL_US;
1309 let value = throttle.get(ts, || {
1310 let nested = throttle.get(ts + 10_000, || panic!("must not read concurrently"));
1311 assert_eq!(nested, 100);
1312 200
1313 });
1314 assert_eq!(value, 200);
1315 assert_eq!(throttle.get(ts + 20_000, || panic!("cached")), 200);
1316 }
1317
1318 #[test]
1319 fn simultaneous_first_calls_read_once() {
1320 const THREADS: usize = 8;
1321 let throttle = FootprintThrottle::new();
1322 let reads = AtomicUsize::new(0);
1323 let barrier = Barrier::new(THREADS);
1324
1325 let values: Vec<u64> = thread::scope(|scope| {
1326 let handles: Vec<_> = (0..THREADS)
1327 .map(|_| {
1328 scope.spawn(|| {
1329 barrier.wait();
1330 throttle.get(0, || {
1331 reads.fetch_add(1, Ordering::Relaxed);
1332 thread::sleep(Duration::from_millis(20));
1334 100
1335 })
1336 })
1337 })
1338 .collect();
1339 handles.into_iter().map(|h| h.join().unwrap()).collect()
1340 });
1341
1342 assert_eq!(reads.load(Ordering::Relaxed), 1);
1343 assert!(values.iter().all(|&v| v == 0 || v == 100), "{values:?}");
1345 assert!(values.contains(&100), "{values:?}");
1346 assert_eq!(throttle.get(10_000, || panic!("cached")), 100);
1347 }
1348}