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