From 1ab9aceced0e591151b36a21400e8264b3ff9f34 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Wed, 21 May 2025 00:24:36 +0300 Subject: [PATCH] cpu: cpu times --- src/cpu.rs | 105 +++++++++++++++++++++++++++++++++++++++++--- src/monitor.rs | 105 -------------------------------------------- src/power_supply.rs | 4 +- src/system.rs | 54 +++++++++++++---------- 4 files changed, 131 insertions(+), 137 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 0179746..2e0db57 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -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. diff --git a/src/monitor.rs b/src/monitor.rs index cda52dc..1cd0dc4 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -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> { - 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::().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, diff --git a/src/power_supply.rs b/src/power_supply.rs index 025ac75..5bbcebc 100644 --- a/src/power_supply.rs +++ b/src/power_supply.rs @@ -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(); diff --git a/src/system.rs b/src/system.rs index cc39776..f8820b1 100644 --- a/src/system.rs +++ b/src/system.rs @@ -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 { + 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(()) } }