use std::time::{SystemTime, UNIX_EPOCH};
pub fn cpu_count() -> u64 {
std::thread::available_parallelism()
.map(|n| n.get() as u64)
.unwrap_or(1)
}
#[cfg(target_os = "linux")]
pub fn total_memory() -> u64 {
meminfo_field("MemTotal:").unwrap_or(0)
}
#[cfg(target_os = "macos")]
pub fn total_memory() -> u64 {
sysctl_u64(b"hw.memsize\0").unwrap_or(0)
}
#[cfg(target_os = "linux")]
pub fn available_memory() -> u64 {
meminfo_field("MemAvailable:").unwrap_or_else(total_memory)
}
#[cfg(target_os = "macos")]
pub fn available_memory() -> u64 {
vm_available().unwrap_or_else(total_memory)
}
#[cfg(target_os = "linux")]
pub fn memory_pressure() -> Option<f64> {
let text = std::fs::read_to_string("/proc/pressure/memory").ok()?;
let some = text.lines().find(|l| l.starts_with("some "))?;
let field = some.split_whitespace().find(|f| f.starts_with("avg10="))?;
field.trim_start_matches("avg10=").parse().ok()
}
#[cfg(not(target_os = "linux"))]
pub fn memory_pressure() -> Option<f64> {
None
}
#[cfg(target_os = "linux")]
fn meminfo_field(key: &str) -> Option<u64> {
let text = std::fs::read_to_string("/proc/meminfo").ok()?;
let line = text.lines().find(|l| l.starts_with(key))?;
let kb: u64 = line.split_whitespace().nth(1)?.parse().ok()?;
Some(kb * 1024)
}
#[cfg(target_os = "macos")]
fn sysctl_u64(name: &[u8]) -> Option<u64> {
let mut value: u64 = 0;
let mut len = std::mem::size_of::<u64>();
let rc = unsafe {
libc::sysctlbyname(
name.as_ptr() as *const libc::c_char,
&mut value as *mut u64 as *mut libc::c_void,
&mut len,
std::ptr::null_mut(),
0,
)
};
(rc == 0).then_some(value)
}
#[cfg(target_os = "macos")]
fn vm_available() -> Option<u64> {
let mut stats: libc::vm_statistics64 = unsafe { std::mem::zeroed() };
let mut count = libc::HOST_VM_INFO64_COUNT;
#[allow(deprecated)]
let rc = unsafe {
libc::host_statistics64(
libc::mach_host_self(),
libc::HOST_VM_INFO64,
&mut stats as *mut _ as *mut libc::integer_t,
&mut count,
)
};
if rc != 0 {
return None;
}
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64;
let usable = stats.free_count as u64
+ stats.inactive_count as u64
+ stats.purgeable_count as u64
+ stats.speculative_count as u64;
Some(usable * page_size)
}
pub fn boot_id() -> String {
#[cfg(target_os = "linux")]
{
if let Ok(id) = std::fs::read_to_string("/proc/sys/kernel/random/boot_id") {
return id.trim().to_string();
}
}
#[cfg(target_os = "macos")]
{
if let Some(boot) = sysctl_boottime() {
return boot;
}
}
"unknown".to_string()
}
#[cfg(target_os = "macos")]
fn sysctl_boottime() -> Option<String> {
let mut tv = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
let mut len = std::mem::size_of::<libc::timeval>();
let rc = unsafe {
libc::sysctlbyname(
b"kern.boottime\0".as_ptr() as *const libc::c_char,
&mut tv as *mut _ as *mut libc::c_void,
&mut len,
std::ptr::null_mut(),
0,
)
};
(rc == 0).then(|| format!("boot-{}", tv.tv_sec))
}
pub fn pid_alive(pid: i32) -> bool {
if pid <= 0 {
return false;
}
let rc = unsafe { libc::kill(pid, 0) };
if rc == 0 {
return true;
}
std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
}
pub fn now_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn machine_capacity_is_plausible() {
assert!(cpu_count() >= 1);
let total = total_memory();
assert!(total > 0, "total memory probe returned zero");
assert!(
available_memory() <= total,
"available memory exceeds total"
);
}
#[test]
fn pressure_is_a_percentage_when_reported() {
if let Some(p) = memory_pressure() {
assert!((0.0..=100.0).contains(&p), "pressure {p} out of range");
}
}
#[test]
fn liveness_check_agrees_about_this_process() {
assert!(pid_alive(std::process::id() as i32));
assert!(!pid_alive(-1));
assert!(!pid_alive(0));
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct GroupUsage {
pub rss: u64,
pub cpu_secs: f64,
pub processes: usize,
}
#[cfg(target_os = "linux")]
pub fn group_usage(pgid: i32) -> GroupUsage {
let mut out = GroupUsage::default();
let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64;
let ticks = unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as f64;
let Ok(entries) = std::fs::read_dir("/proc") else {
return out;
};
for entry in entries.flatten() {
let name = entry.file_name();
let Some(name) = name.to_str() else { continue };
if name.parse::<i32>().is_err() {
continue;
}
let Ok(stat) = std::fs::read_to_string(entry.path().join("stat")) else {
continue;
};
let Some(rest) = stat.rsplit_once(") ") else {
continue;
};
let fields: Vec<&str> = rest.1.split_whitespace().collect();
if fields.len() < 22 {
continue;
}
let Ok(group) = fields[2].parse::<i32>() else {
continue;
};
if group != pgid {
continue;
}
let utime: f64 = fields[11].parse().unwrap_or(0.0);
let stime: f64 = fields[12].parse().unwrap_or(0.0);
let rss_pages: u64 = fields[21].parse().unwrap_or(0);
out.cpu_secs += (utime + stime) / ticks;
out.rss += rss_pages * page;
out.processes += 1;
}
out
}
#[cfg(not(target_os = "linux"))]
pub fn group_usage(pgid: i32) -> GroupUsage {
let mut out = GroupUsage::default();
let Ok(result) = std::process::Command::new("ps")
.args(["-A", "-o", "pgid=,rss=,time="])
.output()
else {
return out;
};
for line in String::from_utf8_lossy(&result.stdout).lines() {
let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() < 3 {
continue;
}
if fields[0].parse::<i32>() != Ok(pgid) {
continue;
}
out.rss += fields[1].parse::<u64>().unwrap_or(0) * 1024;
out.cpu_secs += parse_ps_time(fields[2]);
out.processes += 1;
}
out
}
#[cfg(not(target_os = "linux"))]
fn parse_ps_time(text: &str) -> f64 {
let parts: Vec<&str> = text.split(':').collect();
let mut seconds = 0.0;
for part in &parts {
seconds = seconds * 60.0 + part.parse::<f64>().unwrap_or(0.0);
}
seconds
}
pub fn clock_text(epoch_secs: u64) -> String {
let t = epoch_secs as _;
let mut parts: libc::tm = unsafe { std::mem::zeroed() };
unsafe {
libc::localtime_r(&t, &mut parts);
}
format!(
"{:02}:{:02}:{:02}",
parts.tm_hour, parts.tm_min, parts.tm_sec
)
}
pub fn stdin_is_terminal() -> bool {
unsafe { libc::isatty(libc::STDIN_FILENO) == 1 }
}