par_term/status_bar/
system_monitor.rs1use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::thread::JoinHandle;
10use std::time::{Duration, Instant};
11
12use parking_lot::Mutex;
13
14#[derive(Debug, Clone, Default)]
16pub struct SystemMonitorData {
17 pub cpu_usage: f32,
19 pub memory_used: u64,
21 pub memory_total: u64,
23 pub network_rx_rate: u64,
25 pub network_tx_rate: u64,
27 pub last_update: Option<Instant>,
29}
30
31pub struct SystemMonitor {
36 data: Arc<Mutex<SystemMonitorData>>,
37 running: Arc<AtomicBool>,
38 thread: Mutex<Option<JoinHandle<()>>>,
39}
40
41impl SystemMonitor {
42 pub fn new() -> Self {
44 Self {
45 data: Arc::new(Mutex::new(SystemMonitorData::default())),
46 running: Arc::new(AtomicBool::new(false)),
47 thread: Mutex::new(None),
48 }
49 }
50
51 pub fn start(&self, poll_interval_secs: f32) {
55 if self
56 .running
57 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
58 .is_err()
59 {
60 return;
61 }
62
63 let data = Arc::clone(&self.data);
64 let running = Arc::clone(&self.running);
65 let interval = Duration::from_secs_f32(poll_interval_secs.max(0.5));
66
67 let handle = std::thread::Builder::new()
68 .name("status-bar-sysmon".to_string())
69 .spawn(move || {
70 use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
71
72 let mut sys = System::new_with_specifics(
73 RefreshKind::nothing()
74 .with_cpu(CpuRefreshKind::everything())
75 .with_memory(MemoryRefreshKind::everything()),
76 );
77 let mut networks = sysinfo::Networks::new_with_refreshed_list();
78
79 sys.refresh_cpu_all();
81 std::thread::sleep(Duration::from_millis(200));
82
83 let mut prev_rx: u64 = 0;
84 let mut prev_tx: u64 = 0;
85 let mut first_net = true;
86
87 while running.load(Ordering::SeqCst) {
88 sys.refresh_cpu_all();
89 sys.refresh_memory();
90 networks.refresh(true);
91
92 let (mut total_rx, mut total_tx) = (0u64, 0u64);
94 for (_name, net) in networks.iter() {
95 total_rx = total_rx.saturating_add(net.total_received());
96 total_tx = total_tx.saturating_add(net.total_transmitted());
97 }
98
99 let (rx_rate, tx_rate) = if first_net {
100 first_net = false;
101 (0, 0)
102 } else {
103 let secs = interval.as_secs_f64();
104 let rx_delta = total_rx.saturating_sub(prev_rx);
105 let tx_delta = total_tx.saturating_sub(prev_tx);
106 (
107 (rx_delta as f64 / secs) as u64,
108 (tx_delta as f64 / secs) as u64,
109 )
110 };
111 prev_rx = total_rx;
112 prev_tx = total_tx;
113
114 {
115 let mut d = data.lock();
116 d.cpu_usage = sys.global_cpu_usage();
117 d.memory_used = sys.used_memory();
118 d.memory_total = sys.total_memory();
119 d.network_rx_rate = rx_rate;
120 d.network_tx_rate = tx_rate;
121 d.last_update = Some(Instant::now());
122 }
123
124 let deadline = Instant::now() + interval;
126 while Instant::now() < deadline && running.load(Ordering::Relaxed) {
127 std::thread::sleep(Duration::from_millis(50));
128 }
129 }
130 })
131 .expect("failed to spawn sysmon thread");
132
133 *self.thread.lock() = Some(handle);
134 }
135
136 pub fn signal_stop(&self) {
138 self.running.store(false, Ordering::SeqCst);
139 }
140
141 pub fn stop(&self) {
143 self.signal_stop();
144 if let Some(handle) = self.thread.lock().take() {
145 let _ = handle.join();
146 }
147 }
148
149 pub fn is_running(&self) -> bool {
151 self.running.load(Ordering::SeqCst)
152 }
153
154 pub fn data(&self) -> SystemMonitorData {
156 self.data.lock().clone()
157 }
158}
159
160impl Default for SystemMonitor {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166impl Drop for SystemMonitor {
167 fn drop(&mut self) {
168 self.stop();
169 }
170}
171
172pub fn format_bytes_per_sec(bytes: u64) -> String {
181 const KB: u64 = 1024;
182 const MB: u64 = 1024 * 1024;
183 const GB: u64 = 1024 * 1024 * 1024;
184
185 if bytes >= GB {
186 format!("{:>5.1} GB/s", bytes as f64 / GB as f64)
187 } else if bytes >= MB {
188 format!("{:>5.1} MB/s", bytes as f64 / MB as f64)
189 } else if bytes >= KB {
190 format!("{:>5.1} KB/s", bytes as f64 / KB as f64)
191 } else {
192 format!("{:>5} B/s", bytes)
194 }
195}
196
197pub fn format_memory(used: u64, total: u64) -> String {
202 fn human(bytes: u64) -> String {
203 const KB: u64 = 1024;
204 const MB: u64 = 1024 * 1024;
205 const GB: u64 = 1024 * 1024 * 1024;
206
207 if bytes >= GB {
208 format!("{:>5.1} GB", bytes as f64 / GB as f64)
209 } else if bytes >= MB {
210 format!("{:>5.1} MB", bytes as f64 / MB as f64)
211 } else if bytes >= KB {
212 format!("{:>5.1} KB", bytes as f64 / KB as f64)
213 } else {
214 format!("{:>5} B", bytes)
215 }
216 }
217
218 format!("{} / {}", human(used), human(total))
219}
220
221#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_system_monitor_data_default() {
231 let d = SystemMonitorData::default();
232 assert_eq!(d.cpu_usage, 0.0);
233 assert_eq!(d.memory_used, 0);
234 assert_eq!(d.memory_total, 0);
235 assert_eq!(d.network_rx_rate, 0);
236 assert_eq!(d.network_tx_rate, 0);
237 assert!(d.last_update.is_none());
238 }
239
240 #[test]
241 fn test_format_bytes_per_sec() {
242 assert_eq!(format_bytes_per_sec(0), " 0 B/s");
243 assert_eq!(format_bytes_per_sec(512), " 512 B/s");
244 assert_eq!(format_bytes_per_sec(1024), " 1.0 KB/s");
245 assert_eq!(format_bytes_per_sec(1536), " 1.5 KB/s");
246 assert_eq!(format_bytes_per_sec(1_048_576), " 1.0 MB/s");
247 assert_eq!(format_bytes_per_sec(1_073_741_824), " 1.0 GB/s");
248 assert_eq!(
250 format_bytes_per_sec(0).len(),
251 format_bytes_per_sec(1024).len()
252 );
253 assert_eq!(
254 format_bytes_per_sec(1024).len(),
255 format_bytes_per_sec(1_048_576).len()
256 );
257 }
258
259 #[test]
260 fn test_format_memory() {
261 assert_eq!(format_memory(0, 0), " 0 B / 0 B");
262 assert_eq!(
264 format_memory(1_073_741_824, 8_589_934_592),
265 " 1.0 GB / 8.0 GB"
266 );
267 assert_eq!(
269 format_memory(536_870_912, 1_073_741_824),
270 "512.0 MB / 1.0 GB"
271 );
272 }
273
274 #[test]
275 fn test_system_monitor_start_stop() {
276 let monitor = SystemMonitor::new();
277 assert!(!monitor.is_running());
278
279 monitor.start(1.0);
280 assert!(monitor.is_running());
281
282 std::thread::sleep(Duration::from_millis(500));
284
285 let data = monitor.data();
286 assert!(data.last_update.is_some());
288
289 monitor.stop();
290 assert!(!monitor.is_running());
291 }
292}