• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.49+][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.49. 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.45, the minimum supported version will not be
105 //! increased past 1.42, 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/master/assets/logo-type.png",
111     issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/"
112 )]
113 #![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
114 #![warn(
115     missing_debug_implementations,
116     missing_docs,
117     rust_2018_idioms,
118     unreachable_pub,
119     bad_style,
120     const_err,
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_in_public,
129     unconditional_recursion,
130     unused,
131     unused_allocation,
132     unused_comparisons,
133     unused_parens,
134     while_true
135 )]
136 
137 pub use error::Error;
138 
139 use error::Kind;
140 use once_cell::sync::Lazy;
141 use std::cell::Cell;
142 use std::fmt;
143 use std::fmt::Write as _;
144 use std::fs::File;
145 use std::io::BufWriter;
146 use std::io::Write;
147 use std::marker::PhantomData;
148 use std::path::Path;
149 use std::sync::Arc;
150 use std::sync::Mutex;
151 use std::time::{Duration, Instant};
152 use tracing::span;
153 use tracing::Subscriber;
154 use tracing_subscriber::layer::Context;
155 use tracing_subscriber::registry::LookupSpan;
156 use tracing_subscriber::registry::SpanRef;
157 use tracing_subscriber::Layer;
158 
159 mod error;
160 
161 static START: Lazy<Instant> = Lazy::new(Instant::now);
162 
163 thread_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)]
211 pub struct FlameLayer<S, W> {
212     out: Arc<Mutex<W>>,
213     config: Config,
214     _inner: PhantomData<S>,
215 }
216 
217 #[derive(Debug)]
218 struct 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 
232 impl Default for Config {
default() -> Self233     fn default() -> Self {
234         Self {
235             empty_samples: true,
236             threads_collapsed: false,
237             module_path: true,
238             file_and_line: true,
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)]
251 pub struct FlushGuard<W>
252 where
253     W: Write + 'static,
254 {
255     out: Arc<Mutex<W>>,
256 }
257 
258 impl<S, W> FlameLayer<S, W>
259 where
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.
new(writer: W) -> Self265     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.
flush_on_drop(&self) -> FlushGuard<W>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.
with_empty_samples(mut self, enabled: bool) -> Self297     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.
with_threads_collapsed(mut self, enabled: bool) -> Self312     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.
with_module_path(mut self, enabled: bool) -> Self318     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.
with_file_and_line(mut self, enabled: bool) -> Self324     pub fn with_file_and_line(mut self, enabled: bool) -> Self {
325         self.config.file_and_line = enabled;
326         self
327     }
328 }
329 
330 impl<W> FlushGuard<W>
331 where
332     W: Write + 'static,
333 {
334     /// Flush the internal writer of the `FlameLayer`, ensuring that all
335     /// intermediately buffered contents reach their destination.
flush(&self) -> Result<(), Error>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 
352 impl<W> Drop for FlushGuard<W>
353 where
354     W: Write + 'static,
355 {
drop(&mut self)356     fn drop(&mut self) {
357         match self.flush() {
358             Ok(_) => (),
359             Err(e) => e.report(),
360         }
361     }
362 }
363 
364 impl<S> FlameLayer<S, BufWriter<File>>
365 where
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.
with_file(path: impl AsRef<Path>) -> Result<(Self, FlushGuard<BufWriter<File>>), Error>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 
385 impl<S, W> Layer<S> for FlameLayer<S, W>
386 where
387     S: Subscriber + for<'span> LookupSpan<'span>,
388     W: Write + 'static,
389 {
on_enter(&self, id: &span::Id, ctx: Context<'_, S>)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 
on_exit(&self, id: &span::Id, ctx: Context<'_, S>)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 
467 impl<S, W> FlameLayer<S, W>
468 where
469     S: Subscriber + for<'span> LookupSpan<'span>,
470     W: Write + 'static,
471 {
time_since_last_event(&self) -> Duration472     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 
write<S>(dest: &mut String, span: SpanRef<'_, S>, config: &Config) -> fmt::Result where S: Subscriber + for<'span> LookupSpan<'span>,485 fn write<S>(dest: &mut String, span: SpanRef<'_, S>, config: &Config) -> fmt::Result
486 where
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 }
509