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
298#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn test_system_monitor_data_default() {
308 let d = SystemMonitorData::default();
309 assert_eq!(d.cpu_usage, 0.0);
310 assert_eq!(d.memory_used, 0);
311 assert_eq!(d.memory_total, 0);
312 assert_eq!(d.network_rx_rate, 0);
313 assert_eq!(d.network_tx_rate, 0);
314 assert!(d.last_update.is_none());
315 }
316
317 #[test]
318 fn test_format_bytes_per_sec() {
319 assert_eq!(format_bytes_per_sec(0), " 0 B/s");
320 assert_eq!(format_bytes_per_sec(512), " 512 B/s");
321 assert_eq!(format_bytes_per_sec(1024), " 1.0 KB/s");
322 assert_eq!(format_bytes_per_sec(1536), " 1.5 KB/s");
323 assert_eq!(format_bytes_per_sec(1_048_576), " 1.0 MB/s");
324 assert_eq!(format_bytes_per_sec(1_073_741_824), " 1.0 GB/s");
325 assert_eq!(
327 format_bytes_per_sec(0).len(),
328 format_bytes_per_sec(1024).len()
329 );
330 assert_eq!(
331 format_bytes_per_sec(1024).len(),
332 format_bytes_per_sec(1_048_576).len()
333 );
334 }
335
336 #[test]
337 fn test_format_memory() {
338 assert_eq!(format_memory(0, 0), " 0 B / 0 B");
339 assert_eq!(
341 format_memory(1_073_741_824, 8_589_934_592),
342 " 1.0 GB / 8.0 GB"
343 );
344 assert_eq!(
346 format_memory(536_870_912, 1_073_741_824),
347 "512.0 MB / 1.0 GB"
348 );
349 }
350
351 #[cfg(feature = "system-monitor")]
352 #[test]
353 fn test_system_monitor_start_stop() {
354 use std::time::Duration;
355
356 let monitor = SystemMonitor::new();
357 assert!(!monitor.is_running());
358
359 monitor.start(1.0);
360 assert!(monitor.is_running());
361
362 let deadline = std::time::Instant::now() + Duration::from_secs(10);
367 let mut polled = false;
368 while std::time::Instant::now() < deadline {
369 if monitor.data().last_update.is_some() {
370 polled = true;
371 break;
372 }
373 std::thread::sleep(Duration::from_millis(50));
374 }
375 assert!(
376 polled,
377 "system monitor recorded no initial poll within 10s of start()"
378 );
379
380 monitor.stop();
381 assert!(!monitor.is_running());
382 }
383}