log_diagnostics/
log_diagnostics.rs

1//! Shows different built-in plugins that logs diagnostics, like frames per second (FPS), to the console.
2
3use bevy::{
4    color::palettes,
5    diagnostic::{
6        DiagnosticPath, EntityCountDiagnosticsPlugin, FrameTimeDiagnosticsPlugin,
7        LogDiagnosticsPlugin, LogDiagnosticsState, SystemInformationDiagnosticsPlugin,
8    },
9    prelude::*,
10};
11
12const FRAME_TIME_DIAGNOSTICS: [DiagnosticPath; 3] = [
13    FrameTimeDiagnosticsPlugin::FPS,
14    FrameTimeDiagnosticsPlugin::FRAME_COUNT,
15    FrameTimeDiagnosticsPlugin::FRAME_TIME,
16];
17const ENTITY_COUNT_DIAGNOSTICS: [DiagnosticPath; 1] = [EntityCountDiagnosticsPlugin::ENTITY_COUNT];
18const SYSTEM_INFO_DIAGNOSTICS: [DiagnosticPath; 4] = [
19    SystemInformationDiagnosticsPlugin::PROCESS_CPU_USAGE,
20    SystemInformationDiagnosticsPlugin::PROCESS_MEM_USAGE,
21    SystemInformationDiagnosticsPlugin::SYSTEM_CPU_USAGE,
22    SystemInformationDiagnosticsPlugin::SYSTEM_MEM_USAGE,
23];
24
25fn main() {
26    App::new()
27        .add_plugins((
28            // The diagnostics plugins need to be added after DefaultPlugins as they use e.g. the time plugin for timestamps.
29            DefaultPlugins,
30            // Adds a system that prints diagnostics to the console.
31            // The other diagnostics plugins can still be used without this if you want to use them in an ingame overlay for example.
32            LogDiagnosticsPlugin::default(),
33            // Adds frame time, FPS and frame count diagnostics.
34            FrameTimeDiagnosticsPlugin::default(),
35            // Adds an entity count diagnostic.
36            EntityCountDiagnosticsPlugin::default(),
37            // Adds cpu and memory usage diagnostics for systems and the entire game process.
38            SystemInformationDiagnosticsPlugin,
39            // Forwards various diagnostics from the render app to the main app.
40            // These are pretty verbose but can be useful to pinpoint performance issues.
41            bevy::render::diagnostic::RenderDiagnosticsPlugin,
42        ))
43        // No rendering diagnostics are emitted unless something is drawn to the screen,
44        // so we spawn a small scene.
45        .add_systems(Startup, setup)
46        .add_systems(Update, filters_inputs)
47        .add_systems(
48            Update,
49            update_commands.run_if(
50                resource_exists_and_changed::<LogDiagnosticsStatus>
51                    .or(resource_exists_and_changed::<LogDiagnosticsFilters>),
52            ),
53        )
54        .run();
55}
56
57/// set up a simple 3D scene
58fn setup(
59    mut commands: Commands,
60    mut meshes: ResMut<Assets<Mesh>>,
61    mut materials: ResMut<Assets<StandardMaterial>>,
62) {
63    // circular base
64    commands.spawn((
65        Mesh3d(meshes.add(Circle::new(4.0))),
66        MeshMaterial3d(materials.add(Color::WHITE)),
67        Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
68    ));
69    // cube
70    commands.spawn((
71        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
72        MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
73        Transform::from_xyz(0.0, 0.5, 0.0),
74    ));
75    // light
76    commands.spawn((
77        PointLight {
78            shadows_enabled: true,
79            ..default()
80        },
81        Transform::from_xyz(4.0, 8.0, 4.0),
82    ));
83    // camera
84    commands.spawn((
85        Camera3d::default(),
86        Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
87    ));
88
89    commands.init_resource::<LogDiagnosticsFilters>();
90    commands.init_resource::<LogDiagnosticsStatus>();
91
92    commands.spawn((
93        LogDiagnosticsCommands,
94        Node {
95            top: px(5),
96            left: px(5),
97            flex_direction: FlexDirection::Column,
98            ..default()
99        },
100    ));
101}
102
103fn filters_inputs(
104    keys: Res<ButtonInput<KeyCode>>,
105    mut status: ResMut<LogDiagnosticsStatus>,
106    mut filters: ResMut<LogDiagnosticsFilters>,
107    mut log_state: ResMut<LogDiagnosticsState>,
108) {
109    if keys.just_pressed(KeyCode::KeyQ) {
110        *status = match *status {
111            LogDiagnosticsStatus::Enabled => {
112                log_state.disable_filtering();
113                LogDiagnosticsStatus::Disabled
114            }
115            LogDiagnosticsStatus::Disabled => {
116                log_state.enable_filtering();
117                if filters.frame_time {
118                    enable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
119                }
120                if filters.entity_count {
121                    enable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
122                }
123                if filters.system_info {
124                    enable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
125                }
126                LogDiagnosticsStatus::Enabled
127            }
128        };
129    }
130
131    let enabled = *status == LogDiagnosticsStatus::Enabled;
132    if keys.just_pressed(KeyCode::Digit1) {
133        filters.frame_time = !filters.frame_time;
134        if enabled {
135            if filters.frame_time {
136                enable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
137            } else {
138                disable_filters(&mut log_state, FRAME_TIME_DIAGNOSTICS);
139            }
140        }
141    }
142    if keys.just_pressed(KeyCode::Digit2) {
143        filters.entity_count = !filters.entity_count;
144        if enabled {
145            if filters.entity_count {
146                enable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
147            } else {
148                disable_filters(&mut log_state, ENTITY_COUNT_DIAGNOSTICS);
149            }
150        }
151    }
152    if keys.just_pressed(KeyCode::Digit3) {
153        filters.system_info = !filters.system_info;
154        if enabled {
155            if filters.system_info {
156                enable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
157            } else {
158                disable_filters(&mut log_state, SYSTEM_INFO_DIAGNOSTICS);
159            }
160        }
161    }
162}
163
164fn enable_filters(
165    log_state: &mut LogDiagnosticsState,
166    diagnostics: impl IntoIterator<Item = DiagnosticPath>,
167) {
168    log_state.extend_filter(diagnostics);
169}
170
171fn disable_filters(
172    log_state: &mut LogDiagnosticsState,
173    diagnostics: impl IntoIterator<Item = DiagnosticPath>,
174) {
175    for diagnostic in diagnostics {
176        log_state.remove_filter(&diagnostic);
177    }
178}
179
180fn update_commands(
181    mut commands: Commands,
182    log_commands: Single<Entity, With<LogDiagnosticsCommands>>,
183    status: Res<LogDiagnosticsStatus>,
184    filters: Res<LogDiagnosticsFilters>,
185) {
186    let enabled = *status == LogDiagnosticsStatus::Enabled;
187    let alpha = if enabled { 1. } else { 0.25 };
188    let enabled_color = |enabled| {
189        if enabled {
190            Color::from(palettes::tailwind::GREEN_400)
191        } else {
192            Color::from(palettes::tailwind::RED_400)
193        }
194    };
195    commands
196        .entity(*log_commands)
197        .despawn_related::<Children>()
198        .insert(children![
199            (
200                Node {
201                    flex_direction: FlexDirection::Row,
202                    column_gap: px(5),
203                    ..default()
204                },
205                children![
206                    Text::new("[Q] Toggle filtering:"),
207                    (
208                        Text::new(format!("{:?}", *status)),
209                        TextColor(enabled_color(enabled))
210                    )
211                ]
212            ),
213            (
214                Node {
215                    flex_direction: FlexDirection::Row,
216                    column_gap: px(5),
217                    ..default()
218                },
219                children![
220                    (
221                        Text::new("[1] Frame times:"),
222                        TextColor(Color::WHITE.with_alpha(alpha))
223                    ),
224                    (
225                        Text::new(format!("{:?}", filters.frame_time)),
226                        TextColor(enabled_color(filters.frame_time).with_alpha(alpha))
227                    )
228                ]
229            ),
230            (
231                Node {
232                    flex_direction: FlexDirection::Row,
233                    column_gap: px(5),
234                    ..default()
235                },
236                children![
237                    (
238                        Text::new("[2] Entity count:"),
239                        TextColor(Color::WHITE.with_alpha(alpha))
240                    ),
241                    (
242                        Text::new(format!("{:?}", filters.entity_count)),
243                        TextColor(enabled_color(filters.entity_count).with_alpha(alpha))
244                    )
245                ]
246            ),
247            (
248                Node {
249                    flex_direction: FlexDirection::Row,
250                    column_gap: px(5),
251                    ..default()
252                },
253                children![
254                    (
255                        Text::new("[3] System info:"),
256                        TextColor(Color::WHITE.with_alpha(alpha))
257                    ),
258                    (
259                        Text::new(format!("{:?}", filters.system_info)),
260                        TextColor(enabled_color(filters.system_info).with_alpha(alpha))
261                    )
262                ]
263            ),
264            (
265                Node {
266                    flex_direction: FlexDirection::Row,
267                    column_gap: px(5),
268                    ..default()
269                },
270                children![
271                    (
272                        Text::new("[4] Render diagnostics:"),
273                        TextColor(Color::WHITE.with_alpha(alpha))
274                    ),
275                    (
276                        Text::new("Private"),
277                        TextColor(enabled_color(false).with_alpha(alpha))
278                    )
279                ]
280            ),
281        ]);
282}
283
284#[derive(Debug, Default, PartialEq, Eq, Resource)]
285enum LogDiagnosticsStatus {
286    /// No filtering, showing all logs
287    #[default]
288    Disabled,
289    /// Filtering enabled, showing only subset of logs
290    Enabled,
291}
292
293#[derive(Default, Resource)]
294struct LogDiagnosticsFilters {
295    frame_time: bool,
296    entity_count: bool,
297    system_info: bool,
298    #[expect(
299        dead_code,
300        reason = "Currently the diagnostic paths referent to RenderDiagnosticPlugin are private"
301    )]
302    render_diagnostics: bool,
303}
304
305#[derive(Component)]
306/// Marks the UI node that has instructions on how to change the filtering
307struct LogDiagnosticsCommands;