Files
2026-09-19 02:45:52 +00:00

204 lines
5.5 KiB
Rust

use serde::Serialize;
use sysinfo::{Networks, System};
#[derive(Debug, Clone, Serialize)]
pub struct Sample {
pub ts: i64,
pub cpu_pct: f32,
pub mem_used_bytes: u64,
pub mem_total_bytes: u64,
pub mem_pct: f32,
pub disk_used_bytes: u64,
pub disk_total_bytes: u64,
pub disk_pct: f32,
pub net_rx_bps: f64,
pub net_tx_bps: f64,
pub load1: f64,
pub load5: f64,
pub load15: f64,
}
#[derive(Debug, Clone, Serialize)]
pub struct HostInfo {
pub hostname: String,
pub os: String,
pub kernel: String,
pub cpus: usize,
pub mem_total_bytes: u64,
pub disk_used_bytes: u64,
pub disk_total_bytes: u64,
pub disk_pct: f32,
pub uptime_secs: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct DiskUsage {
pub used_bytes: u64,
pub total_bytes: u64,
pub pct: f32,
}
/// Boot / root filesystem usage via `statvfs("/")`.
pub fn boot_disk_usage() -> DiskUsage {
#[cfg(target_os = "linux")]
{
use std::ffi::CString;
let Ok(path) = CString::new("/") else {
return DiskUsage {
used_bytes: 0,
total_bytes: 0,
pct: 0.0,
};
};
let mut buf: libc::statvfs = unsafe { std::mem::zeroed() };
let rc = unsafe { libc::statvfs(path.as_ptr(), &mut buf) };
if rc != 0 {
return DiskUsage {
used_bytes: 0,
total_bytes: 0,
pct: 0.0,
};
}
let frsize = buf.f_frsize as u64;
let total = buf.f_blocks as u64 * frsize;
let available = buf.f_bavail as u64 * frsize;
let used = total.saturating_sub(available);
let pct = if total > 0 {
(used as f64 / total as f64 * 100.0) as f32
} else {
0.0
};
return DiskUsage {
used_bytes: used,
total_bytes: total,
pct,
};
}
#[cfg(not(target_os = "linux"))]
{
DiskUsage {
used_bytes: 0,
total_bytes: 0,
pct: 0.0,
}
}
}
pub struct Sampler {
sys: System,
networks: Networks,
last_rx: u64,
last_tx: u64,
last_ts: Option<i64>,
primed: bool,
}
impl Sampler {
pub fn new() -> Self {
let mut sys = System::new();
sys.refresh_memory();
sys.refresh_cpu_all();
let networks = Networks::new_with_refreshed_list();
Self {
sys,
networks,
last_rx: 0,
last_tx: 0,
last_ts: None,
primed: false,
}
}
pub fn host_info(&mut self) -> HostInfo {
self.sys.refresh_memory();
let disk = boot_disk_usage();
HostInfo {
hostname: hostname::get()
.ok()
.and_then(|h| h.into_string().ok())
.unwrap_or_else(|| "unknown".into()),
os: System::long_os_version().unwrap_or_else(|| "unknown".into()),
kernel: System::kernel_version().unwrap_or_else(|| "unknown".into()),
cpus: self
.sys
.physical_core_count()
.unwrap_or_else(|| self.sys.cpus().len())
.max(1),
mem_total_bytes: self.sys.total_memory(),
disk_used_bytes: disk.used_bytes,
disk_total_bytes: disk.total_bytes,
disk_pct: disk.pct,
uptime_secs: System::uptime(),
}
}
pub fn sample(&mut self) -> Sample {
self.sys.refresh_cpu_all();
self.sys.refresh_memory();
self.networks.refresh(true);
let now = chrono::Utc::now().timestamp();
let cpu_pct = self.sys.global_cpu_usage();
let mem_total = self.sys.total_memory();
let mem_used = self.sys.used_memory();
let mem_pct = if mem_total > 0 {
(mem_used as f64 / mem_total as f64 * 100.0) as f32
} else {
0.0
};
let disk = boot_disk_usage();
let (rx, tx) = aggregate_networks(&self.networks);
let (rx_bps, tx_bps) = if let Some(prev) = self.last_ts {
let dt = (now - prev).max(1) as f64;
if self.primed && rx >= self.last_rx && tx >= self.last_tx {
(
(rx - self.last_rx) as f64 / dt,
(tx - self.last_tx) as f64 / dt,
)
} else {
(0.0, 0.0)
}
} else {
(0.0, 0.0)
};
self.last_rx = rx;
self.last_tx = tx;
self.last_ts = Some(now);
self.primed = true;
let load = System::load_average();
Sample {
ts: now,
cpu_pct,
mem_used_bytes: mem_used,
mem_total_bytes: mem_total,
mem_pct,
disk_used_bytes: disk.used_bytes,
disk_total_bytes: disk.total_bytes,
disk_pct: disk.pct,
net_rx_bps: rx_bps,
net_tx_bps: tx_bps,
load1: load.one,
load5: load.five,
load15: load.fifteen,
}
}
}
fn aggregate_networks(networks: &Networks) -> (u64, u64) {
let mut rx = 0u64;
let mut tx = 0u64;
for (name, data) in networks.iter() {
let n = name.to_lowercase();
if n == "lo" || n.starts_with("docker") || n.starts_with("br-") || n.starts_with("veth") {
continue;
}
rx = rx.saturating_add(data.total_received());
tx = tx.saturating_add(data.total_transmitted());
}
(rx, tx)
}