From 57cd7aac33252736565a1f785a6c76858e2a9fc7 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Fri, 17 Apr 2026 11:54:36 -0300 Subject: [PATCH] lesavka: improve process cpu sampling --- client/Cargo.toml | 2 +- common/Cargo.toml | 2 +- common/src/cli.rs | 2 +- common/src/process_metrics.rs | 39 +++++++++++++++++++++++++++++++++++ server/Cargo.toml | 2 +- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/client/Cargo.toml b/client/Cargo.toml index c791da4..a7987e1 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.11.2" +version = "0.11.3" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index cbf031c..e690d76 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.11.2" +version = "0.11.3" edition = "2024" build = "build.rs" diff --git a/common/src/cli.rs b/common/src/cli.rs index 34aa87e..9d28050 100644 --- a/common/src/cli.rs +++ b/common/src/cli.rs @@ -17,6 +17,6 @@ mod tests { #[test] fn banner_includes_version() { - assert_eq!(banner("0.11.2"), "lesavka-common CLI (v0.11.2)"); + assert_eq!(banner("0.11.3"), "lesavka-common CLI (v0.11.3)"); } } diff --git a/common/src/process_metrics.rs b/common/src/process_metrics.rs index cfa83ee..96cb642 100644 --- a/common/src/process_metrics.rs +++ b/common/src/process_metrics.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] use std::time::Instant; +use std::{process::Command, sync::OnceLock}; /// Sample per-process CPU pressure from Linux procfs. /// @@ -41,10 +42,43 @@ impl ProcessCpuSampler { } fn read_process_runtime_ns() -> Option { + read_process_runtime_from_stat().or_else(read_process_runtime_from_schedstat) +} + +fn read_process_runtime_from_stat() -> Option { + let text = std::fs::read_to_string("/proc/self/stat").ok()?; + let close = text.rfind(')')?; + let rest = text.get(close + 2..)?; + let fields: Vec<&str> = rest.split_whitespace().collect(); + let utime_ticks = fields.get(11)?.parse::().ok()?; + let stime_ticks = fields.get(12)?.parse::().ok()?; + let total_ticks = utime_ticks.saturating_add(stime_ticks); + let ticks_per_second = *clock_ticks_per_second()?; + Some(total_ticks.saturating_mul(1_000_000_000) / ticks_per_second.max(1)) +} + +fn read_process_runtime_from_schedstat() -> Option { let text = std::fs::read_to_string("/proc/self/schedstat").ok()?; text.split_whitespace().next()?.parse::().ok() } +fn clock_ticks_per_second() -> Option<&'static u64> { + static CLOCK_TICKS_PER_SECOND: OnceLock> = OnceLock::new(); + CLOCK_TICKS_PER_SECOND + .get_or_init(|| { + let output = Command::new("getconf").arg("CLK_TCK").output().ok()?; + if !output.status.success() { + return None; + } + String::from_utf8(output.stdout) + .ok()? + .trim() + .parse::() + .ok() + }) + .as_ref() +} + #[cfg(test)] mod tests { use super::*; @@ -56,6 +90,11 @@ mod tests { assert!(read_process_runtime_ns().is_some()); } + #[test] + fn clock_tick_lookup_reads() { + assert!(clock_ticks_per_second().is_some()); + } + #[test] fn sampler_returns_percentage_after_two_samples() { let mut sampler = ProcessCpuSampler::new(); diff --git a/server/Cargo.toml b/server/Cargo.toml index ee9f859..46c7fe6 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,7 +10,7 @@ bench = false [package] name = "lesavka_server" -version = "0.11.2" +version = "0.11.3" edition = "2024" autobins = false