nullnet_libresmon/
lib.rs

1use async_channel::Receiver;
2use std::collections::HashMap;
3use std::fmt::{Display, Formatter};
4use std::path::Path;
5use sysinfo::{
6    Components, CpuRefreshKind, DiskRefreshKind, Disks, MemoryRefreshKind, RefreshKind, System,
7};
8
9static SYSTEM_REFRESH_KIND: std::sync::LazyLock<RefreshKind> = std::sync::LazyLock::new(|| {
10    RefreshKind::nothing()
11        .with_cpu(CpuRefreshKind::nothing().with_cpu_usage())
12        .with_memory(MemoryRefreshKind::nothing().with_ram())
13});
14
15static DISK_REFRESH_KIND: std::sync::LazyLock<DiskRefreshKind> =
16    std::sync::LazyLock::new(|| DiskRefreshKind::nothing().with_io_usage().with_storage());
17
18#[derive(Default)]
19pub struct SystemResources {
20    pub num_cpus: usize,
21    pub global_cpu_usage: f32,
22    pub cpu_usages: HashMap<String, f32>,
23    pub total_memory: u64,
24    pub used_memory: u64,
25    pub total_disk_space: u64,
26    pub available_disk_space: u64,
27    pub read_bytes: u64,
28    pub written_bytes: u64,
29    pub temperatures: HashMap<String, Option<f32>>,
30}
31
32#[must_use]
33pub fn poll_system_resources(interval_msec: u64) -> Receiver<SystemResources> {
34    let (tx, rx) = async_channel::unbounded();
35
36    std::thread::spawn(move || {
37        let mut sys = System::new_with_specifics(*SYSTEM_REFRESH_KIND);
38        let mut disks = Disks::new_with_refreshed_list_specifics(*DISK_REFRESH_KIND);
39        let mut components = Components::new_with_refreshed_list();
40        loop {
41            // sleep for 1 second
42            std::thread::sleep(std::time::Duration::from_millis(interval_msec));
43
44            // refresh system resources
45            sys.refresh_specifics(*SYSTEM_REFRESH_KIND);
46            disks.refresh_specifics(true, *DISK_REFRESH_KIND);
47            components.refresh(true);
48
49            // update resources
50
51            let mut cpu_usages = HashMap::new();
52            for cpu in sys.cpus() {
53                let usage = cpu.cpu_usage();
54                cpu_usages.insert(cpu.name().to_string(), usage);
55            }
56
57            let mut total_disk_space = 0;
58            let mut available_disk_space = 0;
59            let mut read_bytes = 0;
60            let mut written_bytes = 0;
61            for disk in &disks {
62                if disk.mount_point() == Path::new("/") {
63                    total_disk_space = disk.total_space();
64                    available_disk_space = disk.available_space();
65                    let disk_usage = disk.usage();
66                    read_bytes = disk_usage.read_bytes;
67                    written_bytes = disk_usage.written_bytes;
68                }
69            }
70
71            let mut temperatures = HashMap::new();
72            for component in &components {
73                let temperature = component.temperature();
74                temperatures.insert(component.label().to_string(), temperature);
75            }
76
77            let resources = SystemResources {
78                num_cpus: sys.cpus().len(),
79                global_cpu_usage: sys.global_cpu_usage(),
80                cpu_usages,
81                total_memory: sys.total_memory(),
82                used_memory: sys.used_memory(),
83                total_disk_space,
84                available_disk_space,
85                read_bytes,
86                written_bytes,
87                temperatures,
88            };
89
90            // send resources to caller, or exit if channel is closed
91            let Ok(()) = tx.send_blocking(resources) else {
92                return;
93            };
94        }
95    });
96
97    rx
98}
99
100impl Display for SystemResources {
101    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
102        writeln!(
103            f,
104            "\n\nCPU -------------------------------------------------------------------------\n"
105        )?;
106        writeln!(f, "Number of CPUs: {}", self.num_cpus)?;
107        writeln!(f, "Global CPU usage: {:.2}%", self.global_cpu_usage)?;
108        let mut sorted_cpu_usages = self
109            .cpu_usages
110            .iter()
111            .map(|(k, v)| (k.clone(), *v))
112            .collect::<Vec<_>>();
113        sorted_cpu_usages.sort_by(|(l1, _), (l2, _)| l1.cmp(l2));
114        for (name, usage) in &sorted_cpu_usages {
115            writeln!(f, "  - '{name}' usage: {usage:.2}%")?;
116        }
117
118        writeln!(
119            f,
120            "\n\nRAM -------------------------------------------------------------------------\n"
121        )?;
122        writeln!(f, "Total RAM space: {} bytes", self.total_memory)?;
123        writeln!(f, "Used RAM space: {} bytes", self.used_memory)?;
124
125        writeln!(
126            f,
127            "\n\nDISK ------------------------------------------------------------------------\n"
128        )?;
129        writeln!(f, "Total disk space : {} bytes", self.total_disk_space)?;
130        writeln!(
131            f,
132            "Available disk space: {} bytes",
133            self.available_disk_space
134        )?;
135
136        writeln!(
137            f,
138            "\n\nI/O -------------------------------------------------------------------------\n"
139        )?;
140        writeln!(f, "Read: {:?} bytes", self.read_bytes)?;
141        writeln!(f, "Written: {:?} bytes", self.written_bytes)?;
142
143        writeln!(
144            f,
145            "\n\nTEMPERATURE -----------------------------------------------------------------\n"
146        )?;
147        let mut sorted_temperatures = self
148            .temperatures
149            .iter()
150            .map(|(k, v)| (k.clone(), *v))
151            .collect::<Vec<_>>();
152        sorted_temperatures.sort_by(|(l1, _), (l2, _)| l1.cmp(l2));
153        for (label, temperature) in &sorted_temperatures {
154            let temperature_str = temperature
155                .map(|t| format!("{t:.2}°C"))
156                .unwrap_or("?".to_string());
157            writeln!(f, "{label}: {temperature_str}")?;
158        }
159
160        Ok(())
161    }
162}