bevy_perf_hud

Crates.iobevy_perf_hud
lib.rsbevy_perf_hud
version0.1.3
created_at2025-09-24 12:55:06.640037+00
updated_at2025-09-25 12:38:28.032869+00
descriptionConfigurable performance HUD overlay plugin for Bevy apps
homepagehttps://crates.io/crates/bevy_perf_hud
repositoryhttps://github.com/ZoOLForge/bevy_perf_hud
max_upload_size
id1853070
size268,430
ZoOL (foxzool)

documentation

https://docs.rs/bevy_perf_hud

README

bevy_perf_hud

CI Crates.io Downloads Documentation MIT/Apache 2.0 Discord

Sep-24-2025 18-37-55

Bar Scaling Modes Demo

A configurable performance heads-up display (HUD) plugin for Bevy applications. Visualize frame pacing, entity counts, and resource usage in real time, with extensibility for your own metrics.

Table of Contents

Features

  • Flexible HUD layout with multi-curve graphs and resource bars.
  • Built-in providers for FPS, frame time, entity count, and system/process CPU & memory usage.
  • Fine-grained control over smoothing, quantization, autoscaling, and appearance.
  • Extensible PerfMetricProvider trait for custom metrics that appear alongside built-ins.

Installation

Minimum Supported Rust Version (MSRV): 1.76.0

Add the crate to your Cargo.toml:

[dependencies]
bevy = { version = "0.16", default-features = false, features = [
    "bevy_winit",
    "bevy_ui",
    "bevy_render",
    "bevy_diagnostic",
    "sysinfo_plugin",
] }
bevy_perf_hud = "0.1"

Feature Flags

Feature Description Default
default Enables all standard functionality

Requirements

  • Bevy Features: The HUD requires bevy_ui, bevy_diagnostic, and bevy_render features
  • System Metrics: Add sysinfo_plugin feature for CPU/memory monitoring
  • Platform Support: Windows, macOS, Linux (system metrics may have limited functionality on some platforms)

Tip: If you use DefaultPlugins, the required features are already enabled. Without sysinfo_plugin, system/process CPU & memory providers will be silently skipped.

Quick Start

use bevy::prelude::*;
use bevy_perf_hud::BevyPerfHudPlugin;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(BevyPerfHudPlugin)
        .run();
}

By default the HUD appears near the top-right corner. To reposition or customize the layout, insert a PerfHudSettings resource before adding the plugin:

use bevy::prelude::*;
use bevy_perf_hud::{BevyPerfHudPlugin, PerfHudSettings};

fn main() {
    App::new()
        .insert_resource(PerfHudSettings {
            origin: Vec2::new(32.0, 32.0),
            ..default()
        })
        .add_plugins(DefaultPlugins)
        .add_plugins(BevyPerfHudPlugin)
        .run();
}

Advanced Configuration

PerfHudSettings exposes additional knobs for tailoring the HUD:

  • graph: adjust canvas size, curve smoothing, quantization, and decide which metrics appear in the time-series chart.
  • bars: control whether resource bars render, set per-metric min/max bounds, and decide when to show numeric values.
  • enabled / origin: toggle the HUD globally and anchor it anywhere on screen.

Example: expand the graph, smooth the FPS curve, and shrink the system CPU bar range.

use bevy::prelude::*;
use bevy_perf_hud::{BevyPerfHudPlugin, PerfHudSettings};

fn main() {
    App::new()
        .insert_resource({
            let mut settings = PerfHudSettings::default();
            settings.origin = Vec2::new(24.0, 24.0);
            settings.graph.size = Vec2::new(360.0, 120.0);
            if let Some(fps_curve) = settings
                .graph
                .curves
                .iter_mut()
                .find(|curve| curve.metric.id == "fps")
            {
                fps_curve.smoothing = Some(0.15);
            }
            // Modify the entity count bar to use auto-scaling
            if let Some(entity_bar) = settings
                .bars
                .bars
                .iter_mut()
                .find(|bar| bar.metric.id == "entity_count")
            {
                entity_bar.scale_mode = bevy_perf_hud::BarScaleMode::Auto {
                    smoothing: 0.8,     // Smooth range transitions
                    min_span: 100.0,    // Minimum range of 100 entities
                    margin_frac: 0.2,   // 20% margin for growth headroom
                };
                entity_bar.show_value = Some(true);
            }
            settings
        })
        .add_plugins(DefaultPlugins)
        .add_plugins(BevyPerfHudPlugin)
        .run();
}

Bar Scaling Modes

