lesavka: improve process cpu sampling

This commit is contained in:
Brad Stein 2026-04-17 11:54:36 -03:00
parent 7cb0a4d655
commit 57cd7aac33
5 changed files with 43 additions and 4 deletions

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package] [package]
name = "lesavka_client" name = "lesavka_client"
version = "0.11.2" version = "0.11.3"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lesavka_common" name = "lesavka_common"
version = "0.11.2" version = "0.11.3"
edition = "2024" edition = "2024"
build = "build.rs" build = "build.rs"

View File

@ -17,6 +17,6 @@ mod tests {
#[test] #[test]
fn banner_includes_version() { 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)");
} }
} }

View File

@ -1,6 +1,7 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
use std::time::Instant; use std::time::Instant;
use std::{process::Command, sync::OnceLock};
/// Sample per-process CPU pressure from Linux procfs. /// Sample per-process CPU pressure from Linux procfs.
/// ///
@ -41,10 +42,43 @@ impl ProcessCpuSampler {
} }
fn read_process_runtime_ns() -> Option<u64> { fn read_process_runtime_ns() -> Option<u64> {
read_process_runtime_from_stat().or_else(read_process_runtime_from_schedstat)
}
fn read_process_runtime_from_stat() -> Option<u64> {
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::<u64>().ok()?;
let stime_ticks = fields.get(12)?.parse::<u64>().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<u64> {
let text = std::fs::read_to_string("/proc/self/schedstat").ok()?; let text = std::fs::read_to_string("/proc/self/schedstat").ok()?;
text.split_whitespace().next()?.parse::<u64>().ok() text.split_whitespace().next()?.parse::<u64>().ok()
} }
fn clock_ticks_per_second() -> Option<&'static u64> {
static CLOCK_TICKS_PER_SECOND: OnceLock<Option<u64>> = 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::<u64>()
.ok()
})
.as_ref()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -56,6 +90,11 @@ mod tests {
assert!(read_process_runtime_ns().is_some()); assert!(read_process_runtime_ns().is_some());
} }
#[test]
fn clock_tick_lookup_reads() {
assert!(clock_ticks_per_second().is_some());
}
#[test] #[test]
fn sampler_returns_percentage_after_two_samples() { fn sampler_returns_percentage_after_two_samples() {
let mut sampler = ProcessCpuSampler::new(); let mut sampler = ProcessCpuSampler::new();

View File

@ -10,7 +10,7 @@ bench = false
[package] [package]
name = "lesavka_server" name = "lesavka_server"
version = "0.11.2" version = "0.11.3"
edition = "2024" edition = "2024"
autobins = false autobins = false