Skip to content

Commit

Permalink
Cleanups in diagnostics (#3871)
Browse files Browse the repository at this point in the history
- changed `EntityCountDiagnosticsPlugin` to not use an exclusive system to get its entity count
- removed mention of `WgpuResourceDiagnosticsPlugin` in example `log_diagnostics` as it doesn't exist anymore
- added ability to enable, disable ~~or toggle~~ a diagnostic (fix #3767)
- made diagnostic values lazy, so they are only computed if the diagnostic is enabled
- do not log an average for diagnostics with only one value
- removed `sum` function from diagnostic as it isn't really useful
- ~~do not keep an average of the FPS diagnostic. it is already an average on the last 20 frames, so the average FPS was an average of the last 20 frames over the last 20 frames~~
- do not compute the FPS value as an average over the last 20 frames but give the actual "instant FPS"
- updated log format to use variable capture
- added some doc
- the frame counter diagnostic value can be reseted to 0
  • Loading branch information
mockersf committed Jun 20, 2022
1 parent 9095d2f commit bb1d524
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ impl<T: Asset> AssetCountDiagnosticsPlugin<T> {
}

pub fn diagnostic_system(mut diagnostics: ResMut<Diagnostics>, assets: Res<Assets<T>>) {
diagnostics.add_measurement(Self::diagnostic_id(), assets.len() as f64);
diagnostics.add_measurement(Self::diagnostic_id(), || assets.len() as f64);
}
}
56 changes: 44 additions & 12 deletions crates/bevy_diagnostic/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,30 @@ pub struct Diagnostic {
history: VecDeque<DiagnosticMeasurement>,
sum: f64,
max_history_length: usize,
pub is_enabled: bool,
}

impl Diagnostic {
/// Add a new value as a [`DiagnosticMeasurement`]. Its timestamp will be [`Instant::now`].
pub fn add_measurement(&mut self, value: f64) {
let time = Instant::now();
if self.history.len() == self.max_history_length {
if let Some(removed_diagnostic) = self.history.pop_front() {
self.sum -= removed_diagnostic.value;
if self.max_history_length > 1 {
if self.history.len() == self.max_history_length {
if let Some(removed_diagnostic) = self.history.pop_front() {
self.sum -= removed_diagnostic.value;
}
}
}

self.sum += value;
self.sum += value;
} else {
self.history.clear();
self.sum = value;
}
self.history
.push_back(DiagnosticMeasurement { time, value });
}

/// Create a new diagnostic with the given ID, name and maximum history.
pub fn new(
id: DiagnosticId,
name: impl Into<Cow<'static, str>>,
Expand All @@ -74,28 +82,30 @@ impl Diagnostic {
history: VecDeque::with_capacity(max_history_length),
max_history_length,
sum: 0.0,
is_enabled: true,
}
}

/// Add a suffix to use when logging the value, can be used to show a unit.
#[must_use]
pub fn with_suffix(mut self, suffix: impl Into<Cow<'static, str>>) -> Self {
self.suffix = suffix.into();
self
}

/// Get the latest measurement from this diagnostic.
#[inline]
pub fn measurement(&self) -> Option<&DiagnosticMeasurement> {
self.history.back()
}

/// Get the latest value from this diagnostic.
pub fn value(&self) -> Option<f64> {
self.measurement().map(|measurement| measurement.value)
}

pub fn sum(&self) -> f64 {
self.sum
}

/// Return the mean (average) of this diagnostic's values.
/// N.B. this a cheap operation as the sum is cached.
pub fn average(&self) -> Option<f64> {
if !self.history.is_empty() {
Some(self.sum / self.history.len() as f64)
Expand All @@ -104,10 +114,12 @@ impl Diagnostic {
}
}

/// Return the number of elements for this diagnostic.
pub fn history_len(&self) -> usize {
self.history.len()
}

/// Return the duration between the oldest and most recent values for this diagnostic.
pub fn duration(&self) -> Option<Duration> {
if self.history.len() < 2 {
return None;
Expand All @@ -122,6 +134,7 @@ impl Diagnostic {
None
}

/// Return the maximum number of elements for this diagnostic.
pub fn get_max_history_length(&self) -> usize {
self.max_history_length
}
Expand All @@ -133,6 +146,11 @@ impl Diagnostic {
pub fn measurements(&self) -> impl Iterator<Item = &DiagnosticMeasurement> {
self.history.iter()
}

/// Clear the history of this diagnostic.
pub fn clear_history(&mut self) {
self.history.clear();
}
}

/// A collection of [Diagnostic]s
Expand All @@ -144,6 +162,7 @@ pub struct Diagnostics {
}

impl Diagnostics {
/// Add a new [`Diagnostic`].
pub fn add(&mut self, diagnostic: Diagnostic) {
self.diagnostics.insert(diagnostic.id, diagnostic);
}
Expand All @@ -156,18 +175,31 @@ impl Diagnostics {
self.diagnostics.get_mut(&id)
}

/// Get the latest [`DiagnosticMeasurement`] from an enabled [`Diagnostic`].
pub fn get_measurement(&self, id: DiagnosticId) -> Option<&DiagnosticMeasurement> {
self.diagnostics
.get(&id)
.filter(|diagnostic| diagnostic.is_enabled)
.and_then(|diagnostic| diagnostic.measurement())
}

pub fn add_measurement(&mut self, id: DiagnosticId, value: f64) {
if let Some(diagnostic) = self.diagnostics.get_mut(&id) {
diagnostic.add_measurement(value);
/// Add a measurement to an enabled [`Diagnostic`]. The measurement is passed as a function so that
/// it will be evaluated only if the [`Diagnostic`] is enabled. This can be useful if the value is
/// costly to calculate.
pub fn add_measurement<F>(&mut self, id: DiagnosticId, value: F)
where
F: FnOnce() -> f64,
{
if let Some(diagnostic) = self
.diagnostics
.get_mut(&id)
.filter(|diagnostic| diagnostic.is_enabled)
{
diagnostic.add_measurement(value());
}
}

/// Return an iterator over all [`Diagnostic`].
pub fn iter(&self) -> impl Iterator<Item = &Diagnostic> {
self.diagnostics.values()
}
Expand Down
14 changes: 4 additions & 10 deletions crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use bevy_app::{App, Plugin};
use bevy_ecs::{
system::{IntoExclusiveSystem, ResMut},
world::World,
};
use bevy_ecs::{entity::Entities, system::ResMut};

use crate::{Diagnostic, DiagnosticId, Diagnostics};

Expand All @@ -13,7 +10,7 @@ pub struct EntityCountDiagnosticsPlugin;
impl Plugin for EntityCountDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.add_startup_system(Self::setup_system)
.add_system(Self::diagnostic_system.exclusive_system());
.add_system(Self::diagnostic_system);
}
}

Expand All @@ -25,10 +22,7 @@ impl EntityCountDiagnosticsPlugin {
diagnostics.add(Diagnostic::new(Self::ENTITY_COUNT, "entity_count", 20));
}

pub fn diagnostic_system(world: &mut World) {
let entity_count = world.entities().len();
if let Some(mut diagnostics) = world.get_resource_mut::<Diagnostics>() {
diagnostics.add_measurement(Self::ENTITY_COUNT, entity_count as f64);
}
pub fn diagnostic_system(mut diagnostics: ResMut<Diagnostics>, entities: &Entities) {
diagnostics.add_measurement(Self::ENTITY_COUNT, || entities.len() as f64);
}
}
32 changes: 13 additions & 19 deletions crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,23 @@ impl FrameTimeDiagnosticsPlugin {
time: Res<Time>,
mut state: ResMut<FrameTimeDiagnosticsState>,
) {
state.frame_count = state.frame_count.wrapping_add(1);
diagnostics.add_measurement(Self::FRAME_COUNT, state.frame_count as f64);
diagnostics.add_measurement(Self::FRAME_COUNT, || {
state.frame_count = state.frame_count.wrapping_add(1);
state.frame_count as f64
});

if time.delta_seconds_f64() == 0.0 {
return;
}

diagnostics.add_measurement(Self::FRAME_TIME, time.delta_seconds_f64());
if let Some(fps) = diagnostics
.get(Self::FRAME_TIME)
.and_then(|frame_time_diagnostic| {
frame_time_diagnostic
.average()
.and_then(|frame_time_average| {
if frame_time_average > 0.0 {
Some(1.0 / frame_time_average)
} else {
None
}
})
})
{
diagnostics.add_measurement(Self::FPS, fps);
}
diagnostics.add_measurement(Self::FRAME_TIME, || time.delta_seconds_f64());

diagnostics.add_measurement(Self::FPS, || 1.0 / time.delta_seconds_f64());
}
}

impl FrameTimeDiagnosticsState {
pub fn reset_frame_count(&mut self) {
self.frame_count = 0;
}
}
67 changes: 40 additions & 27 deletions crates/bevy_diagnostic/src/log_diagnostics_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,30 +53,29 @@ impl LogDiagnosticsPlugin {

fn log_diagnostic(diagnostic: &Diagnostic) {
if let Some(value) = diagnostic.value() {
if let Some(average) = diagnostic.average() {
info!(
target: "bevy diagnostic",
// Suffix is only used for 's' as in seconds currently,
// so we reserve one column for it; however,
// Do not reserve one column for the suffix in the average
// The ) hugging the value is more aesthetically pleasing
"{name:<name_width$}: {value:>11.6}{suffix:1} (avg {average:>.6}{suffix:})",
name = diagnostic.name,
value = value,
suffix = diagnostic.suffix,
average = average,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
);
} else {
info!(
target: "bevy diagnostic",
"{name:<name_width$}: {value:>.6}{suffix:}",
name = diagnostic.name,
value = value,
suffix = diagnostic.suffix,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
);
if diagnostic.get_max_history_length() > 1 {
if let Some(average) = diagnostic.average() {
info!(
target: "bevy diagnostic",
// Suffix is only used for 's' as in seconds currently,
// so we reserve one column for it; however,
// Do not reserve one column for the suffix in the average
// The ) hugging the value is more aesthetically pleasing
"{name:<name_width$}: {value:>11.6}{suffix:1} (avg {average:>.6}{suffix:})",
name = diagnostic.name,
suffix = diagnostic.suffix,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
);
return;
}
}
info!(
target: "bevy diagnostic",
"{name:<name_width$}: {value:>.6}{suffix:}",
name = diagnostic.name,
suffix = diagnostic.suffix,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
);
}
}

Expand All @@ -87,11 +86,18 @@ impl LogDiagnosticsPlugin {
) {
if state.timer.tick(time.delta()).finished() {
if let Some(ref filter) = state.filter {
for diagnostic in filter.iter().map(|id| diagnostics.get(*id).unwrap()) {
for diagnostic in filter.iter().flat_map(|id| {
diagnostics
.get(*id)
.filter(|diagnostic| diagnostic.is_enabled)
}) {
Self::log_diagnostic(diagnostic);
}
} else {
for diagnostic in diagnostics.iter() {
for diagnostic in diagnostics
.iter()
.filter(|diagnostic| diagnostic.is_enabled)
{
Self::log_diagnostic(diagnostic);
}
}
Expand All @@ -105,11 +111,18 @@ impl LogDiagnosticsPlugin {
) {
if state.timer.tick(time.delta()).finished() {
if let Some(ref filter) = state.filter {
for diagnostic in filter.iter().map(|id| diagnostics.get(*id).unwrap()) {
for diagnostic in filter.iter().flat_map(|id| {
diagnostics
.get(*id)
.filter(|diagnostic| diagnostic.is_enabled)
}) {
debug!("{:#?}\n", diagnostic);
}
} else {
for diagnostic in diagnostics.iter() {
for diagnostic in diagnostics
.iter()
.filter(|diagnostic| diagnostic.is_enabled)
{
debug!("{:#?}\n", diagnostic);
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/diagnostics/custom_diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ fn setup_diagnostic_system(mut diagnostics: ResMut<Diagnostics>) {

fn my_system(mut diagnostics: ResMut<Diagnostics>) {
// Add a measurement of 10.0 for our diagnostic each time this system runs.
diagnostics.add_measurement(SYSTEM_ITERATION_COUNT, 10.0);
diagnostics.add_measurement(SYSTEM_ITERATION_COUNT, || 10.0);
}

0 comments on commit bb1d524

Please sign in to comment.