| Crates.io | bevy_perf_hud |
| lib.rs | bevy_perf_hud |
| version | 0.1.3 |
| created_at | 2025-09-24 12:55:06.640037+00 |
| updated_at | 2025-09-25 12:38:28.032869+00 |
| description | Configurable performance HUD overlay plugin for Bevy apps |
| homepage | https://crates.io/crates/bevy_perf_hud |
| repository | https://github.com/ZoOLForge/bevy_perf_hud |
| max_upload_size | |
| id | 1853070 |
| size | 268,430 |


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.
PerfMetricProvider trait for custom metrics that appear alongside built-ins.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 | Description | Default |
|---|---|---|
default |
Enables all standard functionality | ✓ |
bevy_ui, bevy_diagnostic, and bevy_render featuressysinfo_plugin feature for CPU/memory monitoringTip: If you use
DefaultPlugins, the required features are already enabled. Withoutsysinfo_plugin, system/process CPU & memory providers will be silently skipped.
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();
}
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();
}
Performance bars can use different scaling modes to adapt their range dynamically:
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
// ...
}
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)
// ...
}
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:
| 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). |
Implement the PerfMetricProvider trait and register it with the PerfHudAppExt helper:
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();
}
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;
}
}
"game/players" or "net/latency_ms"sample() implementations fast - they're called every frameNone when data isn't available rather than placeholder values_ms, _mb, _percent)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
The performance HUD is designed to have minimal impact on your application:
Optimization Tips:
history_samples in graph settings for lower memory usageupdate_interval for custom metrics that are expensive to sampleHUD not appearing:
bevy_ui feature is enabled in your Bevy dependencyDefaultPlugins or have added the required UI plugins manuallyMissing system metrics (CPU/Memory):
sysinfo_plugin feature to your Bevy dependencyPoor performance with many entities:
entity_count metric can impact performance with 100k+ entitiesCustom metrics not updating:
PerfMetricProvider::sample() method returns Some(value)add_perf_metric_provider()If the HUD itself is causing performance issues:
// Temporarily disable to isolate performance problems
.insert_resource(PerfHudSettings {
enabled: false,
..default ()
})
When reporting issues, please include:
| bevy | bevy_perf_hud |
|---|---|
| 0.16 | 0.1.3 |
Dual-licensed under either the MIT License or Apache License 2.0.
bevy_diagnostic and SystemInformationDiagnosticsPlugin for the metrics that power the HUD.Looking for the Chinese documentation? See README_CN.md.