1
Fork 0
mirror of https://github.com/RGBCube/superfreq synced 2025-07-27 17:07:44 +00:00

cpu: cache /proc/stat

This commit is contained in:
RGBCube 2025-05-28 21:47:44 +03:00
parent 2812baa77b
commit fd3ae29dc5
Signed by: RGBCube
SSH key fingerprint: SHA256:CzqbPcfwt+GxFYNnFVCqoN5Itn4YFrshg1TrnACpA5M
2 changed files with 85 additions and 87 deletions

View file

@ -53,9 +53,10 @@ impl CpuDelta {
let mut cpus = match &self.for_ { let mut cpus = match &self.for_ {
Some(numbers) => { Some(numbers) => {
let mut cpus = Vec::with_capacity(numbers.len()); let mut cpus = Vec::with_capacity(numbers.len());
let cache = cpu::CpuRescanCache::default();
for &number in numbers { for &number in numbers {
cpus.push(cpu::Cpu::new(number)?); cpus.push(cpu::Cpu::new(number, &cache)?);
} }
cpus cpus

View file

@ -1,10 +1,27 @@
use anyhow::{Context, bail}; use anyhow::{Context, bail};
use yansi::Paint as _; use yansi::Paint as _;
use std::{fmt, string::ToString}; use std::{cell::OnceCell, collections::HashMap, fmt, string::ToString};
use crate::fs; use crate::fs;
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct CpuRescanCache {
stat: OnceCell<HashMap<u32, CpuStat>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CpuStat {
pub user: u64,
pub nice: u64,
pub system: u64,
pub idle: u64,
pub iowait: u64,
pub irq: u64,
pub softirq: u64,
pub steal: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cpu { pub struct Cpu {
pub number: u32, pub number: u32,
@ -24,30 +41,23 @@ pub struct Cpu {
pub available_epbs: Vec<String>, pub available_epbs: Vec<String>,
pub epb: Option<String>, pub epb: Option<String>,
pub time_user: u64, pub stat: CpuStat,
pub time_nice: u64,
pub time_system: u64,
pub time_idle: u64,
pub time_iowait: u64,
pub time_irq: u64,
pub time_softirq: u64,
pub time_steal: u64,
} }
impl Cpu { impl Cpu {
pub fn time_total(&self) -> u64 { pub fn time_total(&self) -> u64 {
self.time_user self.stat.user
+ self.time_nice + self.stat.nice
+ self.time_system + self.stat.system
+ self.time_idle + self.stat.idle
+ self.time_iowait + self.stat.iowait
+ self.time_irq + self.stat.irq
+ self.time_softirq + self.stat.softirq
+ self.time_steal + self.stat.steal
} }
pub fn time_idle(&self) -> u64 { pub fn time_idle(&self) -> u64 {
self.time_idle + self.time_iowait self.stat.idle + self.stat.iowait
} }
pub fn usage(&self) -> f64 { pub fn usage(&self) -> f64 {
@ -64,7 +74,7 @@ impl fmt::Display for Cpu {
} }
impl Cpu { impl Cpu {
pub fn new(number: u32) -> anyhow::Result<Self> { pub fn new(number: u32, cache: &CpuRescanCache) -> anyhow::Result<Self> {
let mut cpu = Self { let mut cpu = Self {
number, number,
has_cpufreq: false, has_cpufreq: false,
@ -82,16 +92,18 @@ impl Cpu {
available_epbs: Vec::new(), available_epbs: Vec::new(),
epb: None, epb: None,
time_user: 0, stat: CpuStat {
time_nice: 0, user: 0,
time_system: 0, nice: 0,
time_idle: 0, system: 0,
time_iowait: 0, idle: 0,
time_irq: 0, iowait: 0,
time_softirq: 0, irq: 0,
time_steal: 0, softirq: 0,
steal: 0,
},
}; };
cpu.rescan()?; cpu.rescan(cache)?;
Ok(cpu) Ok(cpu)
} }
@ -101,6 +113,7 @@ impl Cpu {
const PATH: &str = "/sys/devices/system/cpu"; const PATH: &str = "/sys/devices/system/cpu";
let mut cpus = vec![]; let mut cpus = vec![];
let cache = CpuRescanCache::default();
for entry in fs::read_dir(PATH) for entry in fs::read_dir(PATH)
.with_context(|| format!("failed to read contents of '{PATH}'"))? .with_context(|| format!("failed to read contents of '{PATH}'"))?
@ -121,13 +134,13 @@ impl Cpu {
continue; continue;
}; };
cpus.push(Self::new(number)?); cpus.push(Self::new(number, &cache)?);
} }
// Fall back if sysfs iteration above fails to find any cpufreq CPUs. // Fall back if sysfs iteration above fails to find any cpufreq CPUs.
if cpus.is_empty() { if cpus.is_empty() {
for number in 0..num_cpus::get() as u32 { for number in 0..num_cpus::get() as u32 {
cpus.push(Self::new(number)?); cpus.push(Self::new(number, &cache)?);
} }
} }
@ -135,7 +148,7 @@ impl Cpu {
} }
/// Rescan CPU, tuning local copy of settings. /// Rescan CPU, tuning local copy of settings.
pub fn rescan(&mut self) -> anyhow::Result<()> { pub fn rescan(&mut self, cache: &CpuRescanCache) -> anyhow::Result<()> {
let Self { number, .. } = self; let Self { number, .. } = self;
if !fs::exists(format!("/sys/devices/system/cpu/cpu{number}")) { if !fs::exists(format!("/sys/devices/system/cpu/cpu{number}")) {
@ -144,7 +157,7 @@ impl Cpu {
self.has_cpufreq = fs::exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq")); self.has_cpufreq = fs::exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq"));
self.rescan_times()?; self.rescan_stat(cache)?;
if self.has_cpufreq { if self.has_cpufreq {
self.rescan_governor()?; self.rescan_governor()?;
@ -156,63 +169,47 @@ impl Cpu {
Ok(()) Ok(())
} }
fn rescan_times(&mut self) -> anyhow::Result<()> { fn rescan_stat(&mut self, cache: &CpuRescanCache) -> anyhow::Result<()> {
// TODO: Don't read this per CPU. Share the read or // OnceCell::get_or_try_init is unstable. Cope:
// find something in /sys/.../cpu{N} that does it. let stat = match cache.stat.get() {
let content = fs::read("/proc/stat") Some(stat) => stat,
.context("failed to read CPU stat")?
.context("/proc/stat does not exist")?;
let cpu_name = format!("cpu{number}", number = self.number); None => {
let content = fs::read("/proc/stat")
.context("failed to read CPU stat")?
.context("/proc/stat does not exist")?;
let mut stats = content cache
.lines() .stat
.find_map(|line| { .set(HashMap::from_iter(content.lines().skip(1).filter_map(
line.starts_with(&cpu_name) |line| {
.then(|| line.split_whitespace().skip(1)) let mut parts = line.strip_prefix("cpu")?.split_whitespace();
})
.with_context(|| format!("failed to find {self} in CPU stats"))?;
self.time_user = stats let number = parts.next()?.parse().ok()?;
.next()
.with_context(|| format!("failed to parse {self} user time"))? let stat = CpuStat {
.parse() user: parts.next()?.parse().ok()?,
.with_context(|| format!("failed to find {self} user time"))?; nice: parts.next()?.parse().ok()?,
self.time_nice = stats system: parts.next()?.parse().ok()?,
.next() idle: parts.next()?.parse().ok()?,
.with_context(|| format!("failed to parse {self} nice time"))? iowait: parts.next()?.parse().ok()?,
.parse() irq: parts.next()?.parse().ok()?,
.with_context(|| format!("failed to find {self} nice time"))?; softirq: parts.next()?.parse().ok()?,
self.time_system = stats steal: parts.next()?.parse().ok()?,
.next() };
.with_context(|| format!("failed to parse {self} system time"))?
.parse() Some((number, stat))
.with_context(|| format!("failed to find {self} system time"))?; },
self.time_idle = stats )));
.next()
.with_context(|| format!("failed to parse {self} idle time"))? cache.stat.get().unwrap()
.parse() }
.with_context(|| format!("failed to find {self} idle time"))?; };
self.time_iowait = stats
.next() self.stat = stat
.with_context(|| format!("failed to parse {self} iowait time"))? .get(&self.number)
.parse() .with_context(|| format!("failed to get stat of {self}"))?
.with_context(|| format!("failed to find {self} iowait time"))?; .clone();
self.time_irq = stats
.next()
.with_context(|| format!("failed to parse {self} irq time"))?
.parse()
.with_context(|| format!("failed to find {self} irq time"))?;
self.time_softirq = stats
.next()
.with_context(|| format!("failed to parse {self} softirq time"))?
.parse()
.with_context(|| format!("failed to find {self} softirq time"))?;
self.time_steal = stats
.next()
.with_context(|| format!("failed to parse {self} steal time"))?
.parse()
.with_context(|| format!("failed to find {self} steal time"))?;
Ok(()) Ok(())
} }