Skip to content

Commit 972ee78

Browse files
authored
Merge pull request #4524 from Stypox/tracing-chrome-overhead-rdtsc
Account for time spent tracing, use RDTSC for faster time
2 parents 350e0ce + ada3090 commit 972ee78

File tree

3 files changed

+283
-78
lines changed

3 files changed

+283
-78
lines changed

src/bin/log/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod setup;
22
mod tracing_chrome;
3+
mod tracing_chrome_instant;

src/bin/log/tracing_chrome.rs

Lines changed: 99 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
//! (`git log -- path/to/tracing_chrome.rs`), but in summary:
88
//! - the file attributes were changed and `extern crate` was added at the top
99
//! - if a tracing span has a field called "tracing_separate_thread", it will be given a separate
10-
//! span ID even in [TraceStyle::Threaded] mode, to make it appear on a separate line when viewing
11-
//! the trace in <https://ui.perfetto.dev>. This is the syntax to trigger this behavior:
10+
//! span ID even in [TraceStyle::Threaded] mode, to make it appear on a separate line when viewing
11+
//! the trace in <https://ui.perfetto.dev>. This is the syntax to trigger this behavior:
1212
//! ```rust
1313
//! tracing::info_span!("my_span", tracing_separate_thread = tracing::field::Empty, /* ... */)
1414
//! ```
15-
//! - use i64 instead of u64 for the "id" in [ChromeLayer::get_root_id] to be compatible with Perfetto
15+
//! - use i64 instead of u64 for the "id" in [ChromeLayer::get_root_id] to be compatible with
16+
//! Perfetto
17+
//! - use [ChromeLayer::with_elapsed_micros_subtracting_tracing] to make time measurements faster on
18+
//! Linux x86/x86_64 and to subtract time spent tracing from the timestamps in the trace file
1619
//!
1720
//! Depending on the tracing-chrome crate from crates.io is unfortunately not possible, since it
1821
//! depends on `tracing_core` which conflicts with rustc_private's `tracing_core` (meaning it would
@@ -50,9 +53,22 @@ use std::{
5053
thread::JoinHandle,
5154
};
5255

56+
use crate::log::tracing_chrome_instant::TracingChromeInstant;
57+
58+
/// Contains thread-local data for threads that send tracing spans or events.
59+
struct ThreadData {
60+
/// A unique ID for this thread, will populate "tid" field in the output trace file.
61+
tid: usize,
62+
/// A clone of [ChromeLayer::out] to avoid the expensive operation of accessing a mutex
63+
/// every time. This is used to send [Message]s to the thread that saves trace data to file.
64+
out: Sender<Message>,
65+
/// The instant in time this thread was started. All events happening on this thread will be
66+
/// saved to the trace file with a timestamp (the "ts" field) measured relative to this instant.
67+
start: TracingChromeInstant,
68+
}
69+
5370
thread_local! {
54-
static OUT: RefCell<Option<Sender<Message>>> = const { RefCell::new(None) };
55-
static TID: RefCell<Option<usize>> = const { RefCell::new(None) };
71+
static THREAD_DATA: RefCell<Option<ThreadData>> = const { RefCell::new(None) };
5672
}
5773

5874
type NameFn<S> = Box<dyn Fn(&EventOrSpan<'_, '_, S>) -> String + Send + Sync>;
@@ -64,7 +80,6 @@ where
6480
S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
6581
{
6682
out: Arc<Mutex<Sender<Message>>>,
67-
start: std::time::Instant,
6883
max_tid: AtomicUsize,
6984
include_args: bool,
7085
include_locations: bool,
@@ -323,7 +338,6 @@ where
323338
{
324339
fn new(mut builder: ChromeLayerBuilder<S>) -> (ChromeLayer<S>, FlushGuard) {
325340
let (tx, rx) = mpsc::channel();
326-
OUT.with(|val| val.replace(Some(tx.clone())));
327341

328342
let out_writer = builder
329343
.out_writer
@@ -443,7 +457,6 @@ where
443457
};
444458
let layer = ChromeLayer {
445459
out: Arc::new(Mutex::new(tx)),
446-
start: std::time::Instant::now(),
447460
max_tid: AtomicUsize::new(0),
448461
name_fn: builder.name_fn.take(),
449462
cat_fn: builder.cat_fn.take(),
@@ -456,22 +469,7 @@ where
456469
(layer, guard)
457470
}
458471

459-
fn get_tid(&self) -> (usize, bool) {
460-
TID.with(|value| {
461-
let tid = *value.borrow();
462-
match tid {
463-
Some(tid) => (tid, false),
464-
None => {
465-
let tid = self.max_tid.fetch_add(1, Ordering::SeqCst);
466-
value.replace(Some(tid));
467-
(tid, true)
468-
}
469-
}
470-
})
471-
}
472-
473-
fn get_callsite(&self, data: EventOrSpan<S>) -> Callsite {
474-
let (tid, new_thread) = self.get_tid();
472+
fn get_callsite(&self, data: EventOrSpan<S>, tid: usize) -> Callsite {
475473
let name = self.name_fn.as_ref().map(|name_fn| name_fn(&data));
476474
let target = self.cat_fn.as_ref().map(|cat_fn| cat_fn(&data));
477475
let meta = match data {
@@ -502,14 +500,6 @@ where
502500
(None, None)
503501
};
504502

505-
if new_thread {
506-
let name = match std::thread::current().name() {
507-
Some(name) => name.to_owned(),
508-
None => tid.to_string(),
509-
};
510-
self.send_message(Message::NewThread(tid, name));
511-
}
512-
513503
Callsite {
514504
tid,
515505
name,
@@ -548,31 +538,55 @@ where
548538
}
549539
}
550540

551-
fn enter_span(&self, span: SpanRef<S>, ts: f64) {
552-
let callsite = self.get_callsite(EventOrSpan::Span(&span));
541+
fn enter_span(&self, span: SpanRef<S>, ts: f64, tid: usize, out: &Sender<Message>) {
542+
let callsite = self.get_callsite(EventOrSpan::Span(&span), tid);
553543
let root_id = self.get_root_id(span);
554-
self.send_message(Message::Enter(ts, callsite, root_id));
544+
let _ignored = out.send(Message::Enter(ts, callsite, root_id));
555545
}
556546

557-
fn exit_span(&self, span: SpanRef<S>, ts: f64) {
558-
let callsite = self.get_callsite(EventOrSpan::Span(&span));
547+
fn exit_span(&self, span: SpanRef<S>, ts: f64, tid: usize, out: &Sender<Message>) {
548+
let callsite = self.get_callsite(EventOrSpan::Span(&span), tid);
559549
let root_id = self.get_root_id(span);
560-
self.send_message(Message::Exit(ts, callsite, root_id));
550+
let _ignored = out.send(Message::Exit(ts, callsite, root_id));
561551
}
562552

563-
fn get_ts(&self) -> f64 {
564-
self.start.elapsed().as_nanos() as f64 / 1000.0
565-
}
553+
/// Helper function that measures how much time is spent while executing `f` and accounts for it
554+
/// in subsequent calls, with the aim to reduce biases in the data collected by `tracing_chrome`
555+
/// by subtracting the time spent inside tracing functions from the timeline. This makes it so
556+
/// that the time spent inside the `tracing_chrome` functions does not impact the timestamps
557+
/// inside the trace file (i.e. `ts`), even if such functions are slow (e.g. because they need
558+
/// to format arguments on the same thread those arguments are collected on, otherwise memory
559+
/// safety would be broken).
560+
///
561+
/// `f` is called with the microseconds elapsed since the current thread was started (**not**
562+
/// since the program start!), with the current thread ID (i.e. `tid`), and with a [Sender] that
563+
/// can be used to send a [Message] to the thread that collects [Message]s and saves them to the
564+
/// trace file.
565+
#[inline(always)]
566+
fn with_elapsed_micros_subtracting_tracing(&self, f: impl Fn(f64, usize, &Sender<Message>)) {
567+
THREAD_DATA.with(|value| {
568+
let mut thread_data = value.borrow_mut();
569+
let (ThreadData { tid, out, start }, new_thread) = match thread_data.as_mut() {
570+
Some(thread_data) => (thread_data, false),
571+
None => {
572+
let tid = self.max_tid.fetch_add(1, Ordering::SeqCst);
573+
let out = self.out.lock().unwrap().clone();
574+
let start = TracingChromeInstant::setup_for_thread_and_start(tid);
575+
*thread_data = Some(ThreadData { tid, out, start });
576+
(thread_data.as_mut().unwrap(), true)
577+
}
578+
};
566579

567-
fn send_message(&self, message: Message) {
568-
OUT.with(move |val| {
569-
if val.borrow().is_some() {
570-
let _ignored = val.borrow().as_ref().unwrap().send(message);
571-
} else {
572-
let out = self.out.lock().unwrap().clone();
573-
let _ignored = out.send(message);
574-
val.replace(Some(out));
575-
}
580+
start.with_elapsed_micros_subtracting_tracing(|ts| {
581+
if new_thread {
582+
let name = match std::thread::current().name() {
583+
Some(name) => name.to_owned(),
584+
None => tid.to_string(),
585+
};
586+
let _ignored = out.send(Message::NewThread(*tid, name));
587+
}
588+
f(ts, *tid, out);
589+
});
576590
});
577591
}
578592
}
@@ -586,61 +600,68 @@ where
586600
return;
587601
}
588602

589-
let ts = self.get_ts();
590-
self.enter_span(ctx.span(id).expect("Span not found."), ts);
603+
self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
604+
self.enter_span(ctx.span(id).expect("Span not found."), ts, tid, out);
605+
});
591606
}
592607

593608
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
594609
if self.include_args {
595-
let span = ctx.span(id).unwrap();
596-
let mut exts = span.extensions_mut();
610+
self.with_elapsed_micros_subtracting_tracing(|_, _, _| {
611+
let span = ctx.span(id).unwrap();
612+
let mut exts = span.extensions_mut();
597613

598-
let args = exts.get_mut::<ArgsWrapper>();
614+
let args = exts.get_mut::<ArgsWrapper>();
599615

600-
if let Some(args) = args {
601-
let args = Arc::make_mut(&mut args.args);
602-
values.record(&mut JsonVisitor { object: args });
603-
}
616+
if let Some(args) = args {
617+
let args = Arc::make_mut(&mut args.args);
618+
values.record(&mut JsonVisitor { object: args });
619+
}
620+
});
604621
}
605622
}
606623

607624
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
608-
let ts = self.get_ts();
609-
let callsite = self.get_callsite(EventOrSpan::Event(event));
610-
self.send_message(Message::Event(ts, callsite));
625+
self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
626+
let callsite = self.get_callsite(EventOrSpan::Event(event), tid);
627+
let _ignored = out.send(Message::Event(ts, callsite));
628+
});
611629
}
612630

613631
fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
614632
if let TraceStyle::Async = self.trace_style {
615633
return;
616634
}
617-
let ts = self.get_ts();
618-
self.exit_span(ctx.span(id).expect("Span not found."), ts);
635+
self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
636+
self.exit_span(ctx.span(id).expect("Span not found."), ts, tid, out);
637+
});
619638
}
620639

621640
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
622-
if self.include_args {
623-
let mut args = Object::new();
624-
attrs.record(&mut JsonVisitor { object: &mut args });
625-
ctx.span(id).unwrap().extensions_mut().insert(ArgsWrapper {
626-
args: Arc::new(args),
627-
});
628-
}
629-
if let TraceStyle::Threaded = self.trace_style {
630-
return;
631-
}
641+
self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
642+
if self.include_args {
643+
let mut args = Object::new();
644+
attrs.record(&mut JsonVisitor { object: &mut args });
645+
ctx.span(id).unwrap().extensions_mut().insert(ArgsWrapper {
646+
args: Arc::new(args),
647+
});
648+
}
649+
if let TraceStyle::Threaded = self.trace_style {
650+
return;
651+
}
632652

633-
let ts = self.get_ts();
634-
self.enter_span(ctx.span(id).expect("Span not found."), ts);
653+
self.enter_span(ctx.span(id).expect("Span not found."), ts, tid, out);
654+
});
635655
}
636656

637657
fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
638658
if let TraceStyle::Threaded = self.trace_style {
639659
return;
640660
}
641661

642-
let ts = self.get_ts();
643-
self.exit_span(ctx.span(&id).expect("Span not found."), ts);
662+
self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
663+
self.exit_span(ctx.span(&id).expect("Span not found."), ts, tid, out);
664+
});
644665
}
645666
}
646667

0 commit comments

Comments
 (0)