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::bounded(60);
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 std::thread::sleep(std::time::Duration::from_millis(interval_msec));
43
44 sys.refresh_specifics(*SYSTEM_REFRESH_KIND);
46 disks.refresh_specifics(true, *DISK_REFRESH_KIND);
47 components.refresh(true);
48
49 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 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}