1use std::io;
15use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
16use std::time::{Duration, Instant};
17
18#[repr(align(64))]
23pub struct SystemHealth {
24 system_cpu: AtomicU32,
26 process_cpu: AtomicU32,
28 system_memory_mb: AtomicU64,
30 process_memory_mb: AtomicU64,
32 load_average: AtomicU32,
34 thread_count: AtomicU32,
36 fd_count: AtomicU32,
38 health_score: AtomicU32,
40 last_update: AtomicU64,
42 update_interval_ms: u64,
44 created_at: Instant,
46}
47
48#[derive(Debug, Clone)]
50pub struct SystemSnapshot {
51 pub system_cpu_percent: f64,
53 pub process_cpu_percent: f64,
55 pub system_memory_mb: u64,
57 pub process_memory_mb: u64,
59 pub load_average: f64,
61 pub thread_count: u32,
63 pub fd_count: u32,
65 pub health_score: f64,
67 pub last_update: Duration,
69}
70
71#[derive(Debug, Clone)]
73pub struct ProcessStats {
74 pub cpu_percent: f64,
76 pub memory_mb: f64,
78 pub threads: u32,
80 pub file_handles: u32,
82 pub uptime: Duration,
84}
85
86impl SystemHealth {
87 #[inline]
89 pub fn new() -> Self {
90 let instance = Self {
91 system_cpu: AtomicU32::new(0),
92 process_cpu: AtomicU32::new(0),
93 system_memory_mb: AtomicU64::new(0),
94 process_memory_mb: AtomicU64::new(0),
95 load_average: AtomicU32::new(0),
96 thread_count: AtomicU32::new(0),
97 fd_count: AtomicU32::new(0),
98 health_score: AtomicU32::new(10000), last_update: AtomicU64::new(0),
100 update_interval_ms: 1000, created_at: Instant::now(),
102 };
103
104 instance.update_metrics();
106 instance
107 }
108
109 #[inline]
111 pub fn with_interval(interval: Duration) -> Self {
112 let mut instance = Self::new();
113 instance.update_interval_ms = interval.as_millis() as u64;
114 instance
115 }
116
117 #[inline]
119 pub fn cpu_used(&self) -> f64 {
120 self.maybe_update();
121 self.system_cpu.load(Ordering::Relaxed) as f64 / 100.0
122 }
123
124 #[inline]
126 pub fn cpu_free(&self) -> f64 {
127 100.0 - self.cpu_used()
128 }
129
130 #[inline]
132 pub fn mem_used_mb(&self) -> f64 {
133 self.maybe_update();
134 self.system_memory_mb.load(Ordering::Relaxed) as f64
135 }
136
137 #[inline]
139 pub fn mem_used_gb(&self) -> f64 {
140 self.mem_used_mb() / 1024.0
141 }
142
143 #[inline]
145 pub fn process_cpu_used(&self) -> f64 {
146 self.maybe_update();
147 self.process_cpu.load(Ordering::Relaxed) as f64 / 100.0
148 }
149
150 #[inline]
152 pub fn process_mem_used_mb(&self) -> f64 {
153 self.maybe_update();
154 self.process_memory_mb.load(Ordering::Relaxed) as f64
155 }
156
157 #[inline]
159 pub fn load_avg(&self) -> f64 {
160 self.maybe_update();
161 self.load_average.load(Ordering::Relaxed) as f64 / 100.0
162 }
163
164 #[inline]
166 pub fn thread_count(&self) -> u32 {
167 self.maybe_update();
168 self.thread_count.load(Ordering::Relaxed)
169 }
170
171 #[inline]
173 pub fn fd_count(&self) -> u32 {
174 self.maybe_update();
175 self.fd_count.load(Ordering::Relaxed)
176 }
177
178 #[inline]
180 pub fn health_score(&self) -> f64 {
181 self.maybe_update();
182 self.health_score.load(Ordering::Relaxed) as f64 / 100.0
183 }
184
185 #[inline(always)]
187 pub fn quick_check(&self) -> HealthStatus {
188 let score = self.health_score();
189
190 if score >= 80.0 {
191 HealthStatus::Healthy
192 } else if score >= 60.0 {
193 HealthStatus::Warning
194 } else if score >= 40.0 {
195 HealthStatus::Degraded
196 } else {
197 HealthStatus::Critical
198 }
199 }
200
201 #[inline]
203 pub fn update(&self) {
204 self.update_metrics();
205 }
206
207 pub fn snapshot(&self) -> SystemSnapshot {
209 self.maybe_update();
210
211 let last_update_ns = self.last_update.load(Ordering::Relaxed);
212 let last_update = if last_update_ns > 0 {
213 Duration::from_nanos(last_update_ns)
214 } else {
215 Duration::ZERO
216 };
217
218 SystemSnapshot {
219 system_cpu_percent: self.system_cpu.load(Ordering::Relaxed) as f64 / 100.0,
220 process_cpu_percent: self.process_cpu.load(Ordering::Relaxed) as f64 / 100.0,
221 system_memory_mb: self.system_memory_mb.load(Ordering::Relaxed),
222 process_memory_mb: self.process_memory_mb.load(Ordering::Relaxed),
223 load_average: self.load_average.load(Ordering::Relaxed) as f64 / 100.0,
224 thread_count: self.thread_count.load(Ordering::Relaxed),
225 fd_count: self.fd_count.load(Ordering::Relaxed),
226 health_score: self.health_score.load(Ordering::Relaxed) as f64 / 100.0,
227 last_update,
228 }
229 }
230
231 pub fn process(&self) -> ProcessStats {
233 self.maybe_update();
234
235 ProcessStats {
236 cpu_percent: self.process_cpu.load(Ordering::Relaxed) as f64 / 100.0,
237 memory_mb: self.process_memory_mb.load(Ordering::Relaxed) as f64,
238 threads: self.thread_count.load(Ordering::Relaxed),
239 file_handles: self.fd_count.load(Ordering::Relaxed),
240 uptime: self.created_at.elapsed(),
241 }
242 }
243
244 #[inline]
247 fn maybe_update(&self) {
248 let now = self.created_at.elapsed().as_millis() as u64;
249 let last_update = self.last_update.load(Ordering::Relaxed);
250
251 if now >= last_update && (now - last_update) > self.update_interval_ms {
252 self.update_metrics();
253 }
254 }
255
256 fn update_metrics(&self) {
257 let now_ns = self.created_at.elapsed().as_nanos() as u64;
258
259 if let Ok(cpu) = self.get_system_cpu() {
261 self.system_cpu
262 .store((cpu * 100.0) as u32, Ordering::Relaxed);
263 }
264
265 if let Ok(memory_mb) = self.get_system_memory_mb() {
266 self.system_memory_mb.store(memory_mb, Ordering::Relaxed);
267 }
268
269 if let Ok(load) = self.get_load_average() {
270 self.load_average
271 .store((load * 100.0) as u32, Ordering::Relaxed);
272 }
273
274 if let Ok(cpu) = self.get_process_cpu() {
276 self.process_cpu
277 .store((cpu * 100.0) as u32, Ordering::Relaxed);
278 }
279
280 if let Ok(memory_mb) = self.get_process_memory_mb() {
281 self.process_memory_mb.store(memory_mb, Ordering::Relaxed);
282 }
283
284 if let Ok(threads) = self.get_thread_count() {
285 self.thread_count.store(threads, Ordering::Relaxed);
286 }
287
288 if let Ok(fds) = self.get_fd_count() {
289 self.fd_count.store(fds, Ordering::Relaxed);
290 }
291
292 let health = self.calculate_health_score();
294 self.health_score
295 .store((health * 100.0) as u32, Ordering::Relaxed);
296
297 self.last_update.store(now_ns, Ordering::Relaxed);
298 }
299
300 fn calculate_health_score(&self) -> f64 {
301 let mut score: f64 = 100.0;
302
303 let system_cpu = self.system_cpu.load(Ordering::Relaxed) as f64 / 100.0;
305 if system_cpu > 80.0 {
306 score -= 30.0; } else if system_cpu > 60.0 {
308 score -= 15.0;
309 } else if system_cpu > 40.0 {
310 score -= 5.0;
311 }
312
313 let load = self.load_average.load(Ordering::Relaxed) as f64 / 100.0;
315 let cpu_count = num_cpus::get() as f64;
316 if load > cpu_count * 2.0 {
317 score -= 25.0;
318 } else if load > cpu_count * 1.5 {
319 score -= 10.0;
320 } else if load > cpu_count {
321 score -= 5.0;
322 }
323
324 let process_cpu = self.process_cpu.load(Ordering::Relaxed) as f64 / 100.0;
326 if process_cpu > 50.0 {
327 score -= 15.0;
328 } else if process_cpu > 25.0 {
329 score -= 8.0;
330 }
331
332 let memory_gb = self.system_memory_mb.load(Ordering::Relaxed) as f64 / 1024.0;
334 if memory_gb > 16.0 {
335 score -= 10.0;
337 } else if memory_gb > 8.0 {
338 score -= 5.0;
339 }
340
341 let threads = self.thread_count.load(Ordering::Relaxed);
343 if threads > 1000 {
344 score -= 20.0;
345 } else if threads > 500 {
346 score -= 10.0;
347 } else if threads > 200 {
348 score -= 5.0;
349 }
350
351 let fds = self.fd_count.load(Ordering::Relaxed);
353 if fds > 10000 {
354 score -= 15.0;
355 } else if fds > 5000 {
356 score -= 8.0;
357 } else if fds > 1000 {
358 score -= 3.0;
359 }
360
361 score.max(0.0)
362 }
363
364 #[cfg(target_os = "linux")]
367 fn get_system_cpu(&self) -> io::Result<f64> {
368 let contents = std::fs::read_to_string("/proc/stat")?;
369 if let Some(line) = contents.lines().next() {
370 let parts: Vec<&str> = line.split_whitespace().collect();
371 if parts.len() >= 5 && parts[0] == "cpu" {
372 let user: u64 = parts[1].parse().unwrap_or(0);
373 let nice: u64 = parts[2].parse().unwrap_or(0);
374 let system: u64 = parts[3].parse().unwrap_or(0);
375 let idle: u64 = parts[4].parse().unwrap_or(0);
376
377 let total = user + nice + system + idle;
378 let used = user + nice + system;
379
380 if total > 0 {
381 return Ok(used as f64 / total as f64 * 100.0);
382 }
383 }
384 }
385 Ok(0.0)
386 }
387
388 #[cfg(not(target_os = "linux"))]
389 fn get_system_cpu(&self) -> io::Result<f64> {
390 Ok(0.0)
393 }
394
395 #[cfg(target_os = "linux")]
396 fn get_system_memory_mb(&self) -> io::Result<u64> {
397 let contents = std::fs::read_to_string("/proc/meminfo")?;
398 let mut total_kb = 0u64;
399 let mut free_kb = 0u64;
400 let mut available_kb = 0u64;
401
402 for line in contents.lines() {
403 if line.starts_with("MemTotal:") {
404 total_kb = line
405 .split_whitespace()
406 .nth(1)
407 .and_then(|s| s.parse().ok())
408 .unwrap_or(0);
409 } else if line.starts_with("MemFree:") {
410 free_kb = line
411 .split_whitespace()
412 .nth(1)
413 .and_then(|s| s.parse().ok())
414 .unwrap_or(0);
415 } else if line.starts_with("MemAvailable:") {
416 available_kb = line
417 .split_whitespace()
418 .nth(1)
419 .and_then(|s| s.parse().ok())
420 .unwrap_or(0);
421 }
422 }
423
424 let used_kb = if available_kb > 0 {
426 total_kb - available_kb
427 } else {
428 total_kb - free_kb
429 };
430
431 Ok(used_kb / 1024) }
433
434 #[cfg(not(target_os = "linux"))]
435 fn get_system_memory_mb(&self) -> io::Result<u64> {
436 Ok(0)
437 }
438
439 #[cfg(target_os = "linux")]
440 fn get_load_average(&self) -> io::Result<f64> {
441 let contents = std::fs::read_to_string("/proc/loadavg")?;
442 if let Some(first) = contents.split_whitespace().next() {
443 return first
444 .parse()
445 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid load average"));
446 }
447 Ok(0.0)
448 }
449
450 #[cfg(not(target_os = "linux"))]
451 fn get_load_average(&self) -> io::Result<f64> {
452 Ok(0.0)
453 }
454
455 #[cfg(target_os = "linux")]
456 fn get_process_cpu(&self) -> io::Result<f64> {
457 let contents = std::fs::read_to_string("/proc/self/stat")?;
458 let parts: Vec<&str> = contents.split_whitespace().collect();
459
460 if parts.len() >= 15 {
461 let utime: u64 = parts[13].parse().unwrap_or(0);
462 let stime: u64 = parts[14].parse().unwrap_or(0);
463 let total = utime + stime;
464
465 Ok(total as f64 * 0.01) } else {
469 Ok(0.0)
470 }
471 }
472
473 #[cfg(not(target_os = "linux"))]
474 fn get_process_cpu(&self) -> io::Result<f64> {
475 Ok(0.0)
476 }
477
478 #[cfg(target_os = "linux")]
479 fn get_process_memory_mb(&self) -> io::Result<u64> {
480 let contents = std::fs::read_to_string("/proc/self/status")?;
481 for line in contents.lines() {
482 if line.starts_with("VmRSS:") {
483 if let Some(kb_str) = line.split_whitespace().nth(1) {
484 if let Ok(kb) = kb_str.parse::<u64>() {
485 return Ok(kb / 1024); }
487 }
488 }
489 }
490 Ok(0)
491 }
492
493 #[cfg(not(target_os = "linux"))]
494 fn get_process_memory_mb(&self) -> io::Result<u64> {
495 Ok(0)
496 }
497
498 #[cfg(target_os = "linux")]
499 fn get_thread_count(&self) -> io::Result<u32> {
500 let contents = std::fs::read_to_string("/proc/self/status")?;
501 for line in contents.lines() {
502 if line.starts_with("Threads:") {
503 if let Some(count_str) = line.split_whitespace().nth(1) {
504 if let Ok(count) = count_str.parse() {
505 return Ok(count);
506 }
507 }
508 }
509 }
510 Ok(1) }
512
513 #[cfg(not(target_os = "linux"))]
514 fn get_thread_count(&self) -> io::Result<u32> {
515 Ok(1)
516 }
517
518 #[cfg(target_os = "linux")]
519 fn get_fd_count(&self) -> io::Result<u32> {
520 match std::fs::read_dir("/proc/self/fd") {
521 Ok(entries) => Ok(entries.count() as u32),
522 Err(_) => Ok(0),
523 }
524 }
525
526 #[cfg(not(target_os = "linux"))]
527 fn get_fd_count(&self) -> io::Result<u32> {
528 Ok(0)
529 }
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq)]
534pub enum HealthStatus {
535 Healthy,
537 Warning,
539 Degraded,
541 Critical,
543}
544
545impl HealthStatus {
546 #[inline]
548 pub fn is_degraded(&self) -> bool {
549 matches!(self, Self::Degraded | Self::Critical)
550 }
551
552 #[inline]
554 pub fn is_healthy(&self) -> bool {
555 matches!(self, Self::Healthy)
556 }
557
558 #[inline]
560 pub fn has_issues(&self) -> bool {
561 !matches!(self, Self::Healthy)
562 }
563}
564
565impl Default for SystemHealth {
566 fn default() -> Self {
567 Self::new()
568 }
569}
570
571impl std::fmt::Display for SystemHealth {
572 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
573 let snapshot = self.snapshot();
574 write!(
575 f,
576 "SystemHealth(CPU: {:.1}%, Mem: {} MB, Health: {:.1}%)",
577 snapshot.system_cpu_percent, snapshot.system_memory_mb, snapshot.health_score
578 )
579 }
580}
581
582impl std::fmt::Debug for SystemHealth {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 let snapshot = self.snapshot();
585 f.debug_struct("SystemHealth")
586 .field("system_cpu", &snapshot.system_cpu_percent)
587 .field("process_cpu", &snapshot.process_cpu_percent)
588 .field("system_memory_mb", &snapshot.system_memory_mb)
589 .field("process_memory_mb", &snapshot.process_memory_mb)
590 .field("load_average", &snapshot.load_average)
591 .field("threads", &snapshot.thread_count)
592 .field("fds", &snapshot.fd_count)
593 .field("health_score", &snapshot.health_score)
594 .finish()
595 }
596}
597
598impl std::fmt::Display for HealthStatus {
599 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600 match self {
601 Self::Healthy => write!(f, "Healthy"),
602 Self::Warning => write!(f, "Warning"),
603 Self::Degraded => write!(f, "Degraded"),
604 Self::Critical => write!(f, "Critical"),
605 }
606 }
607}
608
609unsafe impl Send for SystemHealth {}
611unsafe impl Sync for SystemHealth {}
612
613#[cfg(test)]
614mod tests {
615 use super::*;
616 use std::thread;
617
618 #[test]
619 fn test_basic_functionality() {
620 let health = SystemHealth::new();
621
622 let _cpu = health.cpu_used();
624 let _mem = health.mem_used_mb();
625 let _process_cpu = health.process_cpu_used();
626 let _process_mem = health.process_mem_used_mb();
627 let _load = health.load_avg();
628 let _threads = health.thread_count();
629 let _fds = health.fd_count();
630 let _score = health.health_score();
631
632 let status = health.quick_check();
634 assert!(matches!(
635 status,
636 HealthStatus::Healthy
637 | HealthStatus::Warning
638 | HealthStatus::Degraded
639 | HealthStatus::Critical
640 ));
641 }
642
643 #[test]
644 fn test_cpu_free() {
645 let health = SystemHealth::new();
646
647 let used = health.cpu_used();
648 let free = health.cpu_free();
649
650 assert!((used + free - 100.0).abs() < 0.1);
652 }
653
654 #[test]
655 fn test_memory_units() {
656 let health = SystemHealth::new();
657
658 let mb = health.mem_used_mb();
659 let gb = health.mem_used_gb();
660
661 if mb > 0.0 {
663 assert!((gb * 1024.0 - mb).abs() < 1.0);
664 }
665 }
666
667 #[test]
668 fn test_snapshot() {
669 let health = SystemHealth::new();
670
671 let snapshot = health.snapshot();
672
673 assert!(snapshot.system_cpu_percent >= 0.0);
675 assert!(snapshot.system_cpu_percent <= 100.0);
676 assert!(snapshot.health_score >= 0.0);
677 assert!(snapshot.health_score <= 100.0);
678 assert!(snapshot.thread_count > 0); }
680
681 #[test]
682 fn test_process_stats() {
683 let health = SystemHealth::new();
684
685 let stats = health.process();
686
687 assert!(stats.threads > 0); assert!(stats.uptime > Duration::ZERO);
689 assert!(stats.cpu_percent >= 0.0);
690 assert!(stats.memory_mb >= 0.0);
691 }
692
693 #[test]
694 fn test_health_status() {
695 let healthy = HealthStatus::Healthy;
696 let warning = HealthStatus::Warning;
697 let degraded = HealthStatus::Degraded;
698 let critical = HealthStatus::Critical;
699
700 assert!(healthy.is_healthy());
701 assert!(!healthy.is_degraded());
702 assert!(!healthy.has_issues());
703
704 assert!(!warning.is_healthy());
705 assert!(!warning.is_degraded());
706 assert!(warning.has_issues());
707
708 assert!(!degraded.is_healthy());
709 assert!(degraded.is_degraded());
710 assert!(degraded.has_issues());
711
712 assert!(!critical.is_healthy());
713 assert!(critical.is_degraded());
714 assert!(critical.has_issues());
715 }
716
717 #[test]
718 fn test_custom_interval() {
719 let health = SystemHealth::with_interval(Duration::from_millis(500));
720
721 let _cpu = health.cpu_used();
723 let _score = health.health_score();
724 }
725
726 #[test]
727 fn test_force_update() {
728 let health = SystemHealth::new();
729
730 let score_before = health.health_score();
731
732 health.update();
734
735 let score_after = health.health_score();
736
737 assert!(score_before >= 0.0);
739 assert!(score_after >= 0.0);
740 }
741
742 #[test]
743 fn test_concurrent_access() {
744 let health = std::sync::Arc::new(SystemHealth::new());
745 let mut handles = vec![];
746
747 for _ in 0..10 {
749 let health_clone = health.clone();
750 let handle = thread::spawn(move || {
751 for _ in 0..100 {
752 let _cpu = health_clone.cpu_used();
753 let _mem = health_clone.mem_used_mb();
754 let _status = health_clone.quick_check();
755 }
756 });
757 handles.push(handle);
758 }
759
760 for handle in handles {
762 handle.join().unwrap();
763 }
764
765 let final_score = health.health_score();
767 assert!((0.0..=100.0).contains(&final_score));
768 }
769
770 #[test]
771 fn test_display_formatting() {
772 let health = SystemHealth::new();
773
774 let display_str = format!("{health}");
775 assert!(display_str.contains("SystemHealth"));
776 assert!(display_str.contains("CPU"));
777 assert!(display_str.contains("Mem"));
778
779 let debug_str = format!("{health:?}");
780 assert!(debug_str.contains("SystemHealth"));
781
782 let status = health.quick_check();
783 let status_str = format!("{status}");
784 assert!(!status_str.is_empty());
785 }
786}
787
788#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
789#[allow(unused_imports)]
790mod benchmarks {
791 use super::*;
792 use std::time::Instant;
793
794 #[cfg_attr(not(feature = "bench-tests"), ignore)]
795 #[test]
796 fn bench_quick_check() {
797 let health = SystemHealth::new();
798 let iterations = 1_000_000;
799
800 let start = Instant::now();
801 for _ in 0..iterations {
802 let _ = health.quick_check();
803 }
804 let elapsed = start.elapsed();
805
806 println!(
807 "SystemHealth quick_check: {:.2} ns/op",
808 elapsed.as_nanos() as f64 / iterations as f64
809 );
810
811 assert!(elapsed.as_nanos() / iterations < 200);
813 }
814
815 #[cfg_attr(not(feature = "bench-tests"), ignore)]
816 #[test]
817 fn bench_cached_metrics() {
818 let health = SystemHealth::new();
819 let iterations = 1_000_000;
820
821 let start = Instant::now();
822 for _ in 0..iterations {
823 let _ = health.cpu_used();
824 let _ = health.mem_used_mb();
825 let _ = health.health_score();
826 }
827 let elapsed = start.elapsed();
828
829 println!(
830 "SystemHealth cached metrics: {:.2} ns/op",
831 elapsed.as_nanos() as f64 / iterations as f64 / 3.0
832 );
833
834 assert!(elapsed.as_nanos() / iterations < 1000);
836 }
837
838 #[cfg_attr(not(feature = "bench-tests"), ignore)]
839 #[test]
840 fn bench_force_update() {
841 let health = SystemHealth::new();
842 let iterations = 1000; let start = Instant::now();
845 for _ in 0..iterations {
846 health.update();
847 }
848 let elapsed = start.elapsed();
849
850 println!(
851 "SystemHealth force update: {:.2} μs/op",
852 elapsed.as_micros() as f64 / iterations as f64
853 );
854
855 assert!(elapsed.as_millis() < 2000);
857 }
858}