1use std::sync::atomic::{AtomicU64, AtomicU32, Ordering};
15use std::time::{Duration, Instant};
16use std::io;
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.store((cpu * 100.0) as u32, Ordering::Relaxed);
262 }
263
264 if let Ok(memory_mb) = self.get_system_memory_mb() {
265 self.system_memory_mb.store(memory_mb, Ordering::Relaxed);
266 }
267
268 if let Ok(load) = self.get_load_average() {
269 self.load_average.store((load * 100.0) as u32, Ordering::Relaxed);
270 }
271
272 if let Ok(cpu) = self.get_process_cpu() {
274 self.process_cpu.store((cpu * 100.0) as u32, Ordering::Relaxed);
275 }
276
277 if let Ok(memory_mb) = self.get_process_memory_mb() {
278 self.process_memory_mb.store(memory_mb, Ordering::Relaxed);
279 }
280
281 if let Ok(threads) = self.get_thread_count() {
282 self.thread_count.store(threads, Ordering::Relaxed);
283 }
284
285 if let Ok(fds) = self.get_fd_count() {
286 self.fd_count.store(fds, Ordering::Relaxed);
287 }
288
289 let health = self.calculate_health_score();
291 self.health_score.store((health * 100.0) as u32, Ordering::Relaxed);
292
293 self.last_update.store(now_ns, Ordering::Relaxed);
294 }
295
296 fn calculate_health_score(&self) -> f64 {
297 let mut score: f64 = 100.0;
298
299 let system_cpu = self.system_cpu.load(Ordering::Relaxed) as f64 / 100.0;
301 if system_cpu > 80.0 {
302 score -= 30.0; } else if system_cpu > 60.0 {
304 score -= 15.0;
305 } else if system_cpu > 40.0 {
306 score -= 5.0;
307 }
308
309 let load = self.load_average.load(Ordering::Relaxed) as f64 / 100.0;
311 let cpu_count = num_cpus::get() as f64;
312 if load > cpu_count * 2.0 {
313 score -= 25.0;
314 } else if load > cpu_count * 1.5 {
315 score -= 10.0;
316 } else if load > cpu_count {
317 score -= 5.0;
318 }
319
320 let process_cpu = self.process_cpu.load(Ordering::Relaxed) as f64 / 100.0;
322 if process_cpu > 50.0 {
323 score -= 15.0;
324 } else if process_cpu > 25.0 {
325 score -= 8.0;
326 }
327
328 let memory_gb = self.system_memory_mb.load(Ordering::Relaxed) as f64 / 1024.0;
330 if memory_gb > 16.0 { score -= 10.0;
332 } else if memory_gb > 8.0 {
333 score -= 5.0;
334 }
335
336 let threads = self.thread_count.load(Ordering::Relaxed);
338 if threads > 1000 {
339 score -= 20.0;
340 } else if threads > 500 {
341 score -= 10.0;
342 } else if threads > 200 {
343 score -= 5.0;
344 }
345
346 let fds = self.fd_count.load(Ordering::Relaxed);
348 if fds > 10000 {
349 score -= 15.0;
350 } else if fds > 5000 {
351 score -= 8.0;
352 } else if fds > 1000 {
353 score -= 3.0;
354 }
355
356 score.max(0.0)
357 }
358
359 #[cfg(target_os = "linux")]
362 fn get_system_cpu(&self) -> io::Result<f64> {
363 let contents = std::fs::read_to_string("/proc/stat")?;
364 if let Some(line) = contents.lines().next() {
365 let parts: Vec<&str> = line.split_whitespace().collect();
366 if parts.len() >= 5 && parts[0] == "cpu" {
367 let user: u64 = parts[1].parse().unwrap_or(0);
368 let nice: u64 = parts[2].parse().unwrap_or(0);
369 let system: u64 = parts[3].parse().unwrap_or(0);
370 let idle: u64 = parts[4].parse().unwrap_or(0);
371
372 let total = user + nice + system + idle;
373 let used = user + nice + system;
374
375 if total > 0 {
376 return Ok(used as f64 / total as f64 * 100.0);
377 }
378 }
379 }
380 Ok(0.0)
381 }
382
383 #[cfg(not(target_os = "linux"))]
384 fn get_system_cpu(&self) -> io::Result<f64> {
385 Ok(0.0)
388 }
389
390 #[cfg(target_os = "linux")]
391 fn get_system_memory_mb(&self) -> io::Result<u64> {
392 let contents = std::fs::read_to_string("/proc/meminfo")?;
393 let mut total_kb = 0u64;
394 let mut free_kb = 0u64;
395 let mut available_kb = 0u64;
396
397 for line in contents.lines() {
398 if line.starts_with("MemTotal:") {
399 total_kb = line.split_whitespace().nth(1)
400 .and_then(|s| s.parse().ok())
401 .unwrap_or(0);
402 } else if line.starts_with("MemFree:") {
403 free_kb = line.split_whitespace().nth(1)
404 .and_then(|s| s.parse().ok())
405 .unwrap_or(0);
406 } else if line.starts_with("MemAvailable:") {
407 available_kb = line.split_whitespace().nth(1)
408 .and_then(|s| s.parse().ok())
409 .unwrap_or(0);
410 }
411 }
412
413 let used_kb = if available_kb > 0 {
415 total_kb - available_kb
416 } else {
417 total_kb - free_kb
418 };
419
420 Ok(used_kb / 1024) }
422
423 #[cfg(not(target_os = "linux"))]
424 fn get_system_memory_mb(&self) -> io::Result<u64> {
425 Ok(0)
426 }
427
428 #[cfg(target_os = "linux")]
429 fn get_load_average(&self) -> io::Result<f64> {
430 let contents = std::fs::read_to_string("/proc/loadavg")?;
431 if let Some(first) = contents.split_whitespace().next() {
432 return first.parse().map_err(|_| {
433 io::Error::new(io::ErrorKind::InvalidData, "Invalid load average")
434 });
435 }
436 Ok(0.0)
437 }
438
439 #[cfg(not(target_os = "linux"))]
440 fn get_load_average(&self) -> io::Result<f64> {
441 Ok(0.0)
442 }
443
444 #[cfg(target_os = "linux")]
445 fn get_process_cpu(&self) -> io::Result<f64> {
446 let contents = std::fs::read_to_string("/proc/self/stat")?;
447 let parts: Vec<&str> = contents.split_whitespace().collect();
448
449 if parts.len() >= 15 {
450 let utime: u64 = parts[13].parse().unwrap_or(0);
451 let stime: u64 = parts[14].parse().unwrap_or(0);
452 let total = utime + stime;
453
454 Ok(total as f64 * 0.01) } else {
458 Ok(0.0)
459 }
460 }
461
462 #[cfg(not(target_os = "linux"))]
463 fn get_process_cpu(&self) -> io::Result<f64> {
464 Ok(0.0)
465 }
466
467 #[cfg(target_os = "linux")]
468 fn get_process_memory_mb(&self) -> io::Result<u64> {
469 let contents = std::fs::read_to_string("/proc/self/status")?;
470 for line in contents.lines() {
471 if line.starts_with("VmRSS:") {
472 if let Some(kb_str) = line.split_whitespace().nth(1) {
473 if let Ok(kb) = kb_str.parse::<u64>() {
474 return Ok(kb / 1024); }
476 }
477 }
478 }
479 Ok(0)
480 }
481
482 #[cfg(not(target_os = "linux"))]
483 fn get_process_memory_mb(&self) -> io::Result<u64> {
484 Ok(0)
485 }
486
487 #[cfg(target_os = "linux")]
488 fn get_thread_count(&self) -> io::Result<u32> {
489 let contents = std::fs::read_to_string("/proc/self/status")?;
490 for line in contents.lines() {
491 if line.starts_with("Threads:") {
492 if let Some(count_str) = line.split_whitespace().nth(1) {
493 if let Ok(count) = count_str.parse() {
494 return Ok(count);
495 }
496 }
497 }
498 }
499 Ok(1) }
501
502 #[cfg(not(target_os = "linux"))]
503 fn get_thread_count(&self) -> io::Result<u32> {
504 Ok(1)
505 }
506
507 #[cfg(target_os = "linux")]
508 fn get_fd_count(&self) -> io::Result<u32> {
509 match std::fs::read_dir("/proc/self/fd") {
510 Ok(entries) => Ok(entries.count() as u32),
511 Err(_) => Ok(0),
512 }
513 }
514
515 #[cfg(not(target_os = "linux"))]
516 fn get_fd_count(&self) -> io::Result<u32> {
517 Ok(0)
518 }
519}
520
521#[derive(Debug, Clone, Copy, PartialEq, Eq)]
523pub enum HealthStatus {
524 Healthy,
526 Warning,
528 Degraded,
530 Critical,
532}
533
534impl HealthStatus {
535 #[inline]
537 pub fn is_degraded(&self) -> bool {
538 matches!(self, Self::Degraded | Self::Critical)
539 }
540
541 #[inline]
543 pub fn is_healthy(&self) -> bool {
544 matches!(self, Self::Healthy)
545 }
546
547 #[inline]
549 pub fn has_issues(&self) -> bool {
550 !matches!(self, Self::Healthy)
551 }
552}
553
554impl Default for SystemHealth {
555 fn default() -> Self {
556 Self::new()
557 }
558}
559
560impl std::fmt::Display for SystemHealth {
561 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562 let snapshot = self.snapshot();
563 write!(f, "SystemHealth(CPU: {:.1}%, Mem: {} MB, Health: {:.1}%)",
564 snapshot.system_cpu_percent,
565 snapshot.system_memory_mb,
566 snapshot.health_score)
567 }
568}
569
570impl std::fmt::Debug for SystemHealth {
571 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
572 let snapshot = self.snapshot();
573 f.debug_struct("SystemHealth")
574 .field("system_cpu", &snapshot.system_cpu_percent)
575 .field("process_cpu", &snapshot.process_cpu_percent)
576 .field("system_memory_mb", &snapshot.system_memory_mb)
577 .field("process_memory_mb", &snapshot.process_memory_mb)
578 .field("load_average", &snapshot.load_average)
579 .field("threads", &snapshot.thread_count)
580 .field("fds", &snapshot.fd_count)
581 .field("health_score", &snapshot.health_score)
582 .finish()
583 }
584}
585
586impl std::fmt::Display for HealthStatus {
587 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
588 match self {
589 Self::Healthy => write!(f, "Healthy"),
590 Self::Warning => write!(f, "Warning"),
591 Self::Degraded => write!(f, "Degraded"),
592 Self::Critical => write!(f, "Critical"),
593 }
594 }
595}
596
597unsafe impl Send for SystemHealth {}
599unsafe impl Sync for SystemHealth {}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604 use std::thread;
605
606 #[test]
607 fn test_basic_functionality() {
608 let health = SystemHealth::new();
609
610 let _cpu = health.cpu_used();
612 let _mem = health.mem_used_mb();
613 let _process_cpu = health.process_cpu_used();
614 let _process_mem = health.process_mem_used_mb();
615 let _load = health.load_avg();
616 let _threads = health.thread_count();
617 let _fds = health.fd_count();
618 let _score = health.health_score();
619
620 let status = health.quick_check();
622 assert!(matches!(status, HealthStatus::Healthy | HealthStatus::Warning | HealthStatus::Degraded | HealthStatus::Critical));
623 }
624
625 #[test]
626 fn test_cpu_free() {
627 let health = SystemHealth::new();
628
629 let used = health.cpu_used();
630 let free = health.cpu_free();
631
632 assert!((used + free - 100.0).abs() < 0.1);
634 }
635
636 #[test]
637 fn test_memory_units() {
638 let health = SystemHealth::new();
639
640 let mb = health.mem_used_mb();
641 let gb = health.mem_used_gb();
642
643 if mb > 0.0 {
645 assert!((gb * 1024.0 - mb).abs() < 1.0);
646 }
647 }
648
649 #[test]
650 fn test_snapshot() {
651 let health = SystemHealth::new();
652
653 let snapshot = health.snapshot();
654
655 assert!(snapshot.system_cpu_percent >= 0.0);
657 assert!(snapshot.system_cpu_percent <= 100.0);
658 assert!(snapshot.health_score >= 0.0);
659 assert!(snapshot.health_score <= 100.0);
660 assert!(snapshot.thread_count > 0); }
662
663 #[test]
664 fn test_process_stats() {
665 let health = SystemHealth::new();
666
667 let stats = health.process();
668
669 assert!(stats.threads > 0); assert!(stats.uptime > Duration::ZERO);
671 assert!(stats.cpu_percent >= 0.0);
672 assert!(stats.memory_mb >= 0.0);
673 }
674
675 #[test]
676 fn test_health_status() {
677 let healthy = HealthStatus::Healthy;
678 let warning = HealthStatus::Warning;
679 let degraded = HealthStatus::Degraded;
680 let critical = HealthStatus::Critical;
681
682 assert!(healthy.is_healthy());
683 assert!(!healthy.is_degraded());
684 assert!(!healthy.has_issues());
685
686 assert!(!warning.is_healthy());
687 assert!(!warning.is_degraded());
688 assert!(warning.has_issues());
689
690 assert!(!degraded.is_healthy());
691 assert!(degraded.is_degraded());
692 assert!(degraded.has_issues());
693
694 assert!(!critical.is_healthy());
695 assert!(critical.is_degraded());
696 assert!(critical.has_issues());
697 }
698
699 #[test]
700 fn test_custom_interval() {
701 let health = SystemHealth::with_interval(Duration::from_millis(500));
702
703 let _cpu = health.cpu_used();
705 let _score = health.health_score();
706 }
707
708 #[test]
709 fn test_force_update() {
710 let health = SystemHealth::new();
711
712 let score_before = health.health_score();
713
714 health.update();
716
717 let score_after = health.health_score();
718
719 assert!(score_before >= 0.0);
721 assert!(score_after >= 0.0);
722 }
723
724 #[test]
725 fn test_concurrent_access() {
726 let health = std::sync::Arc::new(SystemHealth::new());
727 let mut handles = vec![];
728
729 for _ in 0..10 {
731 let health_clone = health.clone();
732 let handle = thread::spawn(move || {
733 for _ in 0..100 {
734 let _cpu = health_clone.cpu_used();
735 let _mem = health_clone.mem_used_mb();
736 let _status = health_clone.quick_check();
737 }
738 });
739 handles.push(handle);
740 }
741
742 for handle in handles {
744 handle.join().unwrap();
745 }
746
747 let final_score = health.health_score();
749 assert!(final_score >= 0.0 && final_score <= 100.0);
750 }
751
752 #[test]
753 fn test_display_formatting() {
754 let health = SystemHealth::new();
755
756 let display_str = format!("{}", health);
757 assert!(display_str.contains("SystemHealth"));
758 assert!(display_str.contains("CPU"));
759 assert!(display_str.contains("Mem"));
760
761 let debug_str = format!("{:?}", health);
762 assert!(debug_str.contains("SystemHealth"));
763
764 let status = health.quick_check();
765 let status_str = format!("{}", status);
766 assert!(!status_str.is_empty());
767 }
768}
769
770#[cfg(test)]
771mod benchmarks {
772 use super::*;
773 use std::time::Instant;
774
775 #[test]
776 fn bench_quick_check() {
777 let health = SystemHealth::new();
778 let iterations = 1_000_000;
779
780 let start = Instant::now();
781 for _ in 0..iterations {
782 let _ = health.quick_check();
783 }
784 let elapsed = start.elapsed();
785
786 println!("SystemHealth quick_check: {:.2} ns/op",
787 elapsed.as_nanos() as f64 / iterations as f64);
788
789 assert!(elapsed.as_nanos() / iterations < 200);
791 }
792
793 #[test]
794 fn bench_cached_metrics() {
795 let health = SystemHealth::new();
796 let iterations = 1_000_000;
797
798 let start = Instant::now();
799 for _ in 0..iterations {
800 let _ = health.cpu_used();
801 let _ = health.mem_used_mb();
802 let _ = health.health_score();
803 }
804 let elapsed = start.elapsed();
805
806 println!("SystemHealth cached metrics: {:.2} ns/op",
807 elapsed.as_nanos() as f64 / iterations as f64 / 3.0);
808
809 assert!(elapsed.as_nanos() / iterations < 1000);
811 }
812
813 #[test]
814 fn bench_force_update() {
815 let health = SystemHealth::new();
816 let iterations = 1000; let start = Instant::now();
819 for _ in 0..iterations {
820 health.update();
821 }
822 let elapsed = start.elapsed();
823
824 println!("SystemHealth force update: {:.2} μs/op",
825 elapsed.as_micros() as f64 / iterations as f64);
826
827 assert!(elapsed.as_millis() < 2000);
829 }
830}