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

cpu: cpu times

This commit is contained in:
RGBCube 2025-05-21 00:24:36 +03:00
parent 137f801d2b
commit 1ab9aceced
Signed by: RGBCube
SSH key fingerprint: SHA256:CzqbPcfwt+GxFYNnFVCqoN5Itn4YFrshg1TrnACpA5M
4 changed files with 131 additions and 137 deletions

View file

@ -5,10 +5,37 @@ use std::{fmt, string::ToString};
use crate::fs;
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cpu {
pub number: u32,
pub has_cpufreq: bool,
pub time_user: u64,
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 {
pub fn time_total(&self) -> u64 {
self.time_user
+ self.time_nice
+ self.time_system
+ self.time_idle
+ self.time_iowait
+ self.time_irq
+ self.time_softirq
+ self.time_steal
}
pub fn time_idle(&self) -> u64 {
self.time_idle + self.time_iowait
}
}
impl fmt::Display for Cpu {
@ -24,6 +51,15 @@ impl Cpu {
let mut cpu = Self {
number,
has_cpufreq: false,
time_user: 0,
time_nice: 0,
time_system: 0,
time_idle: 0,
time_iowait: 0,
time_irq: 0,
time_softirq: 0,
time_steal: 0,
};
cpu.rescan()?;
@ -76,9 +112,68 @@ impl Cpu {
bail!("{self} does not exist");
}
let 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.has_cpufreq = has_cpufreq;
self.rescan_times()?;
Ok(())
}
fn rescan_times(&mut self) -> anyhow::Result<()> {
let content = fs::read("/proc/stat")
.context("/proc/stat does not exist")?
.context("failed to read CPU stat")?;
let cpu_name = format!("cpu{number}", number = self.number);
let mut stats = content
.lines()
.find_map(|line| {
line.starts_with(&cpu_name)
.then(|| line.split_whitespace().skip(1))
})
.with_context(|| format!("failed to find {self} in CPU stats"))?;
self.time_user = stats
.next()
.with_context(|| format!("failed to find {self} user time"))?
.parse()
.with_context(|| format!("failed to parse {self} user time"))?;
self.time_nice = stats
.next()
.with_context(|| format!("failed to find {self} nice time"))?
.parse()
.with_context(|| format!("failed to parse {self} nice time"))?;
self.time_system = stats
.next()
.with_context(|| format!("failed to find {self} system time"))?
.parse()
.with_context(|| format!("failed to parse {self} system time"))?;
self.time_idle = stats
.next()
.with_context(|| format!("failed to find {self} idle time"))?
.parse()
.with_context(|| format!("failed to parse {self} idle time"))?;
self.time_iowait = stats
.next()
.with_context(|| format!("failed to find {self} iowait time"))?
.parse()
.with_context(|| format!("failed to parse {self} iowait time"))?;
self.time_irq = stats
.next()
.with_context(|| format!("failed to find {self} irq time"))?
.parse()
.with_context(|| format!("failed to parse {self} irq time"))?;
self.time_softirq = stats
.next()
.with_context(|| format!("failed to find {self} softirq time"))?
.parse()
.with_context(|| format!("failed to parse {self} softirq time"))?;
self.time_steal = stats
.next()
.with_context(|| format!("failed to find {self} steal time"))?
.parse()
.with_context(|| format!("failed to parse {self} steal time"))?;
Ok(())
}
@ -232,7 +327,7 @@ impl Cpu {
fn validate_frequency_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
let Ok(minimum_frequency_khz) = fs::read_u64(format!(
let Some(Ok(minimum_frequency_khz)) = fs::read_u64(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
)) else {
// Just let it pass if we can't find anything.
@ -270,7 +365,7 @@ impl Cpu {
fn validate_frequency_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> {
let Self { number, .. } = self;
let Ok(maximum_frequency_khz) = fs::read_u64(format!(
let Some(Ok(maximum_frequency_khz)) = fs::read_u64(format!(
"/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq"
)) else {
// Just let it pass if we can't find anything.

View file

@ -43,111 +43,6 @@ pub fn get_system_info() -> SystemInfo {
}
}
#[derive(Debug, Clone, Copy)]
pub struct CpuTimes {
user: u64,
nice: u64,
system: u64,
idle: u64,
iowait: u64,
irq: u64,
softirq: u64,
steal: u64,
}
impl CpuTimes {
const fn total_time(&self) -> u64 {
self.user
+ self.nice
+ self.system
+ self.idle
+ self.iowait
+ self.irq
+ self.softirq
+ self.steal
}
const fn idle_time(&self) -> u64 {
self.idle + self.iowait
}
}
fn read_all_cpu_times() -> anyhow::Result<HashMap<u32, CpuTimes>> {
let content = fs::read_to_string("/proc/stat").map_err(SysMonitorError::Io)?;
let mut cpu_times_map = HashMap::new();
for line in content.lines() {
if line.starts_with("cpu") && line.chars().nth(3).is_some_and(|c| c.is_ascii_digit()) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 11 {
return Err(SysMonitorError::ProcStatParseError(format!(
"Line too short: {line}"
)));
}
let core_id_str = &parts[0][3..];
let core_id = core_id_str.parse::<u32>().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse core_id: {core_id_str}"
))
})?;
let times = CpuTimes {
user: parts[1].parse().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse user time: {}",
parts[1]
))
})?,
nice: parts[2].parse().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse nice time: {}",
parts[2]
))
})?,
system: parts[3].parse().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse system time: {}",
parts[3]
))
})?,
idle: parts[4].parse().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse idle time: {}",
parts[4]
))
})?,
iowait: parts[5].parse().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse iowait time: {}",
parts[5]
))
})?,
irq: parts[6].parse().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse irq time: {}",
parts[6]
))
})?,
softirq: parts[7].parse().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse softirq time: {}",
parts[7]
))
})?,
steal: parts[8].parse().map_err(|_| {
SysMonitorError::ProcStatParseError(format!(
"Failed to parse steal time: {}",
parts[8]
))
})?,
};
cpu_times_map.insert(core_id, times);
}
}
Ok(cpu_times_map)
}
pub fn get_cpu_core_info(
core_id: u32,
prev_times: &CpuTimes,

View file

@ -146,7 +146,7 @@ impl PowerSupply {
bail!("{self} does not exist");
}
let threshold_config = self
self.threshold_config = self
.get_type()
.with_context(|| format!("failed to determine what type of power supply '{self}' is"))?
.eq("Battery")
@ -163,8 +163,6 @@ impl PowerSupply {
})
.flatten();
self.threshold_config = threshold_config;
self.is_from_peripheral = 'is_from_peripheral: {
let name_lower = self.name.to_lowercase();

