Skip to main content

yggdrasil/tasks/
sysinfo_sampler.rs

1//! sysinfo 主机指标后台采样。
2//!
3//! 仅在 `server` feature 启用时编译。sysinfo 的 CPU% **不是即时值**,需要两次采样
4//! 间的 delta。因此用后台任务周期刷新,server function 只读
5//! [`tokio::sync::RwLock`] 快照(毫秒级返回,零采样成本)。
6//!
7//! 采样间隔由环境变量 `SYSINFO_SAMPLE_SECS` 配置(默认 0.5 秒,支持小数如 0.1)。
8//! 快照 DTO [`crate::models::system::SystemSnapshot`] 定义在 [`crate::models::system`](两端共享)。
9
10use std::sync::LazyLock;
11use std::time::Duration;
12
13use crate::models::system::SystemSnapshot;
14
15static SNAPSHOT: LazyLock<tokio::sync::RwLock<SystemSnapshot>> =
16    LazyLock::new(|| tokio::sync::RwLock::new(SystemSnapshot::default()));
17
18/// 采样间隔,由环境变量 `SYSINFO_SAMPLE_SECS` 配置,默认 0.5 秒,下限 0.05 秒。
19fn sample_interval() -> Duration {
20    let secs = std::env::var("SYSINFO_SAMPLE_SECS")
21        .ok()
22        .and_then(|s| s.parse::<f64>().ok())
23        .unwrap_or(0.5);
24    Duration::from_secs_f64(secs.max(0.05))
25}
26
27/// 启动后台采样任务。应在 main 启动流程(migrate 之后、serve 之前)调用一次。
28///
29/// CPU% 需两次采样 delta,故循环里先 refresh 再 sleep 再 refresh 才有有效值。
30pub fn spawn_sampler() {
31    tokio::spawn(async move {
32        use sysinfo::{Disks, System};
33
34        let mut sys = System::new();
35        let interval = sample_interval();
36        // 首次 refresh 建立基线,CPU% 在下一轮才有意义。
37        sys.refresh_cpu_usage();
38        let disks = Disks::new_with_refreshed_list();
39
40        loop {
41            tokio::time::sleep(interval).await;
42            sys.refresh_cpu_usage();
43            sys.refresh_memory();
44            let load = System::load_average();
45
46            // 主磁盘:取空间最大的盘(通常是数据盘)。
47            let (disk_total, disk_available) = disks
48                .list()
49                .iter()
50                .max_by_key(|d| d.total_space())
51                .map(|d| (d.total_space(), d.available_space()))
52                .unwrap_or((0, 0));
53
54            let snap = SystemSnapshot {
55                cpu_usage: sys.global_cpu_usage(),
56                load_avg_1: load.one,
57                total_memory: sys.total_memory(),
58                used_memory: sys.used_memory(),
59                disk_total,
60                disk_available,
61                os_name: System::long_os_version().unwrap_or_default(),
62                kernel_version: System::kernel_version().unwrap_or_default(),
63                uptime_secs: System::uptime(),
64            };
65            *SNAPSHOT.write().await = snap;
66        }
67    });
68}
69
70/// 读取最新快照(只读,毫秒级返回,不触发采样)。
71pub async fn read_snapshot() -> SystemSnapshot {
72    SNAPSHOT.read().await.clone()
73}