Teleprof
A lightweight, debug-only telemetry profiler for Rust applications. Shows thread activity and call stack hierarchy in real-time.
Inspired by RAD Telemetry - built in ~1200 LOC with minimal dependencies.
Features
- Unified thread tracks with expandable call stacks (click headers to toggle)
- Collapsed view shows when threads are active (easy to spot blocking)
- Expanded view shows full call stack hierarchy with flame graph visualization
- Ongoing span support for long-running functions (main loops, render threads)
- Pause mechanism to freeze your application for inspection (Space bar)
- Monokai color palette for easy visual distinction
- Incremental tree building - only processes new spans each frame
- Ringbuffer storage (~16MB, 1M events) for recent history
- Lock-free event recording via MPSC channels
Dependencies
Only 7 direct dependencies (~70 total including transitive):
minifb- Window and framebuffercrossbeam-channel- Lock-free MPSConce_cell- Lazy staticsfontdue- Font rasterizationprocmacro2,syn,quote- Proc macros
Usage
Add to your Cargo.toml:
[dependencies]
teleprof = { path = "../teleprof" }
In your code:
fn main() {
// Start the profiler window (separate thread)
#[cfg(debug_assertions)]
teleprof::start();
// Name your thread (optional, shows in UI)
#[cfg(debug_assertions)]
teleprof::set_thread_name("main");
game_loop();
}
fn game_loop() {
loop {
teleprof::span!("main_frame");
update();
render();
}
}
fn update() {
teleprof::span!("update");
// Your update code
}
fn render() {
teleprof::span!("render");
// Your render code
}
Controls
- Space: Toggle pause (freezes ongoing spans at current time)
- Left click + drag: Box select to zoom (click background not function)
- Right click + drag: Pan timeline
- Scroll: Zoom timeline horizontally
- Click track header: Expand/collapse thread's call stack
- Escape: Close profiler window
How it works
span!()macro creates aSpanGuardthat sendsSpanStarton creation- When the guard drops, sends
SpanEnd - Events are sent via lock-free MPSC channel
- Window thread drains events into a fixed-size ringbuffer
- Incrementally builds per-thread call trees (only processes new spans)
- Renders unified thread tracks with expandable call stacks
Design Goals
- Minimal overhead: Lock-free event recording, incremental tree building
- Debug-only: Compile out in release builds with
#[cfg(debug_assertions)] - Separate window: Doesn't interfere with your app's rendering
- Simple API: Just
span!("name")and you're done - Handle any thread pattern: Long-lived, short-lived, thread pools (Rayon, etc.)
Examples
Run the included examples:
# Multi-threaded physics simulation
cargo run --example demo
# Bouncing ball with color-picking thread (30 FPS)
cargo run --example bouncing_ball
The bouncing ball example demonstrates:
- Main thread running at 30 FPS with clear frame gaps
- Background thread spawned on wall collision to pick colors
- Clear visual separation between thread activities
License
???
Description
Languages
Rust
98.9%
Nix
1.1%