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:
parent
2812baa77b
commit
fd3ae29dc5
2 changed files with 85 additions and 87 deletions
|
@ -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
|
||||||
|
|
169
src/cpu.rs
169
src/cpu.rs
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue