A low-overhead, low-dependency Rust crate for use with the tracing
ecosystem which outputs a Perfetto protobuf trace stream for
visualization in the UI. It streams events directly to
a file or any other std::io::Write sink
(e.g. network connection, or compression encoder) without an SDK or any external daemons.
Open generated .pftrace files in Perfetto UI, or analyze them
programmatically with the Trace Processor
Python bindings.
There are lots of other tracing crates out there, outputting both the Perfetto format
and other comparable formats. So why choose this one?
Compared to other crates, it:
- works with native
tracinginstrumentation and does not require you to change how you instrument your code. - is highly performant while still conforming to the
tracing-subscriberlayer API. - has few dependencies and does not require a heavyweight SDK, generated protobuf code, or an external capture process.
- preserves complete span lifetimes when spans move between threads or close on a different thread.
- respects parent-child relationships by emitting explicitly parented Perfetto tracks instead of relying on same-thread BEGIN/END nesting.
- uses boot-time timestamps and emits clock snapshots, allowing its output to be aligned and
merged with other captures such as
perf/Samply profiles and system scheduling data. - supports visualizing spans in one of three ways:
SpanTracks(default) gives every span its own lifetime track, with optional nestedpollslices for enter-to-exit intervals.ThreadTracksshows each enter-to-exit interval on the executing thread and does not emit separate lifetime tracks.Bothcombines the lifetime and execution views.
- can additionally emit structured event and span fields as queryable debug annotations, source
locations, numeric counters, and
follows_fromrelationships as causal flows.
cargo add tracing-perfetto-file tracing tracing-subscriberuse std::path::Path;
use tracing_perfetto_file::{FlushGuard, PerfettoLayer};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
fn setup_tracing(path: impl AsRef<Path>) -> std::io::Result<FlushGuard> {
let file = std::fs::File::create(path)?;
// The writer can be any owned `Write + Send` sink, e.g. a `TcpStream`.
let (layer, guard) = PerfettoLayer::builder(file)
.with_debug_annotations()
.with_source_locations()
.with_counters()
.build();
tracing_subscriber::registry().with(layer).init();
Ok(guard)
}
#[tracing::instrument(skip_all, fields(items = items.len()))]
fn load_data(items: &[&str]) {
tracing::info!(first = items[0], "loaded data");
tracing::info!(counter.items_loaded = items.len() as u64);
}
fn main() -> std::io::Result<()> {
let guard = setup_tracing("trace.pftrace")?;
load_data(&["users", "orders", "products"]);
guard.flush()
}PerfettoLayer::builder is not limited to files. For example, pass an owned
std::net::TcpStream to stream the trace to a collector, or a compression encoder to compress
it as it is emitted. The sink must implement Write + Send + 'static.
Values are nanoseconds per operation on a 6-core Intel Xeon Ice Lake Linux VM:
tracing-subscriber layer |
Event | Event + fields | Span | Span + field |
|---|---|---|---|---|
tracing-perfetto-file (SpanTracks) |
74 | 101 | 239 | 252 |
Connected tracing-tracy |
77 | 115 | 310 | 400 |
Official tracing-perfetto-sdk |
574 | 895 | 769 | 866 |
tracing-chrome (threaded) |
353 | 1,549 | 1,123 | 1,838 |
Modal SdkLayer |
642 | 738 | 1,588 | 1,597 |
tracing-perfetto |
941 | 1,278 | 1,921 | 2,036 |
Modal NativeLayer (async) |
1,133 | 1,408 | 2,787 | 2,970 |
See the detailed performance breakdown for more information.
- Official
tracing-perfetto-sdk- Pros: integrates with the native Perfetto SDK's in-process and system backends.
- Cons: requires the native SDK and its session machinery and does not encode the
tracingspan tree as parented tracks.
- Modal
tracing-perfetto-sdk-layer- Pros: offers both a Rust protobuf layer and a C++ SDK-backed layer, with richer trace configuration.
- Cons: its process, thread, and Tokio-task tracks do not represent
tracingspan parentage, and both measured paths have higher producer cost.
tracing-perfetto- Pros: writes Perfetto protobuf directly without requiring the native Perfetto SDK.
- Cons: buffers spans as events on thread tracks, does not preserve parentage or cross-thread span lifetimes in the same way, and allocates substantially more on measured workloads.
tracing-chrome- Pros: writes portable Chrome trace JSON and supports both threaded and async span views.
- Cons: those modes infer nesting or correlate root spans rather than preserving the complete immediate-parent hierarchy.
tracing-tracy- Pros: provides a fast, interactive live-profiler connection.
- Cons: requires an active capture process and uses a thread-local zone stack rather than
explicit
tracingparentage. Its field, category, and artifact semantics also differ.
tracing-opentelemetry- Pros: preserves distributed trace context and exports through a widely supported telemetry ecosystem.
- Cons: exports through a configurable SDK instead of writing a local timeline file. Its performance depends on the processor and exporter, so it is not included in the table.
Capture. Each tracing callback writes Perfetto protobuf fields directly into a reusable
thread-local buffer. Every thread owns a packet sequence with its own interning tables and
incremental timestamp state.
Span tracking. Active span state is shared separately from packet encoding, so spans may be entered, updated, or closed on another thread without losing their lifetime or parent relationships.
Output. Completed batches pass through bounded per-thread queues. They take the output
lock only while being drained, either when a queue fills, a thread exits, or
FlushGuard::flush is called.
For coding: AI was used for mechanical coding implementation but the design, architecture and overall shape of the project came from me. I understand all of the code and take full responsibility for it.
For other tasks: AI was used for brainstorming and running benchmarks. This README was hand written with AI filling in links and performance numbers (double checked by me).
Copyright 2026 Lalit Maganti.
Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
