feat(monitor): 顶部栏增加磁盘占用,格式与内存一致

采集根分区(/)容量,按 DISK x% · x/x GB · Free x GB 拼入展示文本,
Stats 新增 disk_used_pct / disk_total_bytes / disk_available_bytes 字段。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cc 2026-06-21 15:31:27 +08:00
parent f576b03315
commit a3075133d1

View File

@ -6,7 +6,7 @@
use std::time::Duration; use std::time::Duration;
use serde::Serialize; use serde::Serialize;
use sysinfo::System; use sysinfo::{Disks, System};
use tokio::time::sleep; use tokio::time::sleep;
use crate::state::AppState; use crate::state::AppState;
@ -26,6 +26,12 @@ pub struct Stats {
pub mem_total_bytes: u64, pub mem_total_bytes: u64,
/// 可用内存(字节) /// 可用内存(字节)
pub mem_available_bytes: u64, pub mem_available_bytes: u64,
/// 磁盘使用率百分比0100
pub disk_used_pct: f32,
/// 磁盘总量(字节)
pub disk_total_bytes: u64,
/// 可用磁盘(字节)
pub disk_available_bytes: u64,
} }
impl Default for Stats { impl Default for Stats {
@ -36,6 +42,9 @@ impl Default for Stats {
mem_used_pct: 0.0, mem_used_pct: 0.0,
mem_total_bytes: 0, mem_total_bytes: 0,
mem_available_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 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 { impl Stats {
/// 从已刷新的 `System` 读出指标并格式化。 /// 从已刷新的 `System` + `Disks` 读出指标并格式化。
fn from_system(sys: &System) -> Self { fn from_system(sys: &System, disks: &Disks) -> Self {
let cpu = sys.global_cpu_usage(); let cpu = sys.global_cpu_usage();
let total = sys.total_memory(); let total = sys.total_memory();
let available = sys.available_memory(); let available = sys.available_memory();
@ -57,13 +79,25 @@ impl Stats {
0.0 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!( 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, cpu,
mem_used_pct, mem_used_pct,
bytes_to_gb(used), bytes_to_gb(used),
bytes_to_gb(total), bytes_to_gb(total),
bytes_to_gb(available), bytes_to_gb(available),
disk_used_pct,
bytes_to_gb(disk_used),
bytes_to_gb(disk_total),
bytes_to_gb(disk_available),
); );
Self { Self {
@ -72,6 +106,9 @@ impl Stats {
mem_used_pct, mem_used_pct,
mem_total_bytes: total, mem_total_bytes: total,
mem_available_bytes: available, 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`),之后循环里的读数才准确。 /// `MINIMUM_CPU_UPDATE_INTERVAL`),之后循环里的读数才准确。
pub async fn run_sampler(state: AppState) { pub async fn run_sampler(state: AppState) {
let mut sys = System::new(); let mut sys = System::new();
// 磁盘容量变化很慢,但每轮 refresh 一下也很廉价,省得单独计时。
let mut disks = Disks::new_with_refreshed_list();
sys.refresh_cpu_usage(); sys.refresh_cpu_usage();
sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await; sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await;
@ -89,8 +128,9 @@ pub async fn run_sampler(state: AppState) {
loop { loop {
sys.refresh_cpu_usage(); sys.refresh_cpu_usage();
sys.refresh_memory(); sys.refresh_memory();
disks.refresh(true);
let stats = Stats::from_system(&sys); let stats = Stats::from_system(&sys, &disks);
*state.snapshot.write().await = stats; *state.snapshot.write().await = stats;
sleep(Duration::from_secs(1)).await; sleep(Duration::from_secs(1)).await;