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    crate::tasks::spawn_supervised("sysinfo_sampler", async move {
32        use sysinfo::{Disks, System};
33
34        let mut sys = match System::new() {
35            Ok(sys) => sys,
36            Err(error) => {
37                tracing::warn!("sysinfo 不支持当前系统,主机指标保持默认值: {error}");
38                return;
39            }
40        };
41        let interval = sample_interval();
42        // 首次 refresh 建立基线,CPU% 在下一轮才有意义。
43        sys.refresh_cpu_usage();
44        let disks = Disks::new_with_refreshed_list().ok();
45
46        loop {
47            tokio::time::sleep(interval).await;
48            sys.refresh_cpu_usage();
49            sys.refresh_memory();
50            let load = System::load_average().unwrap_or_default();
51
52            // 主磁盘:取空间最大的盘(通常是数据盘)。
53            let (disk_total, disk_available) = disks
54                .iter()
55                .flat_map(|disks| disks.iter())
56                .max_by_key(|d| d.total_space())
57                .map(|d| (d.total_space(), d.available_space()))
58                .unwrap_or((0, 0));
59
60            let snap = SystemSnapshot {
61                cpu_usage: sys.global_cpu_usage(),
62                load_avg_1: load.one,
63                total_memory: sys.total_memory(),
64                used_memory: sys.used_memory(),
65                disk_total,
66                disk_available,
67                os_name: System::long_os_version().unwrap_or_default(),
68                kernel_version: System::kernel_version().unwrap_or_default(),
69                uptime_secs: System::uptime().unwrap_or_default(),
70            };
71            *SNAPSHOT.write().await = snap;
72        }
73    });
74}
75
76/// 读取最新快照(只读,毫秒级返回,不触发采样)。
77pub async fn read_snapshot() -> SystemSnapshot {
78    SNAPSHOT.read().await.clone()
79}