use std::io;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
#[cfg(not(target_os = "linux"))]
use sysinfo::{get_current_pid, CpuExt, ProcessExt, System, SystemExt};
const DEFAULT_INTERVAL_MS: u64 = 1000;
const MIN_INTERVAL_MS: u64 = 50;
const MAX_SLEEP_CHUNK_MS: u64 = 1000;
#[repr(align(64))]
struct HealthInner {
system_cpu: AtomicU32,
process_cpu: AtomicU32,
system_memory_mb: AtomicU64,
process_memory_mb: AtomicU64,
load_average: AtomicU32,
thread_count: AtomicU32,
fd_count: AtomicU32,
health_score: AtomicU32,
last_update_ms: AtomicU64,
created_at: Instant,
#[cfg(target_os = "linux")]
proc_cpu_prev: AtomicU64,
#[cfg(target_os = "linux")]
proc_cpu_prev_ms: AtomicU64,
#[cfg(not(target_os = "linux"))]
sys: parking_lot::Mutex<System>,
#[cfg(not(target_os = "linux"))]
pid: Option<sysinfo::Pid>,
}
impl HealthInner {
fn new() -> Self {
Self {
system_cpu: AtomicU32::new(0),
process_cpu: AtomicU32::new(0),
system_memory_mb: AtomicU64::new(0),
process_memory_mb: AtomicU64::new(0),
load_average: AtomicU32::new(0),
thread_count: AtomicU32::new(0),
fd_count: AtomicU32::new(0),
health_score: AtomicU32::new(10000),
last_update_ms: AtomicU64::new(0),
created_at: Instant::now(),
#[cfg(target_os = "linux")]
proc_cpu_prev: AtomicU64::new(0),
#[cfg(target_os = "linux")]
proc_cpu_prev_ms: AtomicU64::new(u64::MAX),
#[cfg(not(target_os = "linux"))]
sys: parking_lot::Mutex::new(System::new()),
#[cfg(not(target_os = "linux"))]
pid: get_current_pid().ok(),
}
}
fn update_metrics(&self) {
let now_ms = self.created_at.elapsed().as_millis() as u64;
if let Ok(cpu) = self.get_system_cpu() {
self.system_cpu
.store((cpu * 100.0) as u32, Ordering::Relaxed);
}
if let Ok(memory_mb) = self.get_system_memory_mb() {
self.system_memory_mb.store(memory_mb, Ordering::Relaxed);
}
if let Ok(load) = self.get_load_average() {
self.load_average
.store((load * 100.0) as u32, Ordering::Relaxed);
}
if let Ok(cpu) = self.get_process_cpu() {
self.process_cpu
.store((cpu * 100.0) as u32, Ordering::Relaxed);
}
if let Ok(memory_mb) = self.get_process_memory_mb() {
self.process_memory_mb.store(memory_mb, Ordering::Relaxed);
}
if let Ok(threads) = self.get_thread_count() {
self.thread_count.store(threads, Ordering::Relaxed);
}
if let Ok(fds) = self.get_fd_count() {
self.fd_count.store(fds, Ordering::Relaxed);
}
let health = self.calculate_health_score();
self.health_score
.store((health * 100.0) as u32, Ordering::Relaxed);
self.last_update_ms.store(now_ms, Ordering::Relaxed);
}
fn calculate_health_score(&self) -> f64 {
let mut score: f64 = 100.0;
let system_cpu = self.system_cpu.load(Ordering::Relaxed) as f64 / 100.0;
if system_cpu > 80.0 {
score -= 30.0;
} else if system_cpu > 60.0 {
score -= 15.0;
} else if system_cpu > 40.0 {
score -= 5.0;
}
let load = self.load_average.load(Ordering::Relaxed) as f64 / 100.0;
let cpu_count = num_cpus::get() as f64;
if load > cpu_count * 2.0 {
score -= 25.0;
} else if load > cpu_count * 1.5 {
score -= 10.0;
} else if load > cpu_count {
score -= 5.0;
}
let process_cpu = self.process_cpu.load(Ordering::Relaxed) as f64 / 100.0;
if process_cpu > 50.0 {
score -= 15.0;
} else if process_cpu > 25.0 {
score -= 8.0;
}
let memory_gb = self.system_memory_mb.load(Ordering::Relaxed) as f64 / 1024.0;
if memory_gb > 16.0 {
score -= 10.0;
} else if memory_gb > 8.0 {
score -= 5.0;
}
let threads = self.thread_count.load(Ordering::Relaxed);
if threads > 1000 {
score -= 20.0;
} else if threads > 500 {
score -= 10.0;
} else if threads > 200 {
score -= 5.0;
}
let fds = self.fd_count.load(Ordering::Relaxed);
if fds > 10000 {
score -= 15.0;
} else if fds > 5000 {
score -= 8.0;
} else if fds > 1000 {
score -= 3.0;
}
score.max(0.0)
}
#[cfg(target_os = "linux")]
fn get_system_cpu(&self) -> io::Result<f64> {
let contents = std::fs::read_to_string("/proc/stat")?;
if let Some(line) = contents.lines().next() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 5 && parts[0] == "cpu" {
let user: u64 = parts[1].parse().unwrap_or(0);
let nice: u64 = parts[2].parse().unwrap_or(0);
let system: u64 = parts[3].parse().unwrap_or(0);
let idle: u64 = parts[4].parse().unwrap_or(0);
let total = user + nice + system + idle;
let used = user + nice + system;
if total > 0 {
return Ok(used as f64 / total as f64 * 100.0);
}
}
}
Ok(0.0)
}
#[cfg(not(target_os = "linux"))]
fn get_system_cpu(&self) -> io::Result<f64> {
let mut guard = self.sys.lock();
guard.refresh_cpu();
Ok(guard.global_cpu_info().cpu_usage() as f64)
}
#[cfg(target_os = "linux")]
fn get_system_memory_mb(&self) -> io::Result<u64> {
let contents = std::fs::read_to_string("/proc/meminfo")?;
let mut total_kb = 0u64;
let mut free_kb = 0u64;
let mut available_kb = 0u64;
for line in contents.lines() {
if let Some(rest) = line.strip_prefix("MemTotal:") {
total_kb = rest
.split_whitespace()
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
} else if let Some(rest) = line.strip_prefix("MemFree:") {
free_kb = rest
.split_whitespace()
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
} else if let Some(rest) = line.strip_prefix("MemAvailable:") {
available_kb = rest
.split_whitespace()
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}
}
let used_kb = if available_kb > 0 {
total_kb.saturating_sub(available_kb)
} else {
total_kb.saturating_sub(free_kb)
};
Ok(used_kb / 1024)
}
#[cfg(not(target_os = "linux"))]
fn get_system_memory_mb(&self) -> io::Result<u64> {
let mut guard = self.sys.lock();
guard.refresh_memory();
let used_kib = guard.used_memory();
Ok(used_kib / 1024)
}
#[cfg(target_os = "linux")]
fn get_load_average(&self) -> io::Result<f64> {
let contents = std::fs::read_to_string("/proc/loadavg")?;
if let Some(first) = contents.split_whitespace().next() {
return first
.parse()
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid load average"));
}
Ok(0.0)
}
#[cfg(not(target_os = "linux"))]
fn get_load_average(&self) -> io::Result<f64> {
let guard = self.sys.lock();
Ok(guard.load_average().one)
}
#[cfg(target_os = "linux")]
fn get_process_cpu(&self) -> io::Result<f64> {
let contents = std::fs::read_to_string("/proc/self/stat")?;
let parts: Vec<&str> = contents.split_whitespace().collect();
if parts.len() < 15 {
return Ok(0.0);
}
let utime: u64 = parts[13].parse().unwrap_or(0);
let stime: u64 = parts[14].parse().unwrap_or(0);
let total_ticks = utime.saturating_add(stime);
let now_ms = self.created_at.elapsed().as_millis() as u64;
let prev_ticks = self.proc_cpu_prev.load(Ordering::Relaxed);
let prev_ms = self.proc_cpu_prev_ms.load(Ordering::Relaxed);
self.proc_cpu_prev.store(total_ticks, Ordering::Relaxed);
self.proc_cpu_prev_ms.store(now_ms, Ordering::Relaxed);
if prev_ms == u64::MAX {
return Ok(0.0);
}
let elapsed_ms = now_ms.saturating_sub(prev_ms);
if elapsed_ms == 0 {
return Ok(0.0);
}
let delta_ticks = total_ticks.saturating_sub(prev_ticks) as f64;
let clk_tck: f64 = 100.0;
let elapsed_s = elapsed_ms as f64 / 1000.0;
let cores = num_cpus::get().max(1) as f64;
let pct = (delta_ticks / (clk_tck * elapsed_s * cores)) * 100.0;
Ok(pct.clamp(0.0, 100.0))
}
#[cfg(not(target_os = "linux"))]
fn get_process_cpu(&self) -> io::Result<f64> {
let mut guard = self.sys.lock();
if let Some(pid) = self.pid {
guard.refresh_process(pid);
if let Some(proc_) = guard.process(pid) {
let raw = proc_.cpu_usage() as f64;
let cores = num_cpus::get() as f64;
let norm = if cores > 0.0 { raw / cores } else { raw };
return Ok(norm.clamp(0.0, 100.0));
}
}
Ok(0.0)
}
#[cfg(target_os = "linux")]
fn get_process_memory_mb(&self) -> io::Result<u64> {
let contents = std::fs::read_to_string("/proc/self/status")?;
for line in contents.lines() {
if let Some(rest) = line.strip_prefix("VmRSS:") {
if let Some(kb) = rest
.split_whitespace()
.next()
.and_then(|s| s.parse::<u64>().ok())
{
return Ok(kb / 1024);
}
}
}
Ok(0)
}
#[cfg(not(target_os = "linux"))]
fn get_process_memory_mb(&self) -> io::Result<u64> {
let mut guard = self.sys.lock();
if let Some(pid) = self.pid {
guard.refresh_process(pid);
if let Some(proc_) = guard.process(pid) {
return Ok(proc_.memory() / 1024);
}
}
Ok(0)
}
#[cfg(target_os = "linux")]
fn get_thread_count(&self) -> io::Result<u32> {
let contents = std::fs::read_to_string("/proc/self/status")?;
for line in contents.lines() {
if let Some(rest) = line.strip_prefix("Threads:") {
if let Some(c) = rest.split_whitespace().next().and_then(|s| s.parse().ok()) {
return Ok(c);
}
}
}
Ok(1)
}
#[cfg(not(target_os = "linux"))]
fn get_thread_count(&self) -> io::Result<u32> {
Ok(1)
}
#[cfg(target_os = "linux")]
fn get_fd_count(&self) -> io::Result<u32> {
match std::fs::read_dir("/proc/self/fd") {
Ok(entries) => Ok(entries.count() as u32),
Err(_) => Ok(0),
}
}
#[cfg(not(target_os = "linux"))]
fn get_fd_count(&self) -> io::Result<u32> {
Ok(0)
}
}
#[repr(align(64))]
pub struct SystemHealth {
inner: Arc<HealthInner>,
_sampler: Option<SamplerHandle>,
update_interval_ms: u64,
}
struct SamplerHandle {
stop: Arc<AtomicBool>,
thread: Option<JoinHandle<()>>,
}
impl Drop for SamplerHandle {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(t) = self.thread.take() {
t.thread().unpark();
let _ = t.join();
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SystemSnapshot {
pub system_cpu_percent: f64,
pub process_cpu_percent: f64,
pub system_memory_mb: u64,
pub process_memory_mb: u64,
pub load_average: f64,
pub thread_count: u32,
pub fd_count: u32,
pub health_score: f64,
pub last_update: Duration,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProcessStats {
pub cpu_percent: f64,
pub memory_mb: f64,
pub threads: u32,
pub file_handles: u32,
pub uptime: Duration,
}
impl SystemHealth {
#[inline]
pub fn new() -> Self {
Self::with_interval(Duration::from_millis(DEFAULT_INTERVAL_MS))
}
#[inline]
pub fn with_interval(interval: Duration) -> Self {
let inner = Arc::new(HealthInner::new());
inner.update_metrics();
if interval.is_zero() {
return Self {
inner,
_sampler: None,
update_interval_ms: 0,
};
}
let interval_ms = (interval.as_millis() as u64).max(MIN_INTERVAL_MS);
let sampler = spawn_sampler(inner.clone(), interval_ms);
Self {
inner,
_sampler: Some(sampler),
update_interval_ms: interval_ms,
}
}
#[inline]
pub fn manual() -> Self {
Self::with_interval(Duration::ZERO)
}
#[must_use]
#[inline]
pub fn update_interval_ms(&self) -> u64 {
self.update_interval_ms
}
#[inline(always)]
pub fn cpu_used(&self) -> f64 {
self.inner.system_cpu.load(Ordering::Relaxed) as f64 / 100.0
}
#[inline]
pub fn cpu_free(&self) -> f64 {
100.0 - self.cpu_used()
}
#[inline(always)]
pub fn mem_used_mb(&self) -> f64 {
self.inner.system_memory_mb.load(Ordering::Relaxed) as f64
}
#[inline]
pub fn mem_used_gb(&self) -> f64 {
self.mem_used_mb() / 1024.0
}
#[inline(always)]
pub fn process_cpu_used(&self) -> f64 {
self.inner.process_cpu.load(Ordering::Relaxed) as f64 / 100.0
}
#[inline(always)]
pub fn process_mem_used_mb(&self) -> f64 {
self.inner.process_memory_mb.load(Ordering::Relaxed) as f64
}
#[inline(always)]
pub fn load_avg(&self) -> f64 {
self.inner.load_average.load(Ordering::Relaxed) as f64 / 100.0
}
#[inline(always)]
pub fn thread_count(&self) -> u32 {
self.inner.thread_count.load(Ordering::Relaxed)
}
#[inline(always)]
pub fn fd_count(&self) -> u32 {
self.inner.fd_count.load(Ordering::Relaxed)
}
#[inline(always)]
pub fn health_score(&self) -> f64 {
self.inner.health_score.load(Ordering::Relaxed) as f64 / 100.0
}
#[inline(always)]
pub fn quick_check(&self) -> HealthStatus {
let score = self.health_score();
if score >= 80.0 {
HealthStatus::Healthy
} else if score >= 60.0 {
HealthStatus::Warning
} else if score >= 40.0 {
HealthStatus::Degraded
} else {
HealthStatus::Critical
}
}
#[inline]
pub fn update(&self) {
self.inner.update_metrics();
}
pub fn snapshot(&self) -> SystemSnapshot {
let inner = &self.inner;
let now_ms = inner.created_at.elapsed().as_millis() as u64;
let last_ms = inner.last_update_ms.load(Ordering::Relaxed);
let last_update = Duration::from_millis(now_ms.saturating_sub(last_ms));
SystemSnapshot {
system_cpu_percent: inner.system_cpu.load(Ordering::Relaxed) as f64 / 100.0,
process_cpu_percent: inner.process_cpu.load(Ordering::Relaxed) as f64 / 100.0,
system_memory_mb: inner.system_memory_mb.load(Ordering::Relaxed),
process_memory_mb: inner.process_memory_mb.load(Ordering::Relaxed),
load_average: inner.load_average.load(Ordering::Relaxed) as f64 / 100.0,
thread_count: inner.thread_count.load(Ordering::Relaxed),
fd_count: inner.fd_count.load(Ordering::Relaxed),
health_score: inner.health_score.load(Ordering::Relaxed) as f64 / 100.0,
last_update,
}
}
pub fn process(&self) -> ProcessStats {
let inner = &self.inner;
ProcessStats {
cpu_percent: inner.process_cpu.load(Ordering::Relaxed) as f64 / 100.0,
memory_mb: inner.process_memory_mb.load(Ordering::Relaxed) as f64,
threads: inner.thread_count.load(Ordering::Relaxed),
file_handles: inner.fd_count.load(Ordering::Relaxed),
uptime: inner.created_at.elapsed(),
}
}
}
fn spawn_sampler(inner: Arc<HealthInner>, interval_ms: u64) -> SamplerHandle {
let stop = Arc::new(AtomicBool::new(false));
let stop2 = stop.clone();
let thread = thread::Builder::new()
.name("metrics-lib-health-sampler".into())
.spawn(move || run_sampler(inner, stop2, interval_ms))
.expect("spawn metrics-lib sampler thread");
SamplerHandle {
stop,
thread: Some(thread),
}
}
fn run_sampler(inner: Arc<HealthInner>, stop: Arc<AtomicBool>, interval_ms: u64) {
while !stop.load(Ordering::Relaxed) {
let chunks = interval_ms.saturating_add(MAX_SLEEP_CHUNK_MS - 1) / MAX_SLEEP_CHUNK_MS;
let chunk_ms = interval_ms.min(MAX_SLEEP_CHUNK_MS);
for _ in 0..chunks.max(1) {
if stop.load(Ordering::Relaxed) {
return;
}
thread::park_timeout(Duration::from_millis(chunk_ms));
}
if stop.load(Ordering::Relaxed) {
return;
}
inner.update_metrics();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum HealthStatus {
Healthy,
Warning,
Degraded,
Critical,
}
impl HealthStatus {
#[inline]
pub fn is_degraded(&self) -> bool {
matches!(self, Self::Degraded | Self::Critical)
}
#[inline]
pub fn is_healthy(&self) -> bool {
matches!(self, Self::Healthy)
}
#[inline]
pub fn has_issues(&self) -> bool {
!matches!(self, Self::Healthy)
}
}
impl Default for SystemHealth {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for SystemHealth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let snapshot = self.snapshot();
write!(
f,
"SystemHealth(CPU: {:.1}%, Mem: {} MB, Health: {:.1}%)",
snapshot.system_cpu_percent, snapshot.system_memory_mb, snapshot.health_score
)
}
}
impl std::fmt::Debug for SystemHealth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let snapshot = self.snapshot();
f.debug_struct("SystemHealth")
.field("system_cpu", &snapshot.system_cpu_percent)
.field("process_cpu", &snapshot.process_cpu_percent)
.field("system_memory_mb", &snapshot.system_memory_mb)
.field("process_memory_mb", &snapshot.process_memory_mb)
.field("load_average", &snapshot.load_average)
.field("threads", &snapshot.thread_count)
.field("fds", &snapshot.fd_count)
.field("health_score", &snapshot.health_score)
.field("update_interval_ms", &self.update_interval_ms)
.finish()
}
}
impl std::fmt::Display for HealthStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Healthy => write!(f, "Healthy"),
Self::Warning => write!(f, "Warning"),
Self::Degraded => write!(f, "Degraded"),
Self::Critical => write!(f, "Critical"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_basic_functionality() {
let health = SystemHealth::new();
let _cpu = health.cpu_used();
let _mem = health.mem_used_mb();
let _process_cpu = health.process_cpu_used();
let _process_mem = health.process_mem_used_mb();
let _load = health.load_avg();
let _threads = health.thread_count();
let _fds = health.fd_count();
let _score = health.health_score();
let status = health.quick_check();
assert!(matches!(
status,
HealthStatus::Healthy
| HealthStatus::Warning
| HealthStatus::Degraded
| HealthStatus::Critical
));
}
#[test]
fn test_cpu_free() {
let health = SystemHealth::new();
let used = health.cpu_used();
let free = health.cpu_free();
assert!((used + free - 100.0).abs() < 0.1);
}
#[test]
fn test_memory_units() {
let health = SystemHealth::new();
let mb = health.mem_used_mb();
let gb = health.mem_used_gb();
if mb > 0.0 {
assert!((gb * 1024.0 - mb).abs() < 1.0);
}
}
#[test]
fn test_snapshot() {
let health = SystemHealth::new();
let snapshot = health.snapshot();
assert!(snapshot.system_cpu_percent >= 0.0);
assert!(snapshot.system_cpu_percent <= 100.0);
assert!(snapshot.health_score >= 0.0);
assert!(snapshot.health_score <= 100.0);
assert!(snapshot.thread_count > 0);
}
#[test]
fn test_process_stats() {
let health = SystemHealth::new();
let stats = health.process();
assert!(stats.threads > 0);
assert!(stats.uptime > Duration::ZERO);
assert!(stats.cpu_percent >= 0.0);
assert!(stats.memory_mb >= 0.0);
}
#[test]
fn test_health_status() {
for hs in [
HealthStatus::Healthy,
HealthStatus::Warning,
HealthStatus::Degraded,
HealthStatus::Critical,
] {
let _ = format!("{hs}");
}
assert!(HealthStatus::Healthy.is_healthy());
assert!(!HealthStatus::Healthy.has_issues());
assert!(HealthStatus::Warning.has_issues());
assert!(HealthStatus::Degraded.is_degraded());
assert!(HealthStatus::Critical.is_degraded());
}
#[test]
fn test_custom_interval_floors_to_50ms() {
let health = SystemHealth::with_interval(Duration::from_millis(5));
assert!(health.update_interval_ms() >= MIN_INTERVAL_MS);
}
#[test]
fn test_background_sampler_refreshes_snapshot_after_interval() {
let health = SystemHealth::with_interval(Duration::from_millis(50));
let snap_before = health.snapshot();
assert!(snap_before.system_cpu_percent.is_finite());
thread::sleep(Duration::from_millis(250));
let snap_after = health.snapshot();
assert!(
snap_after.last_update <= Duration::from_millis(500),
"snapshot.last_update should be 'time since last sampler refresh' \
(≤ interval + slack); got {:?}",
snap_after.last_update,
);
assert!(snap_after.system_cpu_percent.is_finite());
}
#[test]
fn test_manual_mode_does_not_spawn_sampler() {
let health = SystemHealth::manual();
assert_eq!(health.update_interval_ms(), 0);
let snap = health.snapshot();
assert!(snap.system_cpu_percent >= 0.0);
health.update();
}
#[test]
fn test_force_update() {
let health = SystemHealth::new();
let score_before = health.health_score();
health.update();
let score_after = health.health_score();
assert!(score_before >= 0.0);
assert!(score_after >= 0.0);
}
#[test]
fn test_concurrent_access() {
let health = std::sync::Arc::new(SystemHealth::new());
let mut handles = vec![];
for _ in 0..10 {
let health_clone = health.clone();
let handle = thread::spawn(move || {
for _ in 0..100 {
let _cpu = health_clone.cpu_used();
let _mem = health_clone.mem_used_mb();
let _status = health_clone.quick_check();
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let final_score = health.health_score();
assert!((0.0..=100.0).contains(&final_score));
}
#[test]
fn test_display_formatting() {
let health = SystemHealth::new();
let display_str = format!("{health}");
assert!(display_str.contains("SystemHealth"));
assert!(display_str.contains("CPU"));
assert!(display_str.contains("Mem"));
let debug_str = format!("{health:?}");
assert!(debug_str.contains("SystemHealth"));
let status = health.quick_check();
let status_str = format!("{status}");
assert!(!status_str.is_empty());
}
#[test]
fn test_drop_joins_sampler_thread() {
let health = SystemHealth::with_interval(Duration::from_millis(50));
thread::sleep(Duration::from_millis(75));
drop(health);
}
}
#[cfg(all(test, feature = "bench-tests", not(tarpaulin), not(coverage)))]
#[allow(unused_imports)]
mod benchmarks {
use super::*;
use std::time::Instant;
#[cfg_attr(not(feature = "bench-tests"), ignore)]
#[test]
fn bench_quick_check() {
let health = SystemHealth::new();
let iterations = 1_000_000;
let start = Instant::now();
for _ in 0..iterations {
let _ = health.quick_check();
}
let elapsed = start.elapsed();
println!(
"SystemHealth quick_check: {:.2} ns/op",
elapsed.as_nanos() as f64 / iterations as f64
);
}
#[cfg_attr(not(feature = "bench-tests"), ignore)]
#[test]
fn bench_cached_metrics() {
let health = SystemHealth::new();
let iterations = 1_000_000;
let start = Instant::now();
for _ in 0..iterations {
let _ = health.cpu_used();
let _ = health.mem_used_mb();
let _ = health.health_score();
}
let elapsed = start.elapsed();
println!(
"SystemHealth cached metrics: {:.2} ns/op",
elapsed.as_nanos() as f64 / iterations as f64 / 3.0
);
}
#[cfg_attr(not(feature = "bench-tests"), ignore)]
#[test]
fn bench_force_update() {
let health = SystemHealth::manual();
let iterations = 1000;
let start = Instant::now();
for _ in 0..iterations {
health.update();
}
let elapsed = start.elapsed();
println!(
"SystemHealth force update: {:.2} μs/op",
elapsed.as_micros() as f64 / iterations as f64
);
}
}