diff --git a/Server/src/monitor.rs b/Server/src/monitor.rs index a8fd828..4a7a12d 100644 --- a/Server/src/monitor.rs +++ b/Server/src/monitor.rs @@ -6,7 +6,7 @@ use std::time::Duration; use serde::Serialize; -use sysinfo::System; +use sysinfo::{Disks, System}; use tokio::time::sleep; use crate::state::AppState; @@ -26,6 +26,12 @@ pub struct Stats { pub mem_total_bytes: u64, /// 可用内存(字节) pub mem_available_bytes: u64, + /// 磁盘使用率(百分比,0–100) + pub disk_used_pct: f32, + /// 磁盘总量(字节) + pub disk_total_bytes: u64, + /// 可用磁盘(字节) + pub disk_available_bytes: u64, } impl Default for Stats { @@ -36,6 +42,9 @@ impl Default for Stats { mem_used_pct: 0.0, mem_total_bytes: 0, mem_available_bytes: 0, + disk_used_pct: 0.0, + disk_total_bytes: 0, + disk_available_bytes: 0, } } } @@ -44,9 +53,22 @@ fn bytes_to_gb(bytes: u64) -> f64 { bytes as f64 / 1024.0 / 1024.0 / 1024.0 } +/// 选出要展示的磁盘容量:优先根分区 `/`(Linux 线上环境),否则取容量最大的那块 +/// (兜底 Windows/本地等无 `/` 的情况)。返回 `(总字节, 可用字节)`;无磁盘时 `(0, 0)`。 +fn root_disk(disks: &Disks) -> (u64, u64) { + let chosen = disks + .iter() + .find(|d| d.mount_point() == std::path::Path::new("/")) + .or_else(|| disks.iter().max_by_key(|d| d.total_space())); + match chosen { + Some(d) => (d.total_space(), d.available_space()), + None => (0, 0), + } +} + impl Stats { - /// 从已刷新的 `System` 读出指标并格式化。 - fn from_system(sys: &System) -> Self { + /// 从已刷新的 `System` + `Disks` 读出指标并格式化。 + fn from_system(sys: &System, disks: &Disks) -> Self { let cpu = sys.global_cpu_usage(); let total = sys.total_memory(); let available = sys.available_memory(); @@ -57,13 +79,25 @@ impl Stats { 0.0 }; + let (disk_total, disk_available) = root_disk(disks); + let disk_used = disk_total.saturating_sub(disk_available); + let disk_used_pct = if disk_total > 0 { + disk_used as f32 / disk_total as f32 * 100.0 + } else { + 0.0 + }; + let text = format!( - "CPU {:.1}% · MEM {:.1}% · {:.1}/{:.1} GB · Free {:.1} GB", + "CPU {:.1}% · MEM {:.1}% · {:.1}/{:.1} GB · Free {:.1} GB · DISK {:.1}% · {:.1}/{:.1} GB · Free {:.1} GB", cpu, mem_used_pct, bytes_to_gb(used), bytes_to_gb(total), bytes_to_gb(available), + disk_used_pct, + bytes_to_gb(disk_used), + bytes_to_gb(disk_total), + bytes_to_gb(disk_available), ); Self { @@ -72,6 +106,9 @@ impl Stats { mem_used_pct, mem_total_bytes: total, mem_available_bytes: available, + disk_used_pct, + disk_total_bytes: disk_total, + disk_available_bytes: disk_available, } } } @@ -82,6 +119,8 @@ impl Stats { /// `MINIMUM_CPU_UPDATE_INTERVAL`),之后循环里的读数才准确。 pub async fn run_sampler(state: AppState) { let mut sys = System::new(); + // 磁盘容量变化很慢,但每轮 refresh 一下也很廉价,省得单独计时。 + let mut disks = Disks::new_with_refreshed_list(); sys.refresh_cpu_usage(); sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await; @@ -89,8 +128,9 @@ pub async fn run_sampler(state: AppState) { loop { sys.refresh_cpu_usage(); sys.refresh_memory(); + disks.refresh(true); - let stats = Stats::from_system(&sys); + let stats = Stats::from_system(&sys, &disks); *state.snapshot.write().await = stats; sleep(Duration::from_secs(1)).await;