use std::collections::{BTreeMap, BTreeSet};
use std::sync::Mutex;
use serde_json::{json, Value};
use sysinfo::{
Components, MemoryRefreshKind, Pid, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System,
};
static HARN_OWNED_PIDS: Mutex<BTreeMap<u32, usize>> = Mutex::new(BTreeMap::new());
#[must_use = "dropping the registration stops claiming the pid as Harn-owned"]
pub struct HarnOwnedPidRegistration {
pid: u32,
}
impl Drop for HarnOwnedPidRegistration {
fn drop(&mut self) {
unregister_harn_owned_pid(self.pid);
}
}
pub fn register_harn_owned_pid(pid: u32) -> HarnOwnedPidRegistration {
let mut set = HARN_OWNED_PIDS
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*set.entry(pid).or_default() += 1;
HarnOwnedPidRegistration { pid }
}
fn unregister_harn_owned_pid(pid: u32) {
let mut set = HARN_OWNED_PIDS
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match set.get_mut(&pid) {
Some(claims) if *claims > 1 => *claims -= 1,
Some(_) => {
set.remove(&pid);
}
None => {}
}
}
fn harn_owned_pids_snapshot() -> BTreeSet<u32> {
HARN_OWNED_PIDS
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.keys()
.copied()
.collect()
}
pub fn cpu_snapshot() -> Value {
let mut sys = System::new_with_specifics(
RefreshKind::nothing().with_cpu(
sysinfo::CpuRefreshKind::nothing()
.with_cpu_usage()
.with_frequency(),
),
);
sys.refresh_cpu_all();
let cpus = sys.cpus();
let count = cpus.len();
let physical_count = System::physical_core_count();
let (model, frequency_mhz) = match cpus.first() {
Some(cpu) => {
let brand = cpu.brand().trim().to_string();
(
if brand.is_empty() { None } else { Some(brand) },
Some(cpu.frequency()),
)
}
None => (None, None),
};
let cpu_usage = if cpus.is_empty() {
None
} else {
let total: f32 = cpus.iter().map(|c| c.cpu_usage()).sum();
Some(total as f64 / cpus.len() as f64)
};
json!({
"count": count,
"physical_count": physical_count,
"model": model,
"frequency_mhz": frequency_mhz,
"usage_pct": cpu_usage,
})
}
pub fn memory_snapshot() -> Value {
let mut sys = System::new_with_specifics(
RefreshKind::nothing().with_memory(MemoryRefreshKind::everything()),
);
sys.refresh_memory();
let total = sys.total_memory();
let used = sys.used_memory();
let available = sys.available_memory();
let total_gb = bytes_to_gb(total);
let used_gb = bytes_to_gb(used);
let available_gb = bytes_to_gb(available);
let pressure = if total == 0 {
"unknown"
} else {
let ratio = used as f64 / total as f64;
if ratio >= 0.85 {
"high"
} else if ratio >= 0.6 {
"medium"
} else {
"low"
}
};
json!({
"total_bytes": total,
"used_bytes": used,
"available_bytes": available,
"total_gb": total_gb,
"used_gb": used_gb,
"available_gb": available_gb,
"pressure": pressure,
})
}
pub fn current_process_memory_bytes() -> Option<u64> {
let pid = Pid::from_u32(std::process::id());
let mut sys = System::new();
sys.refresh_processes_specifics(
ProcessesToUpdate::Some(&[pid]),
false,
ProcessRefreshKind::nothing().with_memory(),
);
let reported = sys.process(pid).map(|process| process.memory());
reported.filter(|bytes| *bytes > 0).or_else(linux_peak_rss)
}
#[cfg(target_os = "linux")]
fn linux_peak_rss() -> Option<u64> {
use std::mem::MaybeUninit;
unsafe {
let mut usage = MaybeUninit::<libc::rusage>::zeroed();
if libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) != 0 {
return None;
}
u64::try_from(usage.assume_init().ru_maxrss)
.ok()?
.checked_mul(1024)
}
}
#[cfg(not(target_os = "linux"))]
fn linux_peak_rss() -> Option<u64> {
None
}
pub fn gpus_snapshot() -> Value {
Value::Array(Vec::new())
}
pub fn temperature_snapshot() -> Value {
let components = Components::new_with_refreshed_list();
let mut entries = Vec::new();
for component in &components {
entries.push(json!({
"label": component.label(),
"celsius": component.temperature(),
"max_celsius": component.max(),
"critical_celsius": component.critical(),
}));
}
json!({
"components": entries,
})
}
pub fn platform_snapshot() -> Value {
json!({
"os": canonical_os(),
"arch": std::env::consts::ARCH,
"version": System::os_version(),
"kernel": System::kernel_version(),
"long_os_version": System::long_os_version(),
"hostname": System::host_name(),
})
}
fn canonical_os() -> &'static str {
if cfg!(target_os = "macos") {
"darwin"
} else {
std::env::consts::OS
}
}
pub fn identity_snapshot() -> Value {
json!({
"username": login_name(),
"hostname": System::host_name(),
"pid": std::process::id(),
})
}
fn login_name() -> String {
for key in ["USER", "LOGNAME", "USERNAME"] {
if let Ok(value) = std::env::var(key) {
let value = value.trim();
if !value.is_empty() {
return value.to_string();
}
}
}
passwd_name().unwrap_or_default()
}
#[cfg(unix)]
fn passwd_name() -> Option<String> {
use std::mem::MaybeUninit;
let mut entry = MaybeUninit::<libc::passwd>::uninit();
let mut buffer = vec![0 as libc::c_char; 1024];
let mut found: *mut libc::passwd = std::ptr::null_mut();
let status = unsafe {
libc::getpwuid_r(
libc::getuid(),
entry.as_mut_ptr(),
buffer.as_mut_ptr(),
buffer.len(),
std::ptr::addr_of_mut!(found),
)
};
if status != 0 || found.is_null() {
return None;
}
let name = unsafe { std::ffi::CStr::from_ptr((*found).pw_name) };
name.to_str()
.ok()
.map(str::trim)
.filter(|name| !name.is_empty())
.map(str::to_string)
}
#[cfg(not(unix))]
fn passwd_name() -> Option<String> {
None
}
#[cfg(unix)]
fn current_parent_pid() -> Option<u32> {
u32::try_from(unsafe { libc::getppid() })
.ok()
.filter(|pid| *pid != 0)
}
#[cfg(windows)]
fn current_parent_pid() -> Option<u32> {
let pid = Pid::from_u32(std::process::id());
let mut system = System::new();
system.refresh_processes_specifics(
ProcessesToUpdate::Some(&[pid]),
false,
ProcessRefreshKind::nothing(),
);
system.process(pid)?.parent().map(|parent| parent.as_u32())
}
#[cfg(not(any(unix, windows)))]
fn current_parent_pid() -> Option<u32> {
None
}
pub fn parent_process_snapshot() -> Value {
use crate::process_liveness::{process_identity, process_liveness, ProcessLiveness};
let parent = current_parent_pid();
let (state, identity) = match parent.map(process_liveness) {
Some(ProcessLiveness::Alive) => (
"alive",
parent.and_then(process_identity).map(|id| id.to_string()),
),
Some(ProcessLiveness::Dead) => ("dead", None),
Some(ProcessLiveness::Unknown) | None => ("unknown", None),
};
let latest = current_parent_pid();
if latest != parent {
return json!({"pid": latest, "identity": null, "state": "unknown"});
}
json!({"pid": parent, "identity": identity, "state": state})
}
pub fn processes_snapshot() -> Value {
let mut sys = System::new();
sys.refresh_processes_specifics(
ProcessesToUpdate::All,
false,
ProcessRefreshKind::nothing()
.with_cpu()
.with_memory()
.with_exe(sysinfo::UpdateKind::OnlyIfNotSet),
);
let our_pid = std::process::id();
let our_pid_sys = Pid::from_u32(our_pid);
let registry = harn_owned_pids_snapshot();
let mut entries = Vec::new();
for (pid, process) in sys.processes() {
let pid_u32 = pid.as_u32();
let parent_u32 = process.parent().map(|p| p.as_u32());
let is_harn_owned =
pid_u32 == our_pid || registry.contains(&pid_u32) || parent_u32 == Some(our_pid);
if !is_harn_owned {
entries.push(json!({
"pid": pid_u32,
"name": process.name().to_string_lossy(),
"is_harn_owned": false,
}));
continue;
}
entries.push(json!({
"pid": pid_u32,
"parent_pid": parent_u32,
"name": process.name().to_string_lossy(),
"cpu_pct": process.cpu_usage(),
"mem_bytes": process.memory(),
"is_harn_owned": true,
"is_self": pid_u32 == our_pid,
}));
}
entries.sort_by(|a, b| {
let a_owned = a
.get("is_harn_owned")
.and_then(Value::as_bool)
.unwrap_or(false);
let b_owned = b
.get("is_harn_owned")
.and_then(Value::as_bool)
.unwrap_or(false);
b_owned.cmp(&a_owned).then_with(|| {
a.get("pid")
.and_then(Value::as_u64)
.cmp(&b.get("pid").and_then(Value::as_u64))
})
});
if !entries
.iter()
.any(|entry| entry.get("pid").and_then(Value::as_u64).map(|p| p as u32) == Some(our_pid))
{
entries.insert(
0,
json!({
"pid": our_pid,
"parent_pid": Value::Null,
"name": current_process_name(&sys, our_pid_sys),
"cpu_pct": 0.0,
"mem_bytes": 0,
"is_harn_owned": true,
"is_self": true,
}),
);
}
Value::Array(entries)
}
fn current_process_name(sys: &System, pid: Pid) -> String {
sys.process(pid)
.map(|process| process.name().to_string_lossy().into_owned())
.unwrap_or_else(|| "harn".to_string())
}
fn bytes_to_gb(bytes: u64) -> f64 {
bytes as f64 / 1_073_741_824.0
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(any(unix, windows))]
#[test]
fn parent_process_snapshot_matches_the_native_parent() {
let parent = current_parent_pid().expect("test runner has a parent");
let snapshot = parent_process_snapshot();
assert_eq!(snapshot["pid"], parent);
assert_ne!(parent, std::process::id());
assert_eq!(snapshot["state"], "alive");
let identity = crate::process_liveness::process_identity(parent)
.expect("test runner parent has a native identity")
.to_string();
assert_eq!(snapshot["identity"], identity);
assert_eq!(snapshot.as_object().expect("closed record").len(), 3);
}
#[test]
fn cpu_snapshot_reports_nonzero_count() {
let snapshot = cpu_snapshot();
let count = snapshot
.get("count")
.and_then(Value::as_u64)
.expect("count present");
assert!(count >= 1, "expected at least one logical cpu, got {count}");
}
#[test]
fn memory_snapshot_has_nonzero_total() {
let snapshot = memory_snapshot();
let total = snapshot
.get("total_bytes")
.and_then(Value::as_u64)
.expect("total_bytes present");
assert!(total > 0, "total memory should be non-zero, got {total}");
let pressure = snapshot
.get("pressure")
.and_then(Value::as_str)
.expect("pressure present");
assert!(
matches!(pressure, "low" | "medium" | "high" | "unknown"),
"pressure should be a known bucket, got {pressure:?}"
);
}
#[test]
fn gpus_snapshot_returns_list() {
let snapshot = gpus_snapshot();
assert!(snapshot.is_array(), "gpus snapshot is a list");
}
#[test]
fn temperature_snapshot_returns_components_field() {
let snapshot = temperature_snapshot();
assert!(
snapshot.get("components").is_some(),
"components field present"
);
assert!(
snapshot.get("components").unwrap().is_array(),
"components is array"
);
}
#[test]
fn platform_snapshot_includes_arch() {
let snapshot = platform_snapshot();
assert_eq!(
snapshot.get("arch").and_then(Value::as_str),
Some(std::env::consts::ARCH)
);
}
#[test]
fn processes_snapshot_includes_self() {
let snapshot = processes_snapshot();
let entries = snapshot.as_array().expect("array");
let our_pid = std::process::id() as u64;
let self_entry = entries
.iter()
.find(|entry| entry.get("pid").and_then(Value::as_u64) == Some(our_pid))
.expect("self entry present");
assert_eq!(
self_entry.get("is_harn_owned").and_then(Value::as_bool),
Some(true),
"self entry must be harn-owned"
);
}
#[test]
fn current_process_memory_bytes_reports_self_when_available() {
if let Some(bytes) = current_process_memory_bytes() {
assert!(bytes > 0, "current process memory should be non-zero");
}
}
#[cfg(target_os = "linux")]
#[test]
fn linux_peak_rss_reports_self() {
assert!(linux_peak_rss().is_some_and(|bytes| bytes > 0));
}
#[cfg(unix)]
#[test]
fn the_login_name_survives_an_environment_with_no_user_variables() {
assert!(
passwd_name().is_some_and(|name| !name.is_empty()),
"the passwd entry for the real uid should name the current user"
);
}
#[cfg(unix)]
#[test]
fn the_identity_snapshot_names_a_user() {
let identity = identity_snapshot();
let username = identity["username"].as_str().unwrap_or_default();
assert!(!username.is_empty(), "identity: {identity}");
}
#[test]
fn harn_owned_pid_process_global_lifetime_waits_for_every_owner() {
let fake = u32::MAX - 1;
let first = register_harn_owned_pid(fake);
let second = register_harn_owned_pid(fake);
assert!(harn_owned_pids_snapshot().contains(&fake));
drop(first);
assert!(
harn_owned_pids_snapshot().contains(&fake),
"one owner dropping must not erase another owner's live claim"
);
drop(second);
assert!(!harn_owned_pids_snapshot().contains(&fake));
}
}