use std::collections::BTreeSet;
use std::sync::Mutex;
use serde_json::{json, Value};
use sysinfo::{
Components, MemoryRefreshKind, Pid, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System,
};
static HARN_OWNED_PIDS: Mutex<BTreeSet<u32>> = Mutex::new(BTreeSet::new());
pub fn register_harn_owned_pid(pid: u32) {
if let Ok(mut set) = HARN_OWNED_PIDS.lock() {
set.insert(pid);
}
}
pub fn unregister_harn_owned_pid(pid: u32) {
if let Ok(mut set) = HARN_OWNED_PIDS.lock() {
set.remove(&pid);
}
}
fn harn_owned_pids_snapshot() -> BTreeSet<u32> {
HARN_OWNED_PIDS
.lock()
.map(|set| set.clone())
.unwrap_or_default()
}
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 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": System::name(),
"arch": std::env::consts::ARCH,
"version": System::os_version(),
"kernel": System::kernel_version(),
"long_os_version": System::long_os_version(),
"hostname": System::host_name(),
})
}
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::*;
#[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 register_and_unregister_harn_owned_pid_round_trip() {
let fake = u32::MAX - 1;
register_harn_owned_pid(fake);
assert!(harn_owned_pids_snapshot().contains(&fake));
unregister_harn_owned_pid(fake);
assert!(!harn_owned_pids_snapshot().contains(&fake));
}
}