|
| 1 | +use crate::DiagnosticId; |
| 2 | +use bevy_app::{App, Plugin}; |
| 3 | + |
| 4 | +/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %) |
| 5 | +/// |
| 6 | +/// Supported targets: |
| 7 | +/// * linux, |
| 8 | +/// * windows, |
| 9 | +/// * android, |
| 10 | +/// * macos |
| 11 | +/// |
| 12 | +/// NOT supported when using the `bevy/dynamic` feature even when using previously mentioned targets |
| 13 | +#[derive(Default)] |
| 14 | +pub struct SystemInformationDiagnosticsPlugin; |
| 15 | +impl Plugin for SystemInformationDiagnosticsPlugin { |
| 16 | + fn build(&self, app: &mut App) { |
| 17 | + app.add_startup_system(internal::setup_system) |
| 18 | + .add_system(internal::diagnostic_system); |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +impl SystemInformationDiagnosticsPlugin { |
| 23 | + pub const CPU_USAGE: DiagnosticId = |
| 24 | + DiagnosticId::from_u128(78494871623549551581510633532637320956); |
| 25 | + pub const MEM_USAGE: DiagnosticId = |
| 26 | + DiagnosticId::from_u128(42846254859293759601295317811892519825); |
| 27 | +} |
| 28 | + |
| 29 | +// NOTE: sysinfo fails to compile when using bevy dynamic or on iOS and does nothing on wasm |
| 30 | +#[cfg(all( |
| 31 | + any( |
| 32 | + target_os = "linux", |
| 33 | + target_os = "windows", |
| 34 | + target_os = "android", |
| 35 | + target_os = "macos" |
| 36 | + ), |
| 37 | + not(feature = "bevy_dynamic_plugin") |
| 38 | +))] |
| 39 | +pub mod internal { |
| 40 | + use bevy_ecs::{prelude::ResMut, system::Local}; |
| 41 | + use bevy_log::info; |
| 42 | + use sysinfo::{CpuExt, System, SystemExt}; |
| 43 | + |
| 44 | + use crate::{Diagnostic, Diagnostics}; |
| 45 | + |
| 46 | + const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0; |
| 47 | + |
| 48 | + pub(crate) fn setup_system(mut diagnostics: ResMut<Diagnostics>) { |
| 49 | + diagnostics.add( |
| 50 | + Diagnostic::new( |
| 51 | + super::SystemInformationDiagnosticsPlugin::CPU_USAGE, |
| 52 | + "cpu_usage", |
| 53 | + 20, |
| 54 | + ) |
| 55 | + .with_suffix("%"), |
| 56 | + ); |
| 57 | + diagnostics.add( |
| 58 | + Diagnostic::new( |
| 59 | + super::SystemInformationDiagnosticsPlugin::MEM_USAGE, |
| 60 | + "mem_usage", |
| 61 | + 20, |
| 62 | + ) |
| 63 | + .with_suffix("%"), |
| 64 | + ); |
| 65 | + } |
| 66 | + |
| 67 | + pub(crate) fn diagnostic_system( |
| 68 | + mut diagnostics: ResMut<Diagnostics>, |
| 69 | + mut sysinfo: Local<Option<System>>, |
| 70 | + ) { |
| 71 | + if sysinfo.is_none() { |
| 72 | + *sysinfo = Some(System::new_all()); |
| 73 | + } |
| 74 | + let Some(sys) = sysinfo.as_mut() else { |
| 75 | + return; |
| 76 | + }; |
| 77 | + |
| 78 | + sys.refresh_cpu(); |
| 79 | + sys.refresh_memory(); |
| 80 | + let current_cpu_usage = { |
| 81 | + let mut usage = 0.0; |
| 82 | + let cpus = sys.cpus(); |
| 83 | + for cpu in cpus { |
| 84 | + usage += cpu.cpu_usage(); // NOTE: this returns a value from 0.0 to 100.0 |
| 85 | + } |
| 86 | + // average |
| 87 | + usage / cpus.len() as f32 |
| 88 | + }; |
| 89 | + // `memory()` fns return a value in bytes |
| 90 | + let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB; |
| 91 | + let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB; |
| 92 | + let current_used_mem = used_mem / total_mem * 100.0; |
| 93 | + |
| 94 | + diagnostics.add_measurement(super::SystemInformationDiagnosticsPlugin::CPU_USAGE, || { |
| 95 | + current_cpu_usage as f64 |
| 96 | + }); |
| 97 | + diagnostics.add_measurement(super::SystemInformationDiagnosticsPlugin::MEM_USAGE, || { |
| 98 | + current_used_mem |
| 99 | + }); |
| 100 | + } |
| 101 | + |
| 102 | + #[derive(Debug)] |
| 103 | + // This is required because the Debug trait doesn't detect it's used when it's only used in a print :( |
| 104 | + #[allow(dead_code)] |
| 105 | + struct SystemInfo { |
| 106 | + os: String, |
| 107 | + kernel: String, |
| 108 | + cpu: String, |
| 109 | + core_count: String, |
| 110 | + memory: String, |
| 111 | + } |
| 112 | + |
| 113 | + pub(crate) fn log_system_info() { |
| 114 | + let mut sys = sysinfo::System::new(); |
| 115 | + sys.refresh_cpu(); |
| 116 | + sys.refresh_memory(); |
| 117 | + |
| 118 | + let info = SystemInfo { |
| 119 | + os: sys |
| 120 | + .long_os_version() |
| 121 | + .unwrap_or_else(|| String::from("not available")), |
| 122 | + kernel: sys |
| 123 | + .kernel_version() |
| 124 | + .unwrap_or_else(|| String::from("not available")), |
| 125 | + cpu: sys.global_cpu_info().brand().trim().to_string(), |
| 126 | + core_count: sys |
| 127 | + .physical_core_count() |
| 128 | + .map(|x| x.to_string()) |
| 129 | + .unwrap_or_else(|| String::from("not available")), |
| 130 | + // Convert from Bytes to GibiBytes since it's probably what people expect most of the time |
| 131 | + memory: format!("{:.1} GiB", sys.total_memory() as f64 * BYTES_TO_GIB), |
| 132 | + }; |
| 133 | + |
| 134 | + info!("{:?}", info); |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +#[cfg(not(all( |
| 139 | + any( |
| 140 | + target_os = "linux", |
| 141 | + target_os = "windows", |
| 142 | + target_os = "android", |
| 143 | + target_os = "macos" |
| 144 | + ), |
| 145 | + not(feature = "bevy_dynamic_plugin") |
| 146 | +)))] |
| 147 | +pub mod internal { |
| 148 | + pub(crate) fn setup_system() { |
| 149 | + bevy_log::warn!("This platform and/or configuration is not supported!"); |
| 150 | + } |
| 151 | + |
| 152 | + pub(crate) fn diagnostic_system() { |
| 153 | + // no-op |
| 154 | + } |
| 155 | + |
| 156 | + pub(crate) fn log_system_info() { |
| 157 | + // no-op |
| 158 | + } |
| 159 | +} |
0 commit comments