From ea787ca3bce0771c54406b761ba399c2519ff65e Mon Sep 17 00:00:00 2001 From: Brezak Date: Wed, 5 Jun 2024 20:13:04 +0200 Subject: [PATCH 1/4] Poll system information in separate thread --- crates/bevy_diagnostic/src/lib.rs | 4 +- .../system_information_diagnostics_plugin.rs | 96 ++++++++++++++----- 2 files changed, 74 insertions(+), 26 deletions(-) diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index bb0fb5d41d537..2c277e28e9344 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -24,7 +24,9 @@ pub use entity_count_diagnostics_plugin::EntityCountDiagnosticsPlugin; pub use frame_time_diagnostics_plugin::FrameTimeDiagnosticsPlugin; pub use log_diagnostics_plugin::LogDiagnosticsPlugin; #[cfg(feature = "sysinfo_plugin")] -pub use system_information_diagnostics_plugin::{SystemInfo, SystemInformationDiagnosticsPlugin}; +pub use system_information_diagnostics_plugin::{ + SystemInfo, SystemInformationDiagnosticsPlugin, EXPECTED_SYSTEM_INFORMATION_INTERVAL, +}; use bevy_app::prelude::*; diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index 3709a6d05fd18..94e27c936a3d4 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -1,6 +1,7 @@ use crate::DiagnosticPath; use bevy_app::prelude::*; use bevy_ecs::system::Resource; +use std::time::Duration; /// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %) /// @@ -46,6 +47,13 @@ pub struct SystemInfo { pub memory: String, } +/// The expected interval at which system information will be queried and generated. +/// +/// The system diagnostic plugin doesn't work in all situations. In those situations this value will +/// bet set to None. +pub const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = + internal::EXPECTED_SYSTEM_INFORMATION_INTERVAL; + // NOTE: sysinfo fails to compile when using bevy dynamic or on iOS and does nothing on wasm #[cfg(all( any( @@ -57,6 +65,12 @@ pub struct SystemInfo { not(feature = "dynamic_linking") ))] pub mod internal { + use std::{ + sync::mpsc::{self, Receiver, Sender}, + thread, + time::Duration, + }; + use bevy_ecs::{prelude::ResMut, system::Local}; use bevy_utils::tracing::info; use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System}; @@ -74,35 +88,63 @@ pub mod internal { .add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%")); } + pub(crate) const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = + Some(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); + + /// Continuously collects diagnostic data and sends it into `diagnostic_data_sender`. + /// + /// This function will run in a loop until the sender closes. It should be run in + /// another thread. + /// + /// The data set into the sender will be (Cpu usage %, Memory usage %) + fn diagnostic_thread(diagnostic_data_sender: Sender<(f64, f64)>) { + let mut sys = System::new_with_specifics( + RefreshKind::new() + .with_cpu(CpuRefreshKind::new().with_cpu_usage()) + .with_memory(MemoryRefreshKind::everything()), + ); + + loop { + sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()); + sys.refresh_memory(); + let current_cpu_usage = sys.global_cpu_info().cpu_usage(); + // `memory()` fns return a value in bytes + let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB; + let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB; + let current_used_mem = used_mem / total_mem * 100.0; + + if diagnostic_data_sender + .send((current_cpu_usage.into(), current_used_mem)) + .is_err() + { + break; + } + + thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); + } + } + pub(crate) fn diagnostic_system( mut diagnostics: Diagnostics, - mut sysinfo: Local>, + mut sysinfo: Local>>, ) { - if sysinfo.is_none() { - *sysinfo = Some(System::new_with_specifics( - RefreshKind::new() - .with_cpu(CpuRefreshKind::new().with_cpu_usage()) - .with_memory(MemoryRefreshKind::everything()), - )); - } - let Some(sys) = sysinfo.as_mut() else { - return; - }; - - sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()); - sys.refresh_memory(); - let current_cpu_usage = sys.global_cpu_info().cpu_usage(); - // `memory()` fns return a value in bytes - let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB; - let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB; - let current_used_mem = used_mem / total_mem * 100.0; - - diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || { - current_cpu_usage as f64 - }); - diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || { - current_used_mem + let usage_receiver = sysinfo.get_or_insert_with(|| { + let (sender, receiver) = mpsc::channel(); + + // TODO: Use a builder to give the thread a name + thread::spawn(|| diagnostic_thread(sender)); + + receiver }); + + for (current_cpu_usage, current_used_mem) in usage_receiver.try_iter() { + diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || { + current_cpu_usage + }); + diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || { + current_used_mem + }); + } } impl Default for SystemInfo { @@ -145,6 +187,8 @@ pub mod internal { not(feature = "dynamic_linking") )))] pub mod internal { + use std::time::Duration; + pub(crate) fn setup_system() { bevy_utils::tracing::warn!("This platform and/or configuration is not supported!"); } @@ -153,6 +197,8 @@ pub mod internal { // no-op } + pub(crate) const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = None; + impl Default for super::SystemInfo { fn default() -> Self { let unknown = "Unknown".to_string(); From dd3586d41dd7eee18f9b384994b1c1d5b56344d6 Mon Sep 17 00:00:00 2001 From: Brezak Date: Wed, 5 Jun 2024 20:13:04 +0200 Subject: [PATCH 2/4] Poll system information in separate tasks --- crates/bevy_diagnostic/Cargo.toml | 1 + .../system_information_diagnostics_plugin.rs | 144 +++++++++++------- 2 files changed, 86 insertions(+), 59 deletions(-) diff --git a/crates/bevy_diagnostic/Cargo.toml b/crates/bevy_diagnostic/Cargo.toml index 78f1d0bb005fb..a9678d0a23ee0 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -20,6 +20,7 @@ bevy_core = { path = "../bevy_core", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } bevy_time = { path = "../bevy_time", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } +bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } const-fnv1a-hash = "1.1.0" diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index 94e27c936a3d4..35323a670fb80 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -1,7 +1,6 @@ use crate::DiagnosticPath; use bevy_app::prelude::*; use bevy_ecs::system::Resource; -use std::time::Duration; /// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %) /// @@ -20,8 +19,7 @@ use std::time::Duration; pub struct SystemInformationDiagnosticsPlugin; impl Plugin for SystemInformationDiagnosticsPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, internal::setup_system) - .add_systems(Update, internal::diagnostic_system); + internal::setup_plugin(app); } } @@ -51,8 +49,7 @@ pub struct SystemInfo { /// /// The system diagnostic plugin doesn't work in all situations. In those situations this value will /// bet set to None. -pub const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = - internal::EXPECTED_SYSTEM_INFORMATION_INTERVAL; +pub use internal::EXPECTED_SYSTEM_INFORMATION_INTERVAL; // NOTE: sysinfo fails to compile when using bevy dynamic or on iOS and does nothing on wasm #[cfg(all( @@ -65,13 +62,15 @@ pub const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = not(feature = "dynamic_linking") ))] pub mod internal { + use bevy_ecs::{prelude::ResMut, system::Local}; use std::{ - sync::mpsc::{self, Receiver, Sender}, - thread, - time::Duration, + sync::{Arc, Mutex}, + time::{Duration, Instant}, }; - use bevy_ecs::{prelude::ResMut, system::Local}; + use bevy_app::{App, First, Startup, Update}; + use bevy_ecs::system::Resource; + use bevy_tasks::{available_parallelism, block_on, poll_once, AsyncComputeTaskPool, Task}; use bevy_utils::tracing::info; use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System}; @@ -81,70 +80,95 @@ pub mod internal { const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0; - pub(crate) fn setup_system(mut diagnostics: ResMut) { + pub(super) fn setup_plugin(app: &mut App) { + app.add_systems(Startup, setup_system) + .add_systems(First, launch_diagnostic_tasks) + .add_systems(Update, read_diagnostic_tasks) + .init_resource::(); + } + + fn setup_system(mut diagnostics: ResMut) { diagnostics .add(Diagnostic::new(SystemInformationDiagnosticsPlugin::CPU_USAGE).with_suffix("%")); diagnostics .add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%")); } - pub(crate) const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = + pub const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = Some(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); - /// Continuously collects diagnostic data and sends it into `diagnostic_data_sender`. - /// - /// This function will run in a loop until the sender closes. It should be run in - /// another thread. - /// - /// The data set into the sender will be (Cpu usage %, Memory usage %) - fn diagnostic_thread(diagnostic_data_sender: Sender<(f64, f64)>) { - let mut sys = System::new_with_specifics( - RefreshKind::new() - .with_cpu(CpuRefreshKind::new().with_cpu_usage()) - .with_memory(MemoryRefreshKind::everything()), - ); - - loop { - sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()); - sys.refresh_memory(); - let current_cpu_usage = sys.global_cpu_info().cpu_usage(); - // `memory()` fns return a value in bytes - let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB; - let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB; - let current_used_mem = used_mem / total_mem * 100.0; - - if diagnostic_data_sender - .send((current_cpu_usage.into(), current_used_mem)) - .is_err() - { - break; - } + struct SysinfoRefreshData { + current_cpu_usage: f64, + current_used_mem: f64, + } - thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); - } + #[derive(Resource, Default)] + struct SysinfoTasks { + tasks: Vec>, } - pub(crate) fn diagnostic_system( - mut diagnostics: Diagnostics, - mut sysinfo: Local>>, + fn launch_diagnostic_tasks( + mut tasks: ResMut, + // TODO: Consider a fair mutex + mut sysinfo: Local>>>, + // TODO: FromWorld for Instant? + mut last_refresh: Local>, ) { - let usage_receiver = sysinfo.get_or_insert_with(|| { - let (sender, receiver) = mpsc::channel(); + let sysinfo = sysinfo.get_or_insert_with(|| { + Arc::new(Mutex::new(System::new_with_specifics( + RefreshKind::new() + .with_cpu(CpuRefreshKind::new().with_cpu_usage()) + .with_memory(MemoryRefreshKind::everything()), + ))) + }); - // TODO: Use a builder to give the thread a name - thread::spawn(|| diagnostic_thread(sender)); + let last_refresh = last_refresh.get_or_insert_with(Instant::now); + + let thread_pool = AsyncComputeTaskPool::get(); + + // Only queue a new system refresh task when necessary + // Queueing earlier than that will not give new data + if last_refresh.elapsed() > sysinfo::MINIMUM_CPU_UPDATE_INTERVAL + // These tasks don't yield and will take up all of the task pool's + // threads if we don't limit their amount. + && tasks.tasks.len() * 2 < available_parallelism() + { + let sys = Arc::clone(sysinfo); + let task = thread_pool.spawn(async move { + let mut sys = sys.lock().unwrap(); + + sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()); + sys.refresh_memory(); + let current_cpu_usage = sys.global_cpu_info().cpu_usage().into(); + // `memory()` fns return a value in bytes + let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB; + let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB; + let current_used_mem = used_mem / total_mem * 100.0; + + SysinfoRefreshData { + current_cpu_usage, + current_used_mem, + } + }); + tasks.tasks.push(task); + *last_refresh = Instant::now(); + } + } - receiver - }); + fn read_diagnostic_tasks(mut diagnostics: Diagnostics, mut tasks: ResMut) { + tasks.tasks.retain_mut(|task| { + let Some(data) = block_on(poll_once(task)) else { + return true; + }; - for (current_cpu_usage, current_used_mem) in usage_receiver.try_iter() { diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || { - current_cpu_usage + data.current_cpu_usage }); diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || { - current_used_mem + data.current_used_mem }); - } + false + }); } impl Default for SystemInfo { @@ -189,15 +213,17 @@ pub mod internal { pub mod internal { use std::time::Duration; - pub(crate) fn setup_system() { - bevy_utils::tracing::warn!("This platform and/or configuration is not supported!"); + use bevy_app::{App, Startup}; + + pub(super) fn setup_plugin(app: &mut App) { + app.add_systems(Startup, setup_system); } - pub(crate) fn diagnostic_system() { - // no-op + fn setup_system() { + bevy_utils::tracing::warn!("This platform and/or configuration is not supported!"); } - pub(crate) const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = None; + pub const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = None; impl Default for super::SystemInfo { fn default() -> Self { From da17697fff256937d793802ae16adc041df7a8f5 Mon Sep 17 00:00:00 2001 From: Brezak Date: Mon, 10 Jun 2024 20:17:36 +0200 Subject: [PATCH 3/4] Remove the `EXPECTED_SYSTEM_INFORMATION_INTERVAL` and add documentation to replace it. --- crates/bevy_diagnostic/src/lib.rs | 4 +--- .../system_information_diagnostics_plugin.rs | 18 ++++-------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index 2c277e28e9344..bb0fb5d41d537 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -24,9 +24,7 @@ pub use entity_count_diagnostics_plugin::EntityCountDiagnosticsPlugin; pub use frame_time_diagnostics_plugin::FrameTimeDiagnosticsPlugin; pub use log_diagnostics_plugin::LogDiagnosticsPlugin; #[cfg(feature = "sysinfo_plugin")] -pub use system_information_diagnostics_plugin::{ - SystemInfo, SystemInformationDiagnosticsPlugin, EXPECTED_SYSTEM_INFORMATION_INTERVAL, -}; +pub use system_information_diagnostics_plugin::{SystemInfo, SystemInformationDiagnosticsPlugin}; use bevy_app::prelude::*; diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index 35323a670fb80..30f1cdb38663c 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -4,6 +4,9 @@ use bevy_ecs::system::Resource; /// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %) /// +/// Note that gather system information is a time intensive task and therefore can't be done on every frame. +/// Any system diagnostics gathered by this plugin may not be current when you access them. +/// /// Supported targets: /// * linux, /// * windows, @@ -45,12 +48,6 @@ pub struct SystemInfo { pub memory: String, } -/// The expected interval at which system information will be queried and generated. -/// -/// The system diagnostic plugin doesn't work in all situations. In those situations this value will -/// bet set to None. -pub use internal::EXPECTED_SYSTEM_INFORMATION_INTERVAL; - // NOTE: sysinfo fails to compile when using bevy dynamic or on iOS and does nothing on wasm #[cfg(all( any( @@ -65,7 +62,7 @@ pub mod internal { use bevy_ecs::{prelude::ResMut, system::Local}; use std::{ sync::{Arc, Mutex}, - time::{Duration, Instant}, + time::Instant, }; use bevy_app::{App, First, Startup, Update}; @@ -94,9 +91,6 @@ pub mod internal { .add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%")); } - pub const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = - Some(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL); - struct SysinfoRefreshData { current_cpu_usage: f64, current_used_mem: f64, @@ -211,8 +205,6 @@ pub mod internal { not(feature = "dynamic_linking") )))] pub mod internal { - use std::time::Duration; - use bevy_app::{App, Startup}; pub(super) fn setup_plugin(app: &mut App) { @@ -223,8 +215,6 @@ pub mod internal { bevy_utils::tracing::warn!("This platform and/or configuration is not supported!"); } - pub const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option = None; - impl Default for super::SystemInfo { fn default() -> Self { let unknown = "Unknown".to_string(); From 873c22221284d45dd14f334a9df348db6ecd3674 Mon Sep 17 00:00:00 2001 From: Brezak Date: Mon, 10 Jun 2024 20:57:27 +0200 Subject: [PATCH 4/4] Fix typo in system diagnostic plugin documentation Co-authored-by: IceSentry --- .../src/system_information_diagnostics_plugin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index 30f1cdb38663c..981912ddc511c 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -4,7 +4,7 @@ use bevy_ecs::system::Resource; /// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %) /// -/// Note that gather system information is a time intensive task and therefore can't be done on every frame. +/// Note that gathering system information is a time intensive task and therefore can't be done on every frame. /// Any system diagnostics gathered by this plugin may not be current when you access them. /// /// Supported targets: