Poll system information in separate tasks (#13693)
Reading system information severely slows down the update loop. Fixes #12848. Read system info in a separate thread. - Open the scene 3d example - Add `FrameTimeDiagnosticsPlugin`, `SystemInformationDiagnosticsPlugin` and `LogDiagnosticsPlugin` to the app. - Add this system to the update schedule to disable Vsync on the main window ```rust fn change_window_mode(mut windows: Query<&mut Window, Added<Window>>) { for mut window in &mut windows { window.present_mode = PresentMode::AutoNoVsync; } } ``` - Read the fps values in the console before and after this PR. On my PC I went from around 50 fps to around 1150 fps. --- - The `SystemInformationDiagnosticsPlugin` now reads system data separate of the update cycle. - The `EXPECTED_SYSTEM_INFORMATION_INTERVAL` constant which defines how often we read system diagnostic data. --------- Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
This commit is contained in:
parent
65dbfe249b
commit
7cd90990f9
@ -20,6 +20,7 @@ bevy_core = { path = "../bevy_core", version = "0.14.0-rc.2" }
|
|||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-rc.2" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-rc.2" }
|
||||||
bevy_time = { path = "../bevy_time", version = "0.14.0-rc.2" }
|
bevy_time = { path = "../bevy_time", version = "0.14.0-rc.2" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.14.0-rc.2" }
|
bevy_utils = { path = "../bevy_utils", version = "0.14.0-rc.2" }
|
||||||
|
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-rc.2" }
|
||||||
|
|
||||||
const-fnv1a-hash = "1.1.0"
|
const-fnv1a-hash = "1.1.0"
|
||||||
|
|
||||||
|
@ -4,6 +4,9 @@ use bevy_ecs::system::Resource;
|
|||||||
|
|
||||||
/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %)
|
/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %)
|
||||||
///
|
///
|
||||||
|
/// 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:
|
/// Supported targets:
|
||||||
/// * linux,
|
/// * linux,
|
||||||
/// * windows,
|
/// * windows,
|
||||||
@ -19,8 +22,7 @@ use bevy_ecs::system::Resource;
|
|||||||
pub struct SystemInformationDiagnosticsPlugin;
|
pub struct SystemInformationDiagnosticsPlugin;
|
||||||
impl Plugin for SystemInformationDiagnosticsPlugin {
|
impl Plugin for SystemInformationDiagnosticsPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Startup, internal::setup_system)
|
internal::setup_plugin(app);
|
||||||
.add_systems(Update, internal::diagnostic_system);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +60,14 @@ pub struct SystemInfo {
|
|||||||
))]
|
))]
|
||||||
pub mod internal {
|
pub mod internal {
|
||||||
use bevy_ecs::{prelude::ResMut, system::Local};
|
use bevy_ecs::{prelude::ResMut, system::Local};
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 bevy_utils::tracing::info;
|
||||||
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
|
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
|
||||||
|
|
||||||
@ -67,41 +77,91 @@ pub mod internal {
|
|||||||
|
|
||||||
const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0;
|
const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0;
|
||||||
|
|
||||||
pub(crate) fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) {
|
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::<SysinfoTasks>();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) {
|
||||||
diagnostics
|
diagnostics
|
||||||
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::CPU_USAGE).with_suffix("%"));
|
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::CPU_USAGE).with_suffix("%"));
|
||||||
diagnostics
|
diagnostics
|
||||||
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%"));
|
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn diagnostic_system(
|
struct SysinfoRefreshData {
|
||||||
mut diagnostics: Diagnostics,
|
current_cpu_usage: f64,
|
||||||
mut sysinfo: Local<Option<System>>,
|
current_used_mem: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct SysinfoTasks {
|
||||||
|
tasks: Vec<Task<SysinfoRefreshData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn launch_diagnostic_tasks(
|
||||||
|
mut tasks: ResMut<SysinfoTasks>,
|
||||||
|
// TODO: Consider a fair mutex
|
||||||
|
mut sysinfo: Local<Option<Arc<Mutex<System>>>>,
|
||||||
|
// TODO: FromWorld for Instant?
|
||||||
|
mut last_refresh: Local<Option<Instant>>,
|
||||||
) {
|
) {
|
||||||
if sysinfo.is_none() {
|
let sysinfo = sysinfo.get_or_insert_with(|| {
|
||||||
*sysinfo = Some(System::new_with_specifics(
|
Arc::new(Mutex::new(System::new_with_specifics(
|
||||||
RefreshKind::new()
|
RefreshKind::new()
|
||||||
.with_cpu(CpuRefreshKind::new().with_cpu_usage())
|
.with_cpu(CpuRefreshKind::new().with_cpu_usage())
|
||||||
.with_memory(MemoryRefreshKind::everything()),
|
.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 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_diagnostic_tasks(mut diagnostics: Diagnostics, mut tasks: ResMut<SysinfoTasks>) {
|
||||||
|
tasks.tasks.retain_mut(|task| {
|
||||||
|
let Some(data) = block_on(poll_once(task)) else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || {
|
||||||
|
data.current_cpu_usage
|
||||||
|
});
|
||||||
|
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || {
|
||||||
|
data.current_used_mem
|
||||||
|
});
|
||||||
|
false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,12 +205,14 @@ pub mod internal {
|
|||||||
not(feature = "dynamic_linking")
|
not(feature = "dynamic_linking")
|
||||||
)))]
|
)))]
|
||||||
pub mod internal {
|
pub mod internal {
|
||||||
pub(crate) fn setup_system() {
|
use bevy_app::{App, Startup};
|
||||||
bevy_utils::tracing::warn!("This platform and/or configuration is not supported!");
|
|
||||||
|
pub(super) fn setup_plugin(app: &mut App) {
|
||||||
|
app.add_systems(Startup, setup_system);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn diagnostic_system() {
|
fn setup_system() {
|
||||||
// no-op
|
bevy_utils::tracing::warn!("This platform and/or configuration is not supported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for super::SystemInfo {
|
impl Default for super::SystemInfo {
|
||||||
|
Loading…
Reference in New Issue
Block a user