par_term/status_bar/
system_monitor.rs1use std::time::Instant;
8
9#[derive(Debug, Clone, Default)]
15pub struct SystemMonitorData {
16 pub cpu_usage: f32,
18 pub memory_used: u64,
20 pub memory_total: u64,
22 pub network_rx_rate: u64,
24 pub network_tx_rate: u64,
26 pub last_update: Option<Instant>,
28}
29
30#[cfg(feature = "system-monitor")]
35mod inner {
36 use std::sync::Arc;
37 use std::sync::atomic::{AtomicBool, Ordering};
38 use std::thread::JoinHandle;
39 use std::time::{Duration, Instant};
40
41 use parking_lot::Mutex;
42
43 use super::SystemMonitorData;
44
45 pub struct SystemMonitor {
50 data: Arc<Mutex<SystemMonitorData>>,
51 running: Arc<AtomicBool>,
52 thread: Mutex<Option<JoinHandle<()>>>,
53 }
54
55 impl SystemMonitor {
56 pub fn new() -> Self {
58 Self {
59 data: Arc::new(Mutex::new(SystemMonitorData::default())),
60 running: Arc::new(AtomicBool::new(false)),
61 thread: Mutex::new(None),
62 }
63 }
64
65 pub fn start(&self, poll_interval_secs: f32) {
69 if self
70 .running
71 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
72 .is_err()
73 {
74 return;
75 }
76
77 let data = Arc::clone(&self.data);
78 let running = Arc::clone(&self.running);
79 let interval = Duration::from_secs_f32(poll_interval_secs.max(0.5));
80
81 let handle = std::thread::Builder::new()
82 .name("status-bar-sysmon".to_string())
83 .spawn(move || {
84 use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
85
86 let mut sys = System::new_with_specifics(
87 RefreshKind::nothing()
88 .with_cpu(CpuRefreshKind::everything())
89 .with_memory(MemoryRefreshKind::everything()),
90 );
91 let mut networks = sysinfo::Networks::new_with_refreshed_list();
92
93 sys.refresh_cpu_all();
95 std::thread::sleep(Duration::from_millis(200));
96
97 let mut prev_rx: u64 = 0;
98 let mut prev_tx: u64 = 0;
99 let mut first_net = true;
100
101 while running.load(Ordering::SeqCst) {
102 sys.refresh_cpu_all();
103 sys.refresh_memory();
104 networks.refresh(true);
105
106 let (mut total_rx, mut total_tx) = (0u64, 0u64);
108 for net in networks.values() {
109 total_rx = total_rx.saturating_add(net.total_received());
110 total_tx = total_tx.saturating_add(net.total_transmitted());
111 }
112
113 let (rx_rate, tx_rate) = if first_net {
114 first_net = false;
115 (0, 0)
116 } else {
117 let secs = interval.as_secs_f64();
118 let rx_delta = total_rx.saturating_sub(prev_rx);
119 let tx_delta = total_tx.saturating_sub(prev_tx);
120 (
121 (rx_delta as f64 / secs) as u64,
122 (tx_delta as f64 / secs) as u64,
123 )
124 };
125 prev_rx = total_rx;
126 prev_tx = total_tx;
127
128 {
129 let mut d = data.lock();
130 d.cpu_usage = sys.global_cpu_usage();
131 d.memory_used = sys.used_memory();
132 d.memory_total = sys.total_memory();
133 d.network_rx_rate = rx_rate;
134 d.network_tx_rate = tx_rate;
135 d.last_update = Some(Instant::now());
136 }
137
138 let deadline = Instant::now() + interval;
140 while Instant::now() < deadline && running.load(Ordering::Relaxed) {
141 std::thread::sleep(Duration::from_millis(50));
142 }
143 }
144 });
145
146 match handle {
147 Ok(h) => *self.thread.lock() = Some(h),
148 Err(e) => {
149 self.running.store(false, Ordering::SeqCst);
153 crate::debug_error!("SESSION_LOGGER", "failed to spawn sysmon thread: {:?}", e);
154 }
155 }
156 }
157
158 pub fn signal_stop(&self) {
160 self.running.store(false, Ordering::SeqCst);
161 }
162
163 pub fn stop(&self) {
165 self.signal_stop();
166 if let Some(handle) = self.thread.lock().take() {
167 let _ = handle.join();
168 }
169 }
170
171 pub fn is_running(&self) -> bool {
173 self.running.load(Ordering::SeqCst)
174 }
175
176 pub fn data(&self) -> SystemMonitorData {
178 self.data.lock().clone()
179 }
180 }
181
182 impl Default for SystemMonitor {
183 fn default() -> Self {
184 Self::new()
185 }
186 }
187
188 impl Drop for SystemMonitor {
189 fn drop(&mut self) {
190 self.stop();
191 }
192 }
193}
194
195#[cfg(feature = "system-monitor")]
196pub use inner::SystemMonitor;
197
198#[cfg(not(feature = "system-monitor"))]
203mod inner {
204 use super::SystemMonitorData;
205
206 pub struct SystemMonitor;
212
213 impl SystemMonitor {
214 pub fn new() -> Self {
216 Self
217 }
218
219 pub fn start(&self, _poll_interval_secs: f32) {}
221
222 pub fn signal_stop(&self) {}
224
225 pub fn stop(&self) {}
227
228 pub fn is_running(&self) -> bool {
230 false
231 }
232
233 pub fn data(&self) -> SystemMonitorData {
235 SystemMonitorData::default()
236 }
237 }
238
239 impl Default for SystemMonitor {
240 fn default() -> Self {
241 Self::new()
242 }
243 }
244}
245
246#[cfg(not(feature = "system-monitor"))]
247pub use inner::SystemMonitor;
248
249pub fn format_bytes_per_sec(bytes: u64) -> String {
258 const KB: u64 = 1024;
259 const MB: u64 = 1024 * 1024;
260 const GB: u64 = 1024 * 1024 * 1024;
261
262 if bytes >= GB {
263 format!("{:>5.1} GB/s", bytes as f64 / GB as f64)
264 } else if bytes >= MB {
265 format!("{:>5.1} MB/s", bytes as f64 / MB as f64)
266 } else if bytes >= KB {
267 format!("{:>5.1} KB/s", bytes as f64 / KB as f64)
268 } else {
269 format!("{:>5} B/s", bytes)
271 }
272}
273
274pub fn format_memory(used: u64, total: u64) -> String {
279 fn human(bytes: u64) -> String {
280 const KB: u64 = 1024;
281 const MB: u64 = 1024 * 1024;
282 const GB: u64 = 1024 * 1024 * 1024;
283
284 if bytes >= GB {
285 format!("{:>5.1} GB", bytes as f64 / GB as f64)
286 } else if bytes >= MB {
287 format!("{:>5.1} MB", bytes as f64 / MB as f64)
288 } else if bytes >= KB {
289 format!("{:>5.1} KB", bytes as f64 / KB as f64)
290 } else {
291 format!("{:>5} B", bytes)
292 }
293 }
294
295 format!("{} / {}", human(used), human(total))
296}
297
298pub fn format_bytes(bytes: u64) -> String {
304 const KB: u64 = 1024;
305 const MB: u64 = 1024 * 1024;
306 const GB: u64 = 1024 * 1024 * 1024;
307 const TB: u64 = 1024 * 1024 * 1024 * 1024;
308
309 if bytes >= TB {
310 format!("{:>5.1} TB", bytes as f64 / TB as f64)
311 } else if bytes >= GB {
312 format!("{:>5.1} GB", bytes as f64 / GB as f64)
313 } else if bytes >= MB {
314 format!("{:>5.1} MB", bytes as f64 / MB as f64)
315 } else if bytes >= KB {
316 format!("{:>5.1} KB", bytes as f64 / KB as f64)
317 } else {
318 format!("{:>5} B", bytes)
320 }
321}
322
323#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn test_system_monitor_data_default() {
333 let d = SystemMonitorData::default();
334 assert_eq!(d.cpu_usage, 0.0);
335 assert_eq!(d.memory_used, 0);
336 assert_eq!(d.memory_total, 0);
337 assert_eq!(d.network_rx_rate, 0);
338 assert_eq!(d.network_tx_rate, 0);
339 assert!(d.last_update.is_none());
340 }
341
342 #[test]
343 fn test_format_bytes_per_sec() {
344 assert_eq!(format_bytes_per_sec(0), " 0 B/s");
345 assert_eq!(format_bytes_per_sec(512), " 512 B/s");
346 assert_eq!(format_bytes_per_sec(1024), " 1.0 KB/s");
347 assert_eq!(format_bytes_per_sec(1536), " 1.5 KB/s");
348 assert_eq!(format_bytes_per_sec(1_048_576), " 1.0 MB/s");
349 assert_eq!(format_bytes_per_sec(1_073_741_824), " 1.0 GB/s");
350 assert_eq!(
352 format_bytes_per_sec(0).len(),
353 format_bytes_per_sec(1024).len()
354 );
355 assert_eq!(
356 format_bytes_per_sec(1024).len(),
357 format_bytes_per_sec(1_048_576).len()
358 );
359 }
360
361 #[test]
362 fn test_format_memory() {
363 assert_eq!(format_memory(0, 0), " 0 B / 0 B");
364 assert_eq!(
366 format_memory(1_073_741_824, 8_589_934_592),
367 " 1.0 GB / 8.0 GB"
368 );
369 assert_eq!(
371 format_memory(536_870_912, 1_073_741_824),
372 "512.0 MB / 1.0 GB"
373 );
374 }
375
376 #[test]
377 fn test_format_bytes() {
378 assert_eq!(format_bytes(0), " 0 B");
380 assert_eq!(format_bytes(512), " 512 B");
381 assert_eq!(format_bytes(1024), " 1.0 KB");
382 assert_eq!(format_bytes(1_048_576), " 1.0 MB");
383 assert_eq!(format_bytes(1_073_741_824), " 1.0 GB");
384 assert_eq!(format_bytes(1_099_511_627_776), " 1.0 TB");
385 assert_eq!(format_bytes(250 * 1_073_741_824), "250.0 GB");
387 assert_eq!(format_bytes(0).len(), format_bytes(1024).len());
389 assert_eq!(
390 format_bytes(1024).len(),
391 format_bytes(1_099_511_627_776).len()
392 );
393 }
394
395 #[cfg(feature = "system-monitor")]
396 #[test]
397 fn test_system_monitor_start_stop() {
398 use std::time::Duration;
399
400 let monitor = SystemMonitor::new();
401 assert!(!monitor.is_running());
402
403 monitor.start(1.0);
404 assert!(monitor.is_running());
405
406 let deadline = std::time::Instant::now() + Duration::from_secs(10);
411 let mut polled = false;
412 while std::time::Instant::now() < deadline {
413 if monitor.data().last_update.is_some() {
414 polled = true;
415 break;
416 }
417 std::thread::sleep(Duration::from_millis(50));
418 }
419 assert!(
420 polled,
421 "system monitor recorded no initial poll within 10s of start()"
422 );
423
424 monitor.stop();
425 assert!(!monitor.is_running());
426 }
427}