puffin

repository·main·Indexed 23 days ago

https://github.com/embarkstudios/puffin

A lightweight instrumentation profiler for Rust games, version 0.20.0, designed for performance analysis via flamegraphs. It provides macros like profile_function! and profile_scope! for instrumentation, a GlobalProfiler for frame management, and multiple visualization options including an egui integration (puffin_egui), a remote HTTP server (puffin_http), and a standalone CLI viewer (puffin_viewer).

Tokens
10.6K
Snippets
21
Records
77
Agent score
82%

What's inside puffin

  1. Instrument your Rust code with Puffin macros

    main

    To profile your code, use the puffin::profile_function!() macro to profile an entire function, or puffin::profile_scope!(name, args) to profile a specific block of code with a custom name and optional arguments.

    Note that these macros write to a thread-local data stream. The scopes are lightweight (approx. 50-200 ns), but you must explicitly enable the profiler using puffin::set_scopes_on(true); before any data can be captured. When the profiler is disabled, the overhead is minimal (~1 ns).

    fn my_function() {
        puffin::profile_function!();
        ...
        if ... {
            puffin::profile_scope!("load_image", image_name);
            ...
        }
    }
  2. Install and run puffin_viewer

    main

    To view profiling data published via puffin_http over TCP, install the puffin_viewer CLI tool and connect to your server using the --url flag.

    1. Install the tool using cargo: cargo install puffin_viewer --locked
    2. Run the viewer by specifying the server address: puffin_viewer --url <IP_ADDRESS>:<PORT>
    cargo install puffin_viewer --locked
    puffin_viewer --url 127.0.0.1:8585
  3. Integrate puffin profiler flamegraphs into egui

    main

    To visualize puffin profiling data directly within your egui application, use the puffin_egui::profiler_window function. This allows you to inspect flamegraphs in-game.

    First, ensure you are instrumenting your code with puffin macros like profile_function! or profile_scope!. Then, call the profiler window within your egui update loop by passing your egui::Context.

    // 1. Instrument your code
    fn my_function() {
        puffin::profile_function!();
        if ... {
            puffin::profile_scope!("load_image", image_name);
            ...
        }
    }
    
    // 2. Display the profiler in your egui UI
    puffin_egui::profiler_window(egui_ctx);
  4. Set up remote profiling with puffin_http and puffin_viewer

    main

    You can stream profile events over TCP to a separate viewer process using the puffin_http crate.

    1. In your application, initialize a puffin_http::Server with a target address.
    2. Enable scopes with puffin::set_scopes_on(true);.
    3. Periodically call puffin::GlobalProfiler::lock().new_frame(); to flush events.
    4. Run the puffin_viewer CLI tool pointing to your server address to visualize the data.
    fn main() {
        let server_addr = format!("127.0.0.1:{}", puffin_http::DEFAULT_PORT);
        let _puffin_server = puffin_http::Server::new(&server_addr).unwrap();
        eprintln!("Run this to view profiling data:  puffin_viewer {server_addr}");
        puffin::set_scopes_on(true);
    
        // ...
    
        // You also need to periodically call
        // `puffin::GlobalProfiler::lock().new_frame();`
        // to flush the profiling events.
    }
  5. Integrate Puffin with egui for in-game profiling

    main
    If you want to view flamegraphs directly within your application UI, use the puffin_egui crate. This is particularly useful for real-time debugging in games or tools built with egui. For users of eframe, there are existing implementation examples available in the egui repository.
  6. Use puffin_http to serve profiling data over HTTP

    main

    To enable remote profiling, add a puffin_http::Server to your application. This server listens for profiling events and allows the puffin_viewer application to connect and display the flamegraph.

    By default, the server uses puffin_http::DEFAULT_PORT. Ensure you call puffin::set_scopes_on(true) in your application to enable the collection of profiling scopes.

    fn main() {
        let server_addr = format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT);
        let _puffin_server = puffin_http::Server::new(&server_addr).unwrap();
        eprintln!("Serving demo profile data on {server_addr}. Run `puffin_viewer` to view it.");
        puffin::set_scopes_on(true);
    
        // …
    }
  7. How thread sorting works in the flamegraph

    main

    The flamegraph allows you to sort the list of visible threads using the Sorting struct. This is useful for finding specific threads by name or finding the most/least active threads by start time.

    Sorting Modes

    • SortBy::Time: Sorts threads by their start_time_ns.
    • SortBy::Name: Sorts threads alphabetically by name (case-insensitive).

    You can also toggle the reversed boolean to change the sort direction (e.g., from ascending to descending).

  8. How per-thread profiling works with `puffin_http`

    main

    You can achieve complete separation of profiling data (e.g., separating a main UI loop from background worker loops) by combining Server::new_custom with puffin::ThreadProfiler::initialize.

    1. Create a custom GlobalProfiler instance.
    2. Start a Server using new_custom, passing functions that interact with your custom profiler.
    3. In the target threads, call ThreadProfiler::initialize and provide a reporter function that redirects events to your custom profiler instead of the default GlobalProfiler.
    4. Use new_frame() on your custom profiler to flush data to the server.
  9. How FrameView manages memory and performance

    main

    A FrameView uses two primary strategies to balance memory usage and performance:

    1. History Limits: It maintains a fixed-size buffer of recent frames and a fixed-size buffer of slowest frames (spikes). When these limits are reached, older frames are evicted.
    2. Frame Packing: When pack_frames is enabled, FrameView calls .pack() on the previously newest frame as soon as a new frame arrives. This compresses the frame data to save RAM, but increases the CPU cost of the add_frame operation.

    Recommendation: If you are storing a large number of recent frames (max_recent is high), enable pack_frames to prevent excessive memory consumption.

  10. The Puffin profiling data format

    main

    The profiler records all events into a byte stream. Each scope is represented by a start sentinel ( followed by metadata, the child scope data, and an end sentinel ) with timing information.

    Scope Start Structure:

    • '(' (byte): Sentinel
    • scope id (u32): Unique monolithic identifier
    • time_ns (i64): Timestamp of start
    • data (str): Resource name/metadata (max 127 bytes, encoded as u8 length + UTF-8 bytes)
    • scope_size (u64): Number of bytes of child scope data

    Scope End Structure:

    • ')' (byte): Sentinel
    • time_ns (i64): Timestamp of finish

    Encoding Details:

    • Integers are encoded in little endian.
    • Strings are encoded as a single u8 length followed by UTF-8 bytes.