View file

@ -26,17 +26,13 @@ impl System {
}
pub fn rescan(&mut self) -> anyhow::Result<()> {
self.is_desktop = self.is_desktop()?;
let (load_average_1min, load_average_5min, load_average_15min) = self.load_average()?;
self.load_average_1min = load_average_1min;
self.load_average_5min = load_average_5min;
self.load_average_15min = load_average_15min;
self.rescan_is_desktop()?;
self.rescan_load_average()?;
Ok(())
}
fn is_desktop(&self) -> anyhow::Result<bool> {
fn rescan_is_desktop(&mut self) -> anyhow::Result<()> {
if let Some(chassis_type) = fs::read("/sys/class/dmi/id/chassis_type") {
let chassis_type = chassis_type.context("failed to read chassis type")?;
@ -45,9 +41,16 @@ impl System {
// 14=Sub Notebook, 15=Space-saving, 16=Lunch Box, 17=Main Server Chassis
match chassis_type.trim() {
// Desktop form factors.
"3" | "4" | "5" | "6" | "7" | "15" | "16" | "17" => return Ok(true),
"3" | "4" | "5" | "6" | "7" | "15" | "16" | "17" => {
self.is_desktop = true;
return Ok(());
}
// Laptop form factors.
"9" | "10" | "14" => return Ok(false),
"9" | "10" | "14" => {
self.is_desktop = false;
return Ok(());
}
// Unknown, continue with other checks
_ => {}
}
@ -58,7 +61,8 @@ impl System {
|| fs::exists("/sys/devices/system/cpu/cpufreq/conservative");
if !power_saving_exists {
return Ok(true); // Likely a desktop.
self.is_desktop = true;
return Ok(()); // Likely a desktop.
}
// Check battery-specific ACPI paths that laptops typically have
@ -70,15 +74,17 @@ impl System {
for path in laptop_acpi_paths {
if fs::exists(path) {
return Ok(false); // Likely a laptop.
self.is_desktop = false; // Likely a laptop.
return Ok(());
}
}
// Default to assuming desktop if we can't determine.
Ok(true)
self.is_desktop = true;
Ok(())
}
fn load_average(&self) -> anyhow::Result<(f64, f64, f64)> {
fn rescan_load_average(&mut self) -> anyhow::Result<()> {
let content = fs::read("/proc/loadavg")
.context("load average file doesn't exist, are you on linux?")?
.context("failed to read load average")?;
@ -93,16 +99,16 @@ impl System {
);
};
Ok((
load_average_1min
.parse()
.context("failed to parse load average")?,
load_average_5min
.parse()
.context("failed to parse load average")?,
load_average_15min
.parse()
.context("failed to parse load average")?,
))
self.load_average_1min = load_average_1min
.parse()
.context("failed to parse load average")?;
self.load_average_5min = load_average_5min
.parse()
.context("failed to parse load average")?;
self.load_average_15min = load_average_15min
.parse()
.context("failed to parse load average")?;
Ok(())
}
}