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}