2025-12-11 06:40:15 +01:00
2025-12-11 06:40:15 +01:00
2025-12-11 00:12:16 +01:00
2025-12-11 06:40:15 +01:00
2025-12-10 23:50:28 +01:00
2025-12-10 23:50:28 +01:00
2025-12-11 00:12:16 +01:00
2025-12-11 00:12:16 +01:00
2025-12-10 23:50:28 +01:00
2025-12-10 23:50:28 +01:00

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 ~400 LOC with minimal dependencies.

Features

  • Icicle graph showing call stack hierarchy (top half)
  • Thread timeline showing per-thread activity over time (bottom half)
  • Monokai color palette for easy visual distinction
  • Pause mechanism to freeze your application for inspection
  • Ringbuffer storage (~16MB, 1M events) for recent history
  • Lock-free event recording via MPSC channels

Dependencies

Only 3 dependencies (~15 total including transitive):

  • minifb - Window and framebuffer
  • crossbeam-channel - Lock-free MPSC
  • once_cell - Lazy statics

Usage

Add to your Cargo.toml:

[dependencies]
teleprof = { path = "../teleprof" }  # or from crates.io when published

In your code:

fn main() {
    // Start the profiler window (separate thread)
    #[cfg(debug_assertions)]
    teleprof::start();

    // Your application code
    game_loop();
}

fn game_loop() {
    loop {
        // Profile a scope
        teleprof::span!("game_loop");
        
        update();
        render();
        
        // Check if paused (optional)
        if teleprof::PAUSE.try_lock().is_err() {
            // Wait until unpaused
            while teleprof::PAUSE.try_lock().is_err() {
                std::thread::sleep(std::time::Duration::from_millis(100));
            }
        }
    }
}

fn update() {
    teleprof::span!("update");
    // Your update code
}

fn render() {
    teleprof::span!("render");
    // Your render code
}

For closures:

let work = || {
    teleprof::span!("my_closure");
    // work...
};

Controls

  • Space: Toggle pause (acquires PAUSE lock to freeze your app)
  • 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. Renders icicle graph (call hierarchy) and timeline (per-thread activity)

Design Goals

  • Minimal overhead: Lock-free event recording
  • 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

Example Output

┌─────────────────────────────────────┐
│  Icicle Graph (Call Stack)         │
│  ┌──────────────────────────┐       │
│  │       frame_work         │       │
│  ├──────────┬───────────────┤       │
│  │ physics  │    render     │       │
│  ├────┬─────┤               │       │
│  │ w0 │ w1  │               │       │
│  └────┴─────┴───────────────┘       │
├─────────────────────────────────────┤
│  Thread Timeline                    │
│  Main:   ████████████████████       │
│  Work 0: ░░██████░░░░░░░░░░░        │
│  Work 1: ░░░░░░██████░░░░░░░        │
└─────────────────────────────────────┘

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

MIT / Apache-2.0 (choose whichever you prefer)

Description
No description provided
Readme 348 KiB
Languages
Rust 98.9%
Nix 1.1%