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}