tracing_profile/
lib.rs

1// Copyright 2024-2025 Irreducible Inc.
2
3//! A span based profiler, utilizing the [tracing](https://docs.rs/tracing/latest/tracing/) crate.
4//!
5//! # Overview
6//! This implementation of `tracing_subscriber::Layer<S>` records the time
7//! a span took to execute, along with any user supplied metadata and
8//! information necessary to construct a call graph from the resulting logs.
9//!
10//! Multiple `Layer` implementations are provided:
11//!     `PrintTreeLayer`: prints a call graph
12//!     `PrintPerfCountersLayer`: prints aggregated performance counters for each span.
13//!     `PerfettoLayer`: uses a local or system-wide perfetto tracing service to record data.
14//!     `IttApiLayer`: logs data in Intel's [ITT API](https://www.intel.com/content/www/us/en/docs/vtune-profiler/user-guide/2023-1/instrumentation-and-tracing-technology-apis.html)
15//!     `TracyLayer`: re-exports the `tracing_tracy::TracyLayer`.
16//!
17//! `init_tracing` is a convenience function that initializes the tracing with the default values
18//! depending on the features enabled and environment variables set.
19//!
20//! For advanced filename customization, use `init_tracing_with_builder` with `TraceFilenameBuilder`.
21//!
22//! ## Basic Usage
23//!
24//! ```no_run
25//! use tracing::instrument;
26//! use tracing::debug_span;
27//! use tracing_profile::init_tracing;
28//!
29//! #[instrument(skip_all, name= "graph_root", fields(a="b", c="d"))]
30//! fn entry_point() {
31//!     let span = debug_span!("some_span");
32//!     let _scope1 = span.enter();
33//!
34//!     let span2 = debug_span!("another_span", field1 = "value1");
35//!     let _scope2 = span2.enter();
36//! }
37//!
38//! fn main() {
39//!     // Initialize the tracing with the default values
40//!     // Note that the guard must be kept alive for the duration of the program.
41//!     let _guard = init_tracing().unwrap();
42//!
43//!     entry_point();
44//! }
45//! ```
46//!
47//! ## Advanced Usage with Custom Filenames
48//!
49//! With the `gen_filename` feature enabled, you can use a builder pattern:
50//!
51//! ```ignore
52//! use tracing_profile::{init_tracing_with_builder, TraceFilenameBuilder};
53//!
54//! // Create a custom filename builder
55//! let builder = TraceFilenameBuilder::new()
56//!     .name("my_application")
57//!     .iteration(1)
58//!     .timestamp()
59//!     .git_info()
60//!     .platform()
61//!     .hostname();
62//!
63//! // Initialize tracing with the custom builder
64//! let _guard = init_tracing_with_builder(builder).unwrap();
65//!
66//! // Your application code here...
67//! ```
68//!
69//! Note that if `#[instrument]` is used, `skip_all` is recommended. Omitting this will result in
70//! all the function arguments being included as fields.
71//!
72//! # Features
73//! The `panic` feature will turn eprintln! into panic!, causing the program to halt on errors.
74
75mod data;
76mod env_utils;
77mod errors;
78#[cfg(feature = "gen_filename")]
79pub mod filename_builder;
80#[cfg(feature = "gen_filename")]
81mod filename_utils;
82mod layers;
83pub mod utils;
84
85pub use layers::graph::{Config as PrintTreeConfig, Layer as PrintTreeLayer};
86#[cfg(feature = "ittapi")]
87pub use layers::ittapi::Layer as IttApiLayer;
88#[cfg(feature = "perf_counters")]
89pub use layers::print_perf_counters::Layer as PrintPerfCountersLayer;
90#[cfg(feature = "perf_counters")]
91pub use {
92    perf_event::events::Cache as PerfCacheEvent, perf_event::events::Event as PerfEvent,
93    perf_event::events::Hardware as PerfHardwareEvent,
94    perf_event::events::Software as PerfSoftwareEvent,
95};
96
97#[cfg(feature = "perfetto")]
98pub use layers::perfetto::{Layer as PerfettoLayer, PerfettoSettings as PerfettoCategorySettings};
99#[cfg(feature = "perfetto")]
100pub use perfetto_sys::PerfettoGuard;
101
102#[cfg(feature = "tracy")]
103pub use tracing_tracy::TracyLayer;
104
105#[cfg(feature = "gen_filename")]
106pub use filename_builder::{FilenameBuilderError, TraceFilenameBuilder};
107
108pub use layers::init_tracing::init_tracing;
109#[cfg(feature = "gen_filename")]
110pub use layers::init_tracing::init_tracing_with_builder;
111
112/// Test utilities for handling Perfetto test directory setup and cleanup.
113///
114/// **Internal Testing API**: This module is intended only for internal testing
115/// and should not be used by external consumers of this crate. The API may
116/// change without notice.
117///
118/// Note: Uses `#[doc(hidden)]` instead of `#[cfg(test)]` to enable access from
119/// integration tests while keeping it out of public documentation.
120#[doc(hidden)]
121pub mod test_utils {
122    /// RAII guard for Perfetto test directory management.
123    ///
124    /// Sets up temporary directory on creation, cleans up on drop (even if tests panic).
125    pub struct PerfettoTestDir {
126        path: String,
127    }
128
129    impl Default for PerfettoTestDir {
130        fn default() -> Self {
131            Self::new()
132        }
133    }
134
135    impl PerfettoTestDir {
136        /// Creates a new test directory guard.
137        pub fn new() -> Self {
138            use std::env;
139            let temp_dir = env::temp_dir().join("tracing_profile_tests");
140            std::fs::create_dir_all(&temp_dir).ok();
141            let temp_path = temp_dir.to_string_lossy().to_string();
142            env::set_var("PERFETTO_TRACE_DIR", &temp_path);
143            Self { path: temp_path }
144        }
145
146        /// Returns the path to the test directory
147        pub fn path(&self) -> &str {
148            &self.path
149        }
150    }
151
152    impl Drop for PerfettoTestDir {
153        /// Automatically cleans up the test directory and environment variables
154        fn drop(&mut self) {
155            std::fs::remove_dir_all(&self.path).ok();
156            std::env::remove_var("PERFETTO_TRACE_DIR");
157            // Also clean up any files that might have been created in current dir before we set the env var
158            let _ = std::fs::remove_file(".last_perfetto_trace_path");
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use std::thread;
166    use std::time::Duration;
167
168    use rusty_fork::rusty_fork_test;
169    use tracing::{debug_span, event, Level};
170
171    use super::*;
172    use crate::test_utils::PerfettoTestDir;
173
174    fn make_spans() {
175        event!(name: "event outside of span", Level::DEBUG, {value = 10});
176        event!(name: "test_instant_event", Level::DEBUG, test_key = "test_value");
177
178        {
179            let span = debug_span!("root span");
180            let _scope1 = span.enter();
181            thread::sleep(Duration::from_millis(20));
182
183            // child spans 1 and 2 are siblings
184            let span2 = debug_span!("child span1", field1 = "value1", perfetto_track_id = 5);
185            let scope2 = span2.enter();
186            thread::sleep(Duration::from_millis(20));
187            drop(scope2);
188
189            let span3 = debug_span!(
190                "child span2",
191                field2 = "value2",
192                value = 20,
193                perfetto_track_id = 5,
194                perfetto_flow_id = 10
195            );
196            let _scope3 = span3.enter();
197
198            thread::sleep(Duration::from_millis(20));
199            event!(name: "event in span2", Level::DEBUG, {value = 100});
200
201            // child spans 3 and 4 are siblings
202            let span = debug_span!("child span3", field3 = "value3");
203            let scope = span.enter();
204            thread::sleep(Duration::from_millis(20));
205            event!(name: "custom event", Level::DEBUG, {field5 = "value5", counter = true, value = 30});
206            drop(scope);
207
208            thread::spawn(|| {
209                let span = debug_span!("child span5", field5 = "value5");
210                let _scope = span.enter();
211                thread::sleep(Duration::from_millis(20));
212                event!(name: "custom event", Level::DEBUG, {field5 = "value6", counter = true, value = 10});
213            }).join().unwrap();
214
215            let span = debug_span!("child span4", field4 = "value4", perfetto_flow_id = 10);
216            thread::sleep(Duration::from_millis(20));
217            event!(name: "custom event", Level::DEBUG, {field5 = "value5", counter = true, value = 40});
218            let scope = span.enter();
219            thread::sleep(Duration::from_millis(20));
220            drop(scope);
221        }
222        event!(name: "event after last span", Level::DEBUG, {value = 20});
223    }
224
225    // Since tracing_subscriber::registry() is a global singleton, we need to run the tests in separate processes.
226    rusty_fork_test! {
227        #[test]
228        fn all_layers() {
229            let _test_dir = PerfettoTestDir::new();
230            let _guard = init_tracing().unwrap();
231
232            make_spans();
233        }
234    }
235}