Performance bars can use different scaling modes to adapt their range dynamically:

Fixed Mode (Default)

Uses static min_value and max_value - traditional behavior with predictable, stable ranges.

use bevy_perf_hud::{BarConfig, BarScaleMode, MetricDefinition};

BarConfig {
    metric: MetricDefinition { /* ... */ },
    min_value: 0.0,
    max_value: 100.0,
    scale_mode: BarScaleMode::Fixed, // Uses min_value/max_value directly
    // ...
}

Auto Mode

Automatically adjusts range based on historical data with smooth transitions:

BarConfig {
    metric: MetricDefinition { /* ... */ },
    min_value: 0.0,   // Fallback if no data
    max_value: 100.0, // Fallback if no data
    scale_mode: BarScaleMode::Auto {
        smoothing: 0.8,     // Range change smoothing (0.0=instant, 1.0=never)
        min_span: 10.0,     // Minimum range span to prevent division by zero
        margin_frac: 0.15,  // Margin fraction above/below data (0.0-0.5)
    },
    min_limit: Some(0.0),    // Hard minimum bound (optional)
    max_limit: Some(500.0),  // Hard maximum bound (optional)
    // ...
}

Percentile Mode

Uses percentiles of recent samples - ideal for spiky data like latency:

BarConfig {
    metric: MetricDefinition { /* ... */ },
    min_value: 0.0,   // Fallback if insufficient samples
    max_value: 100.0, // Fallback if insufficient samples
    scale_mode: BarScaleMode::Percentile {
        lower: 5.0,        // P5 percentile for minimum (ignores bottom 5%)
        upper: 95.0,       // P95 percentile for maximum (ignores top 5%)
        sample_count: 60,  // Number of recent samples to analyze
    },
    min_limit: Some(0.0),    // Hard bounds prevent extreme outliers
    max_limit: Some(1000.0),
    // ...
}

Use Cases:

  • Fixed: CPU/memory percentages, FPS with known limits
  • Auto: Variable metrics like entity counts, memory usage in MB
  • Percentile: Network latency, frame spikes, any metric with outliers

Built-in Metrics

Metric ID Description
fps Frames per second (floored to an integer).
frame_time_ms Smoothed frame time in milliseconds.
entity_count Active entity count in the World.
system/cpu_usage Overall system CPU usage percentage.
system/mem_usage Overall system memory usage percentage.
process/cpu_usage CPU usage of the running process.
process/mem_usage Memory footprint of the running process (MiB).

Custom Metrics

Implement the PerfMetricProvider trait and register it with the PerfHudAppExt helper:

Basic Example

use bevy::prelude::*;
use bevy_perf_hud::{PerfHudAppExt, PerfMetricProvider, MetricSampleContext};

#[derive(Default)]
struct NetworkLagProvider(f32);

impl PerfMetricProvider for NetworkLagProvider {
    fn metric_id(&self) -> &str { "net/lag_ms" }

    fn sample(&mut self, _ctx: MetricSampleContext) -> Option<f32> {
        // Simulate network latency measurement
        self.0 = (self.0 + 1.0) % 120.0;
        Some(self.0)
    }
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(bevy_perf_hud::BevyPerfHudPlugin)
        .add_perf_metric_provider(NetworkLagProvider::default())
        .run();
}

Advanced Example

Here's a more realistic example that tracks multiple game metrics:

use bevy::prelude::*;
use bevy_perf_hud::{PerfHudAppExt, PerfMetricProvider, MetricSampleContext, PerfHudSettings};
use std::collections::VecDeque;

// Track active player connections
#[derive(Resource, Default)]
struct GameStats {
    active_players: u32,
    packets_per_second: VecDeque<u32>,
    last_update: f64,
}

#[derive(Default)]
struct PlayerCountProvider;

impl PerfMetricProvider for PlayerCountProvider {
    fn metric_id(&self) -> &str { "game/players" }

    fn sample(&mut self, ctx: MetricSampleContext) -> Option<f32> {
        ctx.world.get_resource::<GameStats>()
            .map(|stats| stats.active_players as f32)
    }
}

#[derive(Default)]
struct NetworkThroughputProvider;

impl PerfMetricProvider for NetworkThroughputProvider {
    fn metric_id(&self) -> &str { "net/packets_sec" }

    fn sample(&mut self, ctx: MetricSampleContext) -> Option<f32> {
        ctx.world.get_resource::<GameStats>()
            .and_then(|stats| stats.packets_per_second.back().copied())
            .map(|pps| pps as f32)
    }
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(GameStats::default())
        .insert_resource({
            let mut settings = PerfHudSettings::default();
            // Add our custom metrics to the HUD display
            settings.graph.curves.push(bevy_perf_hud::GraphCurveSettings {
                metric: bevy_perf_hud::MetricSettings {
                    id: "game/players".to_string(),
                    ..default()
                },
                color: Color::srgb(0.2, 0.8, 0.2),
                ..default()
            });
            settings.bars.bars.push(bevy_perf_hud::BarSettings {
                metric: bevy_perf_hud::MetricSettings {
                    id: "net/packets_sec".to_string(),
                    ..default()
                },
                max_value: 1000.0,
                ..default()
            });
            settings
        })
        .add_plugins(bevy_perf_hud::BevyPerfHudPlugin)
        .add_perf_metric_provider(PlayerCountProvider)
        .add_perf_metric_provider(NetworkThroughputProvider)
        .add_systems(Update, update_game_stats)
        .run();
}

// System to update our custom metrics data
fn update_game_stats(
    mut stats: ResMut<GameStats>,
    time: Res<Time>,
    // Your actual game systems would provide real data
) {
    let now = time.elapsed_secs_f64();
    if now - stats.last_update > 1.0 {
        // Simulate some realistic game data
        stats.active_players = (20.0 + 10.0 * (now * 0.1).sin()) as u32;

        let pps = (500.0 + 200.0 * (now * 0.05).cos()) as u32;
        stats.packets_per_second.push_back(pps);
        if stats.packets_per_second.len() > 60 {
            stats.packets_per_second.pop_front();
        }

        stats.last_update = now;
    }
}

Custom Metric Guidelines

  • Unique IDs: Use descriptive, hierarchical names like "game/players" or "net/latency_ms"
  • Performance: Keep sample() implementations fast - they're called every frame
  • Optional Values: Return None when data isn't available rather than placeholder values
  • Units: Include units in the metric ID for clarity (_ms, _mb, _percent)

Examples

The repository ships with several runnable examples:

  • examples/simple.rs: 3D scene with keyboard shortcuts (Space spawns cubes, F1 toggles HUD modes).
  • examples/custom_metric.rs: Demonstrates registering an additional metric provider with auto-scaling.
  • examples/bar_scaling_modes.rs: Shows all three bar scaling modes (Fixed, Auto, Percentile) in action.

Run them with:

cargo run --example simple
cargo run --example custom_metric
cargo run --example bar_scaling_modes

Performance Impact

The performance HUD is designed to have minimal impact on your application:

  • CPU Usage: ~0.1-0.5% overhead on typical applications
  • Memory Usage: ~2-4MB for storing historical data and UI components
  • Render Cost: UI rendering typically adds <0.1ms to frame time

Optimization Tips:

  • Reduce history_samples in graph settings for lower memory usage
  • Disable unused metrics by removing them from curves/bars configuration
  • Use larger update_interval for custom metrics that are expensive to sample
  • Consider disabling the HUD in release builds using feature flags

Troubleshooting

Common Issues

HUD not appearing:

  • Ensure bevy_ui feature is enabled in your Bevy dependency
  • Check that you're using DefaultPlugins or have added the required UI plugins manually
  • Verify the HUD isn't positioned outside your window bounds

Missing system metrics (CPU/Memory):

  • Add the sysinfo_plugin feature to your Bevy dependency
  • Without this feature, system/process metrics will be silently skipped

Poor performance with many entities:

  • The entity_count metric can impact performance with 100k+ entities
  • Consider removing it from the HUD configuration for very large worlds

Custom metrics not updating:

  • Ensure your PerfMetricProvider::sample() method returns Some(value)
  • Check that the provider is properly registered with add_perf_metric_provider()
  • Verify the metric ID is unique and doesn't conflict with built-in metrics

Performance Debugging

If the HUD itself is causing performance issues:

// Temporarily disable to isolate performance problems
.insert_resource(PerfHudSettings {
enabled: false,
..default ()
})

Getting Help

When reporting issues, please include:

  • Your Bevy version
  • Operating system and version
  • Minimal code example that reproduces the problem
  • Console output or error messages

Supported Versions

bevy bevy_perf_hud
0.16 0.1.3

License

Dual-licensed under either the MIT License or Apache License 2.0.

Acknowledgements

  • Bevy Engine for providing the ECS/game-engine foundation.
  • bevy_diagnostic and SystemInformationDiagnosticsPlugin for the metrics that power the HUD.

Looking for the Chinese documentation? See README_CN.md.

Commit count: 56

cargo fmt