tracing_flame/lib.rs
1//! A Tracing [Layer][`FlameLayer`] for generating a folded stack trace for generating flamegraphs
2//! and flamecharts with [`inferno`]
3//!
4//! # Overview
5//!
6//! [`tracing`] is a framework for instrumenting Rust programs to collect
7//! scoped, structured, and async-aware diagnostics. `tracing-flame` provides helpers
8//! for consuming `tracing` instrumentation that can later be visualized as a
9//! flamegraph/flamechart. Flamegraphs/flamecharts are useful for identifying performance
10//! issues bottlenecks in an application. For more details, see Brendan Gregg's [post]
11//! on flamegraphs.
12//!
13//! *Compiler support: [requires `rustc` 1.42+][msrv]*
14//!
15//! [msrv]: #supported-rust-versions
16//! [post]: http://www.brendangregg.com/flamegraphs.html
17//!
18//! ## Usage
19//!
20//! This crate is meant to be used in a two step process:
21//!
22//! 1. Capture textual representation of the spans that are entered and exited
23//! with [`FlameLayer`].
24//! 2. Feed the textual representation into `inferno-flamegraph` to generate the
25//! flamegraph or flamechart.
26//!
27//! *Note*: when using a buffered writer as the writer for a `FlameLayer`, it is necessary to
28//! ensure that the buffer has been flushed before the data is passed into
29//! [`inferno-flamegraph`]. For more details on how to flush the internal writer
30//! of the `FlameLayer`, see the docs for [`FlushGuard`].
31//!
32//! ## Layer Setup
33//!
34//! ```rust
35//! use std::{fs::File, io::BufWriter};
36//! use tracing_flame::FlameLayer;
37//! use tracing_subscriber::{registry::Registry, prelude::*, fmt};
38//!
39//! fn setup_global_subscriber() -> impl Drop {
40//! let fmt_layer = fmt::Layer::default();
41//!
42//! let (flame_layer, _guard) = FlameLayer::with_file("./tracing.folded").unwrap();
43//!
44//! let subscriber = Registry::default()
45//! .with(fmt_layer)
46//! .with(flame_layer);
47//!
48//! tracing::subscriber::set_global_default(subscriber).expect("Could not set global default");
49//! _guard
50//! }
51//!
52//! // your code here ..
53//! ```
54//!
55//! As an alternative, you can provide _any_ type that implements `std::io::Write` to
56//! `FlameLayer::new`.
57//!
58//! ## Generating the Image
59//!
60//! To convert the textual representation of a flamegraph to a visual one, first install `inferno`:
61//!
62//! ```console
63//! cargo install inferno
64//! ```
65//!
66//! Then, pass the file created by `FlameLayer` into `inferno-flamegraph`:
67//!
68//! ```console
69//! # flamegraph
70//! cat tracing.folded | inferno-flamegraph > tracing-flamegraph.svg
71//!
72//! # flamechart
73//! cat tracing.folded | inferno-flamegraph --flamechart > tracing-flamechart.svg
74//! ```
75//!
76//! ## Differences between `flamegraph`s and `flamechart`s
77//!
78//! By default, `inferno-flamegraph` creates flamegraphs. Flamegraphs operate by
79//! that collapsing identical stack frames and sorting them on the frame's names.
80//!
81//! This behavior is great for multithreaded programs and long-running programs
82//! where the same frames occur _many_ times, for short durations, because it reduces
83//! noise in the graph and gives the reader a better idea of the
84//! overall time spent in each part of the application.
85//!
86//! However, it is sometimes desirable to preserve the _exact_ ordering of events
87//! as they were emitted by `tracing-flame`, so that it is clear when each
88//! span is entered relative to others and get an accurate visual trace of
89//! the execution of your program. This representation is best created with a
90//! _flamechart_, which _does not_ sort or collapse identical stack frames.
91//!
92//! [`tracing`]: https://docs.rs/tracing
93//! [`inferno`]: https://docs.rs/inferno
94//! [`FlameLayer`]: struct.FlameLayer.html
95//! [`FlushGuard`]: struct.FlushGuard.html
96//! [`inferno-flamegraph`]: https://docs.rs/inferno/0.9.5/inferno/index.html#producing-a-flame-graph
97//!
98//! ## Supported Rust Versions
99//!
100//! Tracing is built against the latest stable release. The minimum supported
101//! version is 1.42. The current Tracing version is not guaranteed to build on
102//! Rust versions earlier than the minimum supported version.
103//!
104//! Tracing follows the same compiler support policies as the rest of the Tokio
105//! project. The current stable Rust compiler and the three most recent minor
106//! versions before it will always be supported. For example, if the current
107//! stable compiler version is 1.45, the minimum supported version will not be
108//! increased past 1.42, three minor versions prior. Increasing the minimum
109//! supported compiler version is not considered a semver breaking change as
110//! long as doing so complies with this policy.
111//!
112#![doc(
113 html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png",
114 issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/"
115)]
116#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
117#![warn(
118 missing_debug_implementations,
119 missing_docs,
120 rust_2018_idioms,
121 unreachable_pub,
122 bad_style,
123 const_err,
124 dead_code,
125 improper_ctypes,
126 non_shorthand_field_patterns,
127 no_mangle_generic_items,
128 overflowing_literals,
129 path_statements,
130 patterns_in_fns_without_body,
131 private_in_public,
132 unconditional_recursion,
133 unused,
134 unused_allocation,
135 unused_comparisons,
136 unused_parens,
137 while_true
138)]
139
140pub use error::Error;
141
142use error::Kind;
143use lazy_static::lazy_static;
144use std::cell::Cell;
145use std::fmt;
146use std::fmt::Write as _;
147use std::fs::File;
148use std::io::BufWriter;
149use std::io::Write;
150use std::marker::PhantomData;
151use std::path::Path;
152use std::sync::Arc;
153use std::sync::Mutex;
154use std::time::{Duration, Instant};
155use tracing::span;
156use tracing::Subscriber;
157use tracing_subscriber::layer::Context;
158use tracing_subscriber::registry::LookupSpan;
159use tracing_subscriber::registry::SpanRef;
160use tracing_subscriber::Layer;
161
162mod error;
163
164lazy_static! {
165 static ref START: Instant = Instant::now();
166}
167
168thread_local! {
169 static LAST_EVENT: Cell<Instant> = Cell::new(*START);
170
171 static THREAD_NAME: String = {
172 let thread = std::thread::current();
173 let mut thread_name = format!("{:?}", thread.id());
174 if let Some(name) = thread.name() {
175 thread_name += "-";
176 thread_name += name;
177 }
178 thread_name
179 };
180}
181
182/// A `Layer` that records span open/close events as folded flamegraph stack
183/// samples.
184///
185/// The output of `FlameLayer` emulates the output of commands like `perf` once
186/// they've been collapsed by `inferno-flamegraph`. The output of this layer
187/// should look similar to the output of the following commands:
188///
189/// ```sh
190/// perf record --call-graph dwarf target/release/mybin
191/// perf script | inferno-collapse-perf > stacks.folded
192/// ```
193///
194/// # Sample Counts
195///
196/// Because `tracing-flame` doesn't use sampling, the number at the end of each
197/// folded stack trace does not represent a number of samples of that stack.
198/// Instead, the numbers on each line are the number of nanoseconds since the
199/// last event in the same thread.
200///
201/// # Dropping and Flushing
202///
203/// If you use a global subscriber the drop implementations on your various
204/// layers will not get called when your program exits. This means that if
205/// you're using a buffered writer as the inner writer for the `FlameLayer`
206/// you're not guaranteed to see all the events that have been emitted in the
207/// file by default.
208///
209/// To ensure all data is flushed when the program exits, `FlameLayer` exposes
210/// the [`flush_on_drop`] function, which returns a [`FlushGuard`]. The `FlushGuard`
211/// will flush the writer when it is dropped. If necessary, it can also be used to manually
212/// flush the writer.
213///
214/// [`flush_on_drop`]: struct.FlameLayer.html#method.flush_on_drop
215/// [`FlushGuard`]: struct.FlushGuard.html
216#[derive(Debug)]
217pub struct FlameLayer<S, W> {
218 out: Arc<Mutex<W>>,
219 config: Config,
220 _inner: PhantomData<S>,
221}
222
223#[derive(Debug)]
224struct Config {
225 /// Don't include samples where no spans are open
226 empty_samples: bool,
227
228 /// Don't include thread_id
229 threads_collapsed: bool,
230
231 /// Don't display module_path
232 module_path: bool,
233
234 /// Don't display file and line
235 file_and_line: bool,
236}
237
238impl Default for Config {
239 fn default() -> Self {
240 Self {
241 empty_samples: true,
242 threads_collapsed: false,
243 module_path: true,
244 file_and_line: true,
245 }
246 }
247}
248
249/// An RAII guard for managing flushing a global writer that is
250/// otherwise inaccessible.
251///
252/// This type is only needed when using
253/// `tracing::subscriber::set_global_default`, which prevents the drop
254/// implementation of layers from running when the program exits.
255#[must_use]
256#[derive(Debug)]
257pub struct FlushGuard<W>
258where
259 W: Write + 'static,
260{
261 out: Arc<Mutex<W>>,
262}
263
264impl<S, W> FlameLayer<S, W>
265where
266 S: Subscriber + for<'span> LookupSpan<'span>,
267 W: Write + 'static,
268{
269 /// Returns a new `FlameLayer` that outputs all folded stack samples to the
270 /// provided writer.
271 pub fn new(writer: W) -> Self {
272 // Initialize the start used by all threads when initializing the
273 // LAST_EVENT when constructing the layer
274 let _unused = *START;
275 Self {
276 out: Arc::new(Mutex::new(writer)),
277 config: Default::default(),
278 _inner: PhantomData,
279 }
280 }
281
282 /// Returns a `FlushGuard` which will flush the `FlameLayer`'s writer when
283 /// it is dropped, or when `flush` is manually invoked on the guard.
284 pub fn flush_on_drop(&self) -> FlushGuard<W> {
285 FlushGuard {
286 out: self.out.clone(),
287 }
288 }
289
290 /// Configures whether or not periods of time where no spans are entered
291 /// should be included in the output.
292 ///
293 /// Defaults to `true`.
294 ///
295 /// Setting this feature to false can help with situations where no span is
296 /// active for large periods of time. This can include time spent idling, or
297 /// doing uninteresting work that isn't being measured.
298 /// When a large number of empty samples are recorded, the flamegraph
299 /// may be harder to interpret and navigate, since the recorded spans will
300 /// take up a correspondingly smaller percentage of the graph. In some
301 /// cases, a large number of empty samples may even hide spans which
302 /// would otherwise appear in the flamegraph.
303 pub fn with_empty_samples(mut self, enabled: bool) -> Self {
304 self.config.empty_samples = enabled;
305 self
306 }
307
308 /// Configures whether or not spans from different threads should be
309 /// collapsed into one pool of events.
310 ///
311 /// Defaults to `false`.
312 ///
313 /// Setting this feature to true can help with applications that distribute
314 /// work evenly across many threads, such as thread pools. In such
315 /// cases it can be difficult to get an overview of where the application
316 /// as a whole spent most of its time, because work done in the same
317 /// span may be split up across many threads.
318 pub fn with_threads_collapsed(mut self, enabled: bool) -> Self {
319 self.config.threads_collapsed = enabled;
320 self
321 }
322
323 /// Configures whether or not module paths should be included in the output.
324 pub fn with_module_path(mut self, enabled: bool) -> Self {
325 self.config.module_path = enabled;
326 self
327 }
328
329 /// Configures whether or not file and line should be included in the output.
330 pub fn with_file_and_line(mut self, enabled: bool) -> Self {
331 self.config.file_and_line = enabled;
332 self
333 }
334}
335
336impl<W> FlushGuard<W>
337where
338 W: Write + 'static,
339{
340 /// Flush the internal writer of the `FlameLayer`, ensuring that all
341 /// intermediately buffered contents reach their destination.
342 pub fn flush(&self) -> Result<(), Error> {
343 let mut guard = match self.out.lock() {
344 Ok(guard) => guard,
345 Err(e) => {
346 if !std::thread::panicking() {
347 panic!("{}", e);
348 } else {
349 return Ok(());
350 }
351 }
352 };
353
354 guard.flush().map_err(Kind::FlushFile).map_err(Error)
355 }
356}
357
358impl<W> Drop for FlushGuard<W>
359where
360 W: Write + 'static,
361{
362 fn drop(&mut self) {
363 match self.flush() {
364 Ok(_) => (),
365 Err(e) => e.report(),
366 }
367 }
368}
369
370impl<S> FlameLayer<S, BufWriter<File>>
371where
372 S: Subscriber + for<'span> LookupSpan<'span>,
373{
374 /// Constructs a `FlameLayer` that outputs to a `BufWriter` to the given path, and a
375 /// `FlushGuard` to ensure the writer is flushed.
376 pub fn with_file(path: impl AsRef<Path>) -> Result<(Self, FlushGuard<BufWriter<File>>), Error> {
377 let path = path.as_ref();
378 let file = File::create(path)
379 .map_err(|source| Kind::CreateFile {
380 path: path.into(),
381 source,
382 })
383 .map_err(Error)?;
384 let writer = BufWriter::new(file);
385 let layer = Self::new(writer);
386 let guard = layer.flush_on_drop();
387 Ok((layer, guard))
388 }
389}
390
391impl<S, W> Layer<S> for FlameLayer<S, W>
392where
393 S: Subscriber + for<'span> LookupSpan<'span>,
394 W: Write + 'static,
395{
396 fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
397 let samples = self.time_since_last_event();
398
399 let first = ctx.span(id).expect("expected: span id exists in registry");
400
401 if !self.config.empty_samples && first.parent().is_none() {
402 return;
403 }
404
405 let mut stack = String::new();
406
407 if !self.config.threads_collapsed {
408 THREAD_NAME.with(|name| stack += name.as_str());
409 } else {
410 stack += "all-threads";
411 }
412
413 if let Some(second) = first.parent() {
414 for parent in second.scope().from_root() {
415 stack += "; ";
416 write(&mut stack, parent, &self.config)
417 .expect("expected: write to String never fails");
418 }
419 }
420
421 write!(&mut stack, " {}", samples.as_nanos())
422 .expect("expected: write to String never fails");
423
424 let _ = writeln!(*self.out.lock().unwrap(), "{}", stack);
425 }
426
427 fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
428 let panicking = std::thread::panicking();
429 macro_rules! expect {
430 ($e:expr, $msg:literal) => {
431 if panicking {
432 return;
433 } else {
434 $e.expect($msg)
435 }
436 };
437 ($e:expr) => {
438 if panicking {
439 return;
440 } else {
441 $e.unwrap()
442 }
443 };
444 }
445
446 let samples = self.time_since_last_event();
447 let first = expect!(ctx.span(id), "expected: span id exists in registry");
448
449 let mut stack = String::new();
450 if !self.config.threads_collapsed {
451 THREAD_NAME.with(|name| stack += name.as_str());
452 } else {
453 stack += "all-threads";
454 }
455
456 for parent in first.scope().from_root() {
457 stack += "; ";
458 expect!(
459 write(&mut stack, parent, &self.config),
460 "expected: write to String never fails"
461 );
462 }
463
464 expect!(
465 write!(&mut stack, " {}", samples.as_nanos()),
466 "expected: write to String never fails"
467 );
468
469 let _ = writeln!(*expect!(self.out.lock()), "{}", stack);
470 }
471}
472
473impl<S, W> FlameLayer<S, W>
474where
475 S: Subscriber + for<'span> LookupSpan<'span>,
476 W: Write + 'static,
477{
478 fn time_since_last_event(&self) -> Duration {
479 let now = Instant::now();
480
481 let prev = LAST_EVENT.with(|e| {
482 let prev = e.get();
483 e.set(now);
484 prev
485 });
486
487 now - prev
488 }
489}
490
491fn write<S>(dest: &mut String, span: SpanRef<'_, S>, config: &Config) -> fmt::Result
492where
493 S: Subscriber + for<'span> LookupSpan<'span>,
494{
495 if config.module_path {
496 if let Some(module_path) = span.metadata().module_path() {
497 write!(dest, "{}::", module_path)?;
498 }
499 }
500
501 write!(dest, "{}", span.name())?;
502
503 if config.file_and_line {
504 if let Some(file) = span.metadata().file() {
505 write!(dest, ":{}", file)?;
506 }
507
508 if let Some(line) = span.metadata().line() {
509 write!(dest, ":{}", line)?;
510 }
511 }
512
513 Ok(())
514}