114 lines
3.2 KiB
Markdown
114 lines
3.2 KiB
Markdown
# 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 framebuffer
|
|
- `crossbeam-channel` - Lock-free MPSC
|
|
- `once_cell` - Lazy statics
|
|
- `fontdue` - Font rasterization
|
|
- `procmacro2`, `syn`, `quote` - Proc macros
|
|
|
|
## Usage
|
|
|
|
### Add to your `Cargo.toml`:
|
|
```toml
|
|
[dependencies]
|
|
teleprof = { path = "../teleprof" }
|
|
```
|
|
|
|
### In your code:
|
|
```rust
|
|
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
|
|
|
|
1. `span!()` macro creates a `SpanGuard` that sends `SpanStart` on creation
|
|
2. When the guard drops, sends `SpanEnd`
|
|
3. Events are sent via lock-free MPSC channel
|
|
4. Window thread drains events into a fixed-size ringbuffer
|
|
5. Incrementally builds per-thread call trees (only processes new spans)
|
|
6. 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:
|
|
```bash
|
|
# 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
|
|
|
|
??? |