feat(telemetry): report process CPU and FDs plus host memory and load

This commit is contained in:
lda committed 2026-10-12 16:57:06 +07:00 Verified
1 parent 4a4acedbc3
commit 4eef8c758f
7 files changed
+301 -8

No files matched your search

+48 -1
View File
@@ -1,6 +1,6 @@
//! Linux procfs reader for core's OS-independent process/host instruments.
use procfs::Current;
use wakey_core::telemetry::ProcessMetricsSource;
use wakey_core::telemetry::{HostLoad, HostMemory, ProcessMetricsSource};
/// Reads only this process and the visible host clock; never scans other PIDs.
/// Constructing the reader does no I/O, so disabled OTLP remains local-only.
@@ -16,6 +16,39 @@ impl ProcessMetricsSource for ProcfsProcessMetrics {
fn host_uptime_seconds(&self) -> Option<f64> {
host_uptime()
}
fn cpu_time_seconds(&self) -> Option<f64> {
let stat = procfs::process::Process::myself().ok()?.stat().ok()?;
let ticks = procfs::ticks_per_second();
if ticks == 0 {
return None;
}
// utime/stime account for all process threads, not child processes.
Some(stat.utime.checked_add(stat.stime)? as f64 / ticks as f64)
}
fn open_file_descriptors(&self) -> Option<u64> {
let process = procfs::process::Process::myself().ok()?;
// procfs uses the kernel's count when available, otherwise enumerates
// directory entries without reading every FD's target. Sampling itself
// temporarily opens procfs descriptors, which can contribute to this count.
u64::try_from(process.fd_count().ok()?).ok()
}
fn host_memory(&self) -> Option<HostMemory> {
let memory = procfs::Meminfo::current().ok()?;
// Unlike Status::vmrss, Meminfo has already converted its fields to bytes.
// Do not substitute MemFree when an older kernel omits MemAvailable.
Some(HostMemory {
total_bytes: memory.mem_total,
available_bytes: memory.mem_available,
})
}
fn host_load(&self) -> Option<HostLoad> {
let load = procfs::LoadAverage::current().ok()?;
Some(HostLoad {
one_minute: f64::from(load.one),
five_minutes: f64::from(load.five),
fifteen_minutes: f64::from(load.fifteen),
})
}
}
fn host_uptime() -> Option<f64> {
@@ -56,5 +89,19 @@ mod tests {
assert!(age.is_finite() && age >= 0.0);
assert!(host.is_finite() && host >= age);
assert!(source.resident_memory_bytes().expect("VmRSS available") > 0);
let cpu = source
.cpu_time_seconds()
.expect("process CPU ticks available");
assert!(cpu.is_finite() && cpu >= 0.0);
assert!(source.open_file_descriptors().expect("FD count available") > 0);
let memory = source.host_memory().expect("host memory available");
assert!(memory.total_bytes > 0);
if let Some(available) = memory.available_bytes {
assert!(available <= memory.total_bytes);
}
let load = source.host_load().expect("host load available");
for average in [load.one_minute, load.five_minutes, load.fifteen_minutes] {
assert!(average.is_finite() && average >= 0.0);
}
}
}