use once_cell::sync::Lazy;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use sysinfo::{Pid, ProcessesToUpdate, System};
struct CachedStats {
stats: LiveStats,
cached_at_mono: Instant,
}
static STATS_CACHE: Lazy<RwLock<Option<CachedStats>>> = Lazy::new(|| RwLock::new(None));
const MIN_REFRESH_INTERVAL_SECS: u64 = 5;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiveStats {
pub timestamp: u64,
pub cpu_usage: f32,
pub total_memory_bytes: u64,
pub used_memory_bytes: u64,
pub available_memory_bytes: u64,
pub memory_usage_percent: f32,
pub total_swap_bytes: u64,
pub used_swap_bytes: u64,
pub uptime: u64,
pub cpu_count: usize,
pub load_average_1: Option<f64>,
pub load_average_5: Option<f64>,
pub load_average_15: Option<f64>,
pub process_cpu_usage: f32,
pub process_memory_bytes: u64,
pub process_memory_percent: f32,
pub process_virtual_memory_bytes: u64,
pub process_start_time_since_boot_secs: u64,
pub process_uptime: u64,
pub process_pid: u32,
}
impl LiveStats {
pub fn get() -> Result<Self, Box<dyn std::error::Error>> {
let now_mono = Instant::now();
{
let cache = STATS_CACHE.read();
if let Some(cached) = cache.as_ref() {
if now_mono.duration_since(cached.cached_at_mono).as_secs() < MIN_REFRESH_INTERVAL_SECS {
return Ok(cached.stats.clone());
}
}
}
let stats = Self::capture()?;
{
let mut cache = STATS_CACHE.write();
*cache = Some(CachedStats {
stats: stats.clone(),
cached_at_mono: now_mono,
});
}
Ok(stats)
}
pub fn capture() -> Result<Self, Box<dyn std::error::Error>> {
let mut sys = System::new();
sys.refresh_memory();
sys.refresh_cpu_all();
std::thread::sleep(Duration::from_millis(100));
sys.refresh_cpu_all();
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)?
.as_secs();
let total_memory_kib = sys.total_memory();
let used_memory_kib = sys.used_memory();
let available_memory_kib = sys.available_memory();
let total_memory_bytes = total_memory_kib * 1024;
let used_memory_bytes = used_memory_kib * 1024;
let available_memory_bytes = available_memory_kib * 1024;
let memory_usage_percent = if total_memory_kib > 0 {
(used_memory_kib as f32 / total_memory_kib as f32) * 100.0
} else {
0.0
};
let cpu_usage = sys.global_cpu_usage();
let cpu_count = sys.cpus().len();
let (load_average_1, load_average_5, load_average_15) = {
#[cfg(unix)]
{
let load_avg = System::load_average();
(Some(load_avg.one), Some(load_avg.five), Some(load_avg.fifteen))
}
#[cfg(not(unix))]
{
(None, None, None)
}
};
let current_pid = Pid::from_u32(std::process::id());
sys.refresh_processes(ProcessesToUpdate::Some(&[current_pid]), false);
let system_uptime = System::uptime();
let (process_cpu_usage, process_memory_kib, process_virtual_memory_kib,
process_start_time) = sys
.process(current_pid)
.map(|p| {
(
p.cpu_usage(),
p.memory(),
p.virtual_memory(),
p.start_time(),
)
})
.unwrap_or((0.0, 0, 0, 0));
let process_memory_bytes = process_memory_kib * 1024;
let process_virtual_memory_bytes = process_virtual_memory_kib * 1024;
let process_memory_percent = if total_memory_kib > 0 {
(process_memory_kib as f32 / total_memory_kib as f32) * 100.0
} else {
0.0
};
let process_uptime = system_uptime.saturating_sub(process_start_time);
Ok(Self {
timestamp,
cpu_usage,
total_memory_bytes,
used_memory_bytes,
available_memory_bytes,
memory_usage_percent,
total_swap_bytes: sys.total_swap() * 1024,
used_swap_bytes: sys.used_swap() * 1024,
uptime: system_uptime,
cpu_count,
load_average_1,
load_average_5,
load_average_15,
process_cpu_usage,
process_memory_bytes,
process_memory_percent,
process_virtual_memory_bytes,
process_start_time_since_boot_secs: process_start_time,
process_uptime,
process_pid: std::process::id(),
})
}
pub fn format_bytes(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
const TB: u64 = GB * 1024;
if bytes >= TB {
format!("{:.2} TB", bytes as f64 / TB as f64)
} else if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
pub fn format_uptime(seconds: u64) -> String {
let days = seconds / 86400;
let hours = (seconds % 86400) / 3600;
let minutes = (seconds % 3600) / 60;
let secs = seconds % 60;
if days > 0 {
format!("{}d {}h {}m", days, hours, minutes)
} else if hours > 0 {
format!("{}h {}m", hours, minutes)
} else if minutes > 0 {
format!("{}m {}s", minutes, secs)
} else {
format!("{}s", secs)
}
}
}