Skip to main content

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.65+][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//! [`inferno`]: https://docs.rs/inferno
93//! [`inferno-flamegraph`]: https://docs.rs/inferno/0.9.5/inferno/index.html#producing-a-flame-graph
94//!
95//! ## Supported Rust Versions
96//!
97//! Tracing is built against the latest stable release. The minimum supported
98//! version is 1.65. The current Tracing version is not guaranteed to build on
99//! Rust versions earlier than the minimum supported version.
100//!
101//! Tracing follows the same compiler support policies as the rest of the Tokio
102//! project. The current stable Rust compiler and the three most recent minor
103//! versions before it will always be supported. For example, if the current
104//! stable compiler version is 1.69, the minimum supported version will not be
105//! increased past 1.66, three minor versions prior. Increasing the minimum
106//! supported compiler version is not considered a semver breaking change as
107//! long as doing so complies with this policy.
108//!
109#![doc(
110    html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/main/assets/logo-type.png",
111    html_favicon_url = "https://raw.githubusercontent.com/tokio-rs/tracing/main/assets/favicon.ico",
112    issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/"
113)]
114#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
115#![warn(
116    missing_debug_implementations,
117    missing_docs,
118    rust_2018_idioms,
119    unreachable_pub,
120    bad_style,
121    dead_code,
122    improper_ctypes,
123    non_shorthand_field_patterns,
124    no_mangle_generic_items,
125    overflowing_literals,
126    path_statements,
127    patterns_in_fns_without_body,
128    private_interfaces,
129    private_bounds,
130    unconditional_recursion,
131    unused,
132    unused_allocation,
133    unused_comparisons,
134    unused_parens,
135    while_true
136)]
137
138use error::Error;
139use error::Kind;
140use once_cell::sync::Lazy;
141use std::cell::Cell;
142use std::fmt;
143use std::fmt::Write as _;
144use std::fs::File;
145use std::io::BufWriter;
146use std::io::Write;
147use std::marker::PhantomData;
148use std::path::Path;
149use std::sync::Arc;
150use std::sync::Mutex;
151use std::time::{Duration, Instant};
152use tracing::span;
153use tracing::Subscriber;
154use tracing_subscriber::layer::Context;
155use tracing_subscriber::registry::LookupSpan;
156use tracing_subscriber::registry::SpanRef;
157use tracing_subscriber::Layer;
158
159mod error;
160
161static START: Lazy<Instant> = Lazy::new(Instant::now);
162
163thread_local! {
164    static LAST_EVENT: Cell<Instant> = Cell::new(*START);
165
166    static THREAD_NAME: String = {
167        let thread = std::thread::current();
168        let mut thread_name = format!("{:?}", thread.id());
169        if let Some(name) = thread.name() {
170            thread_name += "-";
171            thread_name += name;
172        }
173        thread_name
174    };
175}
176
177/// A `Layer` that records span open/close events as folded flamegraph stack
178/// samples.
179///
180/// The output of `FlameLayer` emulates the output of commands like `perf` once
181/// they've been collapsed by `inferno-flamegraph`. The output of this layer
182/// should look similar to the output of the following commands:
183///
184/// ```sh
185/// perf record --call-graph dwarf target/release/mybin
186/// perf script | inferno-collapse-perf > stacks.folded
187/// ```
188///
189/// # Sample Counts
190///
191/// Because `tracing-flame` doesn't use sampling, the number at the end of each
192/// folded stack trace does not represent a number of samples of that stack.
193/// Instead, the numbers on each line are the number of nanoseconds since the
194/// last event in the same thread.
195///
196/// # Dropping and Flushing
197///
198/// If you use a global subscriber the drop implementations on your various
199/// layers will not get called when your program exits. This means that if
200/// you're using a buffered writer as the inner writer for the `FlameLayer`
201/// you're not guaranteed to see all the events that have been emitted in the
202/// file by default.
203///
204/// To ensure all data is flushed when the program exits, `FlameLayer` exposes
205/// the [`flush_on_drop`] function, which returns a [`FlushGuard`]. The `FlushGuard`
206/// will flush the writer when it is dropped. If necessary, it can also be used to manually
207/// flush the writer.
208///
209/// [`flush_on_drop`]: FlameLayer::flush_on_drop
210#[derive(Debug)]
211pub struct FlameLayer<S, W> {
212    out: Arc<Mutex<W>>,
213    config: Config,
214    _inner: PhantomData<S>,
215}
216
217#[derive(Debug)]
218struct Config {
219    /// Don't include samples where no spans are open
220    empty_samples: bool,
221
222    /// Don't include thread_id
223    threads_collapsed: bool,
224
225    /// Don't display module_path
226    module_path: bool,
227
228    /// Don't display file and line
229    file_and_line: bool,
230}
231
232impl Default for Config {
233    fn default() -> Self {
234        Self {
235            empty_samples: true,
236            threads_collapsed: false,
237            module_path: true,
238            file_and_line: false,
239        }
240    }
241}
242
243/// An RAII guard for managing flushing a global writer that is
244/// otherwise inaccessible.
245///
246/// This type is only needed when using
247/// `tracing::subscriber::set_global_default`, which prevents the drop
248/// implementation of layers from running when the program exits.
249#[must_use]
250#[derive(Debug)]
251pub struct FlushGuard<W>
252where
253    W: Write + 'static,
254{
255    out: Arc<Mutex<W>>,
256}
257
258impl<S, W> FlameLayer<S, W>
259where
260    S: Subscriber + for<'span> LookupSpan<'span>,
261    W: Write + 'static,
262{
263    /// Returns a new `FlameLayer` that outputs all folded stack samples to the
264    /// provided writer.
265    pub fn new(writer: W) -> Self {
266        // Initialize the start used by all threads when initializing the
267        // LAST_EVENT when constructing the layer
268        let _unused = *START;
269        Self {
270            out: Arc::new(Mutex::new(writer)),
271            config: Default::default(),
272            _inner: PhantomData,
273        }
274    }
275
276    /// Returns a `FlushGuard` which will flush the `FlameLayer`'s writer when
277    /// it is dropped, or when `flush` is manually invoked on the guard.
278    pub fn flush_on_drop(&self) -> FlushGuard<W> {
279        FlushGuard {
280            out: self.out.clone(),
281        }
282    }
283
284    /// Configures whether or not periods of time where no spans are entered
285    /// should be included in the output.
286    ///
287    /// Defaults to `true`.
288    ///
289    /// Setting this feature to false can help with situations where no span is
290    /// active for large periods of time. This can include time spent idling, or
291    /// doing uninteresting work that isn't being measured.
292    /// When a large number of empty samples are recorded, the flamegraph
293    /// may be harder to interpret and navigate, since the recorded spans will
294    /// take up a correspondingly smaller percentage of the graph. In some
295    /// cases, a large number of empty samples may even hide spans which
296    /// would otherwise appear in the flamegraph.
297    pub fn with_empty_samples(mut self, enabled: bool) -> Self {
298        self.config.empty_samples = enabled;
299        self
300    }
301
302    /// Configures whether or not spans from different threads should be
303    /// collapsed into one pool of events.
304    ///
305    /// Defaults to `false`.
306    ///
307    /// Setting this feature to true can help with applications that distribute
308    /// work evenly across many threads, such as thread pools. In such
309    /// cases it can be difficult to get an overview of where the application
310    /// as a whole spent most of its time, because work done in the same
311    /// span may be split up across many threads.
312    pub fn with_threads_collapsed(mut self, enabled: bool) -> Self {
313        self.config.threads_collapsed = enabled;
314        self
315    }
316
317    /// Configures whether or not module paths should be included in the output.
318    pub fn with_module_path(mut self, enabled: bool) -> Self {
319        self.config.module_path = enabled;
320        self
321    }
322
323    /// Configures whether or not file and line should be included in the output.
324    pub fn with_file_and_line(mut self, enabled: bool) -> Self {
325        self.config.file_and_line = enabled;
326        self
327    }
328}
329
330impl<W> FlushGuard<W>
331where
332    W: Write + 'static,
333{
334    /// Flush the internal writer of the `FlameLayer`, ensuring that all
335    /// intermediately buffered contents reach their destination.
336    pub fn flush(&self) -> Result<(), Error> {
337        let mut guard = match self.out.lock() {
338            Ok(guard) => guard,
339            Err(e) => {
340                if !std::thread::panicking() {
341                    panic!("{}", e);
342                } else {
343                    return Ok(());
344                }
345            }
346        };
347
348        guard.flush().map_err(Kind::FlushFile).map_err(Error)
349    }
350}
351
352impl<W> Drop for FlushGuard<W>
353where
354    W: Write + 'static,
355{
356    fn drop(&mut self) {
357        match self.flush() {
358            Ok(_) => (),
359            Err(e) => e.report(),
360        }
361    }
362}
363
364impl<S> FlameLayer<S, BufWriter<File>>
365where
366    S: Subscriber + for<'span> LookupSpan<'span>,
367{
368    /// Constructs a `FlameLayer` that outputs to a `BufWriter` to the given path, and a
369    /// `FlushGuard` to ensure the writer is flushed.
370    pub fn with_file(path: impl AsRef<Path>) -> Result<(Self, FlushGuard<BufWriter<File>>), Error> {
371        let path = path.as_ref();
372        let file = File::create(path)
373            .map_err(|source| Kind::CreateFile {
374                path: path.into(),
375                source,
376            })
377            .map_err(Error)?;
378        let writer = BufWriter::new(file);
379        let layer = Self::new(writer);
380        let guard = layer.flush_on_drop();
381        Ok((layer, guard))
382    }
383}
384
385impl<S, W> Layer<S> for FlameLayer<S, W>
386where
387    S: Subscriber + for<'span> LookupSpan<'span>,
388    W: Write + 'static,
389{
390    fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
391        let samples = self.time_since_last_event();
392
393        let first = ctx.span(id).expect("expected: span id exists in registry");
394
395        if !self.config.empty_samples && first.parent().is_none() {
396            return;
397        }
398
399        let mut stack = String::new();
400
401        if !self.config.threads_collapsed {
402            THREAD_NAME.with(|name| stack += name.as_str());
403        } else {
404            stack += "all-threads";
405        }
406
407        if let Some(second) = first.parent() {
408            for parent in second.scope().from_root() {
409                stack += ";";
410                write(&mut stack, parent, &self.config)
411                    .expect("expected: write to String never fails");
412            }
413        }
414
415        write!(&mut stack, " {}", samples.as_nanos())
416            .expect("expected: write to String never fails");
417
418        let _ = writeln!(*self.out.lock().unwrap(), "{}", stack);
419    }
420
421    fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
422        let panicking = std::thread::panicking();
423        macro_rules! expect {
424            ($e:expr, $msg:literal) => {
425                if panicking {
426                    return;
427                } else {
428                    $e.expect($msg)
429                }
430            };
431            ($e:expr) => {
432                if panicking {
433                    return;
434                } else {
435                    $e.unwrap()
436                }
437            };
438        }
439
440        let samples = self.time_since_last_event();
441        let first = expect!(ctx.span(id), "expected: span id exists in registry");
442
443        let mut stack = String::new();
444        if !self.config.threads_collapsed {
445            THREAD_NAME.with(|name| stack += name.as_str());
446        } else {
447            stack += "all-threads";
448        }
449
450        for parent in first.scope().from_root() {
451            stack += ";";
452            expect!(
453                write(&mut stack, parent, &self.config),
454                "expected: write to String never fails"
455            );
456        }
457
458        expect!(
459            write!(&mut stack, " {}", samples.as_nanos()),
460            "expected: write to String never fails"
461        );
462
463        let _ = writeln!(*expect!(self.out.lock()), "{}", stack);
464    }
465}
466
467impl<S, W> FlameLayer<S, W>
468where
469    S: Subscriber + for<'span> LookupSpan<'span>,
470    W: Write + 'static,
471{
472    fn time_since_last_event(&self) -> Duration {
473        let now = Instant::now();
474
475        let prev = LAST_EVENT.with(|e| {
476            let prev = e.get();
477            e.set(now);
478            prev
479        });
480
481        now - prev
482    }
483}
484
485fn write<S>(dest: &mut String, span: SpanRef<'_, S>, config: &Config) -> fmt::Result
486where
487    S: Subscriber + for<'span> LookupSpan<'span>,
488{
489    if config.module_path {
490        if let Some(module_path) = span.metadata().module_path() {
491            write!(dest, "{}::", module_path)?;
492        }
493    }
494
495    write!(dest, "{}", span.name())?;
496
497    if config.file_and_line {
498        if let Some(file) = span.metadata().file() {
499            write!(dest, ":{}", file)?;
500        }
501
502        if let Some(line) = span.metadata().line() {
503            write!(dest, ":{}", line)?;
504        }
505    }
506
507    Ok(())
508}