119 lines
3.9 KiB
Rust
119 lines
3.9 KiB
Rust
//! Lightweight host metrics (CPU %, RAM, disk, battery, uptime) for the portal's
|
|
//! live monitoring. Windows-only; sampled every few seconds by the engine and
|
|
//! pushed to the server as a `host.telemetry` control message.
|
|
#![cfg(windows)]
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
/// Keeps the previous CPU time sample so each `sample()` yields a real % over
|
|
/// the interval since the last call.
|
|
pub struct Telemetry {
|
|
prev_idle: u64,
|
|
prev_total: u64,
|
|
}
|
|
|
|
impl Telemetry {
|
|
pub fn new() -> Self {
|
|
Self { prev_idle: 0, prev_total: 0 }
|
|
}
|
|
|
|
pub fn sample(&mut self) -> Value {
|
|
let cpu = self.cpu_percent();
|
|
let (ram_used, ram_total) = mem();
|
|
let (disk_used, disk_total) = disk();
|
|
let (battery, charging) = power();
|
|
json!({
|
|
"t": "host.telemetry",
|
|
"cpu": cpu,
|
|
"ram_used": ram_used, "ram_total": ram_total,
|
|
"disk_used": disk_used, "disk_total": disk_total,
|
|
"battery": battery, "charging": charging,
|
|
"uptime": uptime_secs(),
|
|
})
|
|
}
|
|
|
|
fn cpu_percent(&mut self) -> u32 {
|
|
use windows::Win32::Foundation::FILETIME;
|
|
use windows::Win32::System::Threading::GetSystemTimes;
|
|
unsafe {
|
|
let mut idle = FILETIME::default();
|
|
let mut kern = FILETIME::default();
|
|
let mut user = FILETIME::default();
|
|
if GetSystemTimes(Some(&mut idle), Some(&mut kern), Some(&mut user)).is_err() {
|
|
return 0;
|
|
}
|
|
let ft = |f: FILETIME| ((f.dwHighDateTime as u64) << 32) | f.dwLowDateTime as u64;
|
|
let idle_t = ft(idle);
|
|
// Kernel time INCLUDES idle, so kernel+user is the full busy+idle total.
|
|
let total_t = ft(kern) + ft(user);
|
|
let d_total = total_t.saturating_sub(self.prev_total);
|
|
let d_idle = idle_t.saturating_sub(self.prev_idle);
|
|
self.prev_total = total_t;
|
|
self.prev_idle = idle_t;
|
|
if d_total == 0 {
|
|
return 0;
|
|
}
|
|
let busy = d_total.saturating_sub(d_idle);
|
|
((busy as f64 / d_total as f64) * 100.0).round().clamp(0.0, 100.0) as u32
|
|
}
|
|
}
|
|
}
|
|
|
|
fn mem() -> (u64, u64) {
|
|
use windows::Win32::System::SystemInformation::{GlobalMemoryStatusEx, MEMORYSTATUSEX};
|
|
unsafe {
|
|
let mut m = MEMORYSTATUSEX {
|
|
dwLength: std::mem::size_of::<MEMORYSTATUSEX>() as u32,
|
|
..Default::default()
|
|
};
|
|
if GlobalMemoryStatusEx(&mut m).is_ok() {
|
|
(m.ullTotalPhys.saturating_sub(m.ullAvailPhys), m.ullTotalPhys)
|
|
} else {
|
|
(0, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn disk() -> (u64, u64) {
|
|
use windows::core::PCWSTR;
|
|
use windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW;
|
|
unsafe {
|
|
let path: Vec<u16> = "C:\\".encode_utf16().chain(std::iter::once(0)).collect();
|
|
let mut free_avail = 0u64;
|
|
let mut total = 0u64;
|
|
let mut total_free = 0u64;
|
|
if GetDiskFreeSpaceExW(
|
|
PCWSTR(path.as_ptr()),
|
|
Some(&mut free_avail),
|
|
Some(&mut total),
|
|
Some(&mut total_free),
|
|
)
|
|
.is_ok()
|
|
{
|
|
(total.saturating_sub(total_free), total)
|
|
} else {
|
|
(0, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn power() -> (i32, bool) {
|
|
use windows::Win32::System::Power::GetSystemPowerStatus;
|
|
use windows::Win32::System::Power::SYSTEM_POWER_STATUS;
|
|
unsafe {
|
|
let mut s = SYSTEM_POWER_STATUS::default();
|
|
if GetSystemPowerStatus(&mut s).is_ok() {
|
|
let pct = if s.BatteryLifePercent == 255 { -1 } else { s.BatteryLifePercent as i32 };
|
|
let charging = s.ACLineStatus == 1; // 1 = plugged in
|
|
(pct, charging)
|
|
} else {
|
|
(-1, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn uptime_secs() -> u64 {
|
|
use windows::Win32::System::SystemInformation::GetTickCount64;
|
|
unsafe { GetTickCount64() / 1000 }
|
|
}
|