Skip to content

Commit

Permalink
Poll system information in separate thread
Browse files Browse the repository at this point in the history
  • Loading branch information
Brezak committed Jun 5, 2024
1 parent 38d3833 commit d15df1e
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 27 deletions.
4 changes: 3 additions & 1 deletion crates/bevy_diagnostic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand Down
95 changes: 69 additions & 26 deletions crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs
Original file line number Diff line number Diff line change
@@ -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 %)
///
Expand Down Expand Up @@ -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<Duration> =
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(
Expand All @@ -57,8 +65,13 @@ pub struct SystemInfo {
not(feature = "dynamic_linking")
))]
pub mod internal {
use std::{
sync::mpsc::{self, Receiver, Sender},
thread,
};

use bevy_ecs::{prelude::ResMut, system::Local};
use bevy_utils::tracing::info;
use bevy_utils::{tracing::info, Duration};
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};

use crate::{Diagnostic, Diagnostics, DiagnosticsStore};
Expand All @@ -74,35 +87,63 @@ pub mod internal {
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%"));
}

pub(crate) const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option<Duration> =
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<Option<System>>,
mut sysinfo: Local<Option<Receiver<(f64, f64)>>>,
) {
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 {
Expand Down Expand Up @@ -153,6 +194,8 @@ pub mod internal {
// no-op
}

pub(crate) const EXPECTED_SYSTEM_INFORMATION_INTERVAL: Option<Duration> = None;

impl Default for super::SystemInfo {
fn default() -> Self {
let unknown = "Unknown".to_string();
Expand Down
64 changes: 64 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#![allow(missing_docs)]

use bevy::{
diagnostic::{
FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin, SystemInformationDiagnosticsPlugin,
},
prelude::*,
window::PresentMode,
};

fn main() {
App::new()
.add_plugins((
DefaultPlugins,
FrameTimeDiagnosticsPlugin,
LogDiagnosticsPlugin::default(),
SystemInformationDiagnosticsPlugin,
))
.add_systems(Startup, setup)
.add_systems(Update, change_window_mode)
.run();
}

fn change_window_mode(mut windows: Query<&mut Window, Added<Window>>) {
for mut window in &mut windows {
window.present_mode = PresentMode::AutoNoVsync;
}
}

/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// circular base
commands.spawn(PbrBundle {
mesh: meshes.add(Circle::new(4.0)),
material: materials.add(Color::WHITE),
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
..default()
});
// cube
commands.spawn(PbrBundle {
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
material: materials.add(Color::srgb_u8(124, 144, 255)),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// camera
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}

0 comments on commit d15df1e

Please sign in to comment.