From c1a509328be30951fdf009370672a9a25cad8634 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Wed, 21 May 2025 17:47:13 +0300 Subject: [PATCH 1/3] cpu: store frequency --- src/config.rs | 8 +++---- src/core.rs | 3 --- src/cpu.rs | 63 ++++++++++++++++++++++++++++++++++++++++++-------- src/monitor.rs | 15 ------------ 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9ca0498..b68485a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -50,7 +50,7 @@ pub struct CpuDelta { impl CpuDelta { pub fn apply(&self) -> anyhow::Result<()> { - let cpus = match &self.for_ { + let mut cpus = match &self.for_ { Some(numbers) => { let mut cpus = Vec::with_capacity(numbers.len()); @@ -63,7 +63,7 @@ impl CpuDelta { None => cpu::Cpu::all().context("failed to get all CPUs and their information")?, }; - for cpu in cpus { + for cpu in &mut cpus { if let Some(governor) = self.governor.as_ref() { cpu.set_governor(governor)?; } @@ -77,11 +77,11 @@ impl CpuDelta { } if let Some(mhz_minimum) = self.frequency_mhz_minimum { - cpu.set_frequency_minimum(mhz_minimum)?; + cpu.set_frequency_mhz_minimum(mhz_minimum)?; } if let Some(mhz_maximum) = self.frequency_mhz_maximum { - cpu.set_frequency_maximum(mhz_maximum)?; + cpu.set_frequency_mhz_maximum(mhz_maximum)?; } } diff --git a/src/core.rs b/src/core.rs index a3f4e33..84f1886 100644 --- a/src/core.rs +++ b/src/core.rs @@ -6,9 +6,6 @@ pub struct SystemInfo { pub struct CpuCoreInfo { // Per-core data pub core_id: u32, - pub current_frequency_mhz: Option, - pub min_frequency_mhz: Option, - pub max_frequency_mhz: Option, pub usage_percent: Option, pub temperature_celsius: Option, } diff --git a/src/cpu.rs b/src/cpu.rs index 736008a..9941020 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -11,6 +11,10 @@ pub struct Cpu { pub has_cpufreq: bool, + pub frequency_mhz: u64, + pub frequency_mhz_minimum: u64, + pub frequency_mhz_maximum: u64, + pub time_user: u64, pub time_nice: u64, pub time_system: u64, @@ -52,6 +56,10 @@ impl Cpu { number, has_cpufreq: false, + frequency_mhz: 0, + frequency_mhz_minimum: 0, + frequency_mhz_maximum: 0, + time_user: 0, time_nice: 0, time_system: 0, @@ -115,6 +123,7 @@ impl Cpu { self.has_cpufreq = fs::exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq")); self.rescan_times()?; + self.rescan_frequency()?; Ok(()) } @@ -180,6 +189,32 @@ impl Cpu { Ok(()) } + fn rescan_frequency(&mut self) -> anyhow::Result<()> { + let Self { number, .. } = *self; + + let frequency_khz = fs::read_u64(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_cur_freq" + )) + .with_context(|| format!("failed to find {self} frequency"))? + .with_context(|| format!("failed to parse {self} frequency"))?; + let frequency_khz_minimum = fs::read_u64(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq" + )) + .with_context(|| format!("failed to find {self} frequency minimum"))? + .with_context(|| format!("failed to parse {self} frequency"))?; + let frequency_khz_maximum = fs::read_u64(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_max_freq" + )) + .with_context(|| format!("failed to find {self} frequency maximum"))? + .with_context(|| format!("failed to parse {self} frequency"))?; + + self.frequency_mhz = frequency_khz / 1000; + self.frequency_mhz_minimum = frequency_khz_minimum / 1000; + self.frequency_mhz_maximum = frequency_khz_maximum / 1000; + + Ok(()) + } + pub fn get_available_governors(&self) -> Vec { let Self { number, .. } = self; @@ -308,10 +343,10 @@ impl Cpu { }) } - pub fn set_frequency_minimum(&self, frequency_mhz: u64) -> anyhow::Result<()> { - let Self { number, .. } = self; + pub fn set_frequency_mhz_minimum(&mut self, frequency_mhz: u64) -> anyhow::Result<()> { + let Self { number, .. } = *self; - self.validate_frequency_minimum(frequency_mhz)?; + self.validate_frequency_mhz_minimum(frequency_mhz)?; // We use u64 for the intermediate calculation to prevent overflow let frequency_khz = frequency_mhz * 1000; @@ -323,10 +358,14 @@ impl Cpu { ) .with_context(|| { format!("this probably means that {self} doesn't exist or doesn't support changing minimum frequency") - }) + })?; + + self.frequency_mhz_minimum = frequency_mhz; + + Ok(()) } - fn validate_frequency_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { + fn validate_frequency_mhz_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { let Self { number, .. } = self; let Some(Ok(minimum_frequency_khz)) = fs::read_u64(format!( @@ -346,10 +385,10 @@ impl Cpu { Ok(()) } - pub fn set_frequency_maximum(&self, frequency_mhz: u64) -> anyhow::Result<()> { - let Self { number, .. } = self; + pub fn set_frequency_mhz_maximum(&mut self, frequency_mhz: u64) -> anyhow::Result<()> { + let Self { number, .. } = *self; - self.validate_frequency_maximum(frequency_mhz)?; + self.validate_frequency_mhz_maximum(frequency_mhz)?; // We use u64 for the intermediate calculation to prevent overflow let frequency_khz = frequency_mhz * 1000; @@ -361,10 +400,14 @@ impl Cpu { ) .with_context(|| { format!("this probably means that {self} doesn't exist or doesn't support changing maximum frequency") - }) + })?; + + self.frequency_mhz_maximum = frequency_mhz; + + Ok(()) } - fn validate_frequency_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { + fn validate_frequency_mhz_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { let Self { number, .. } = self; let Some(Ok(maximum_frequency_khz)) = fs::read_u64(format!( diff --git a/src/monitor.rs b/src/monitor.rs index 5d0468b..9f0c972 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -21,18 +21,6 @@ pub fn get_cpu_core_info( prev_times: &CpuTimes, current_times: &CpuTimes, ) -> anyhow::Result { - let cpufreq_path = PathBuf::from(format!("/sys/devices/system/cpu/cpu{core_id}/cpufreq/")); - - let current_frequency_mhz = read_sysfs_value::(cpufreq_path.join("scaling_cur_freq")) - .map(|khz| khz / 1000) - .ok(); - let min_frequency_mhz = read_sysfs_value::(cpufreq_path.join("scaling_min_freq")) - .map(|khz| khz / 1000) - .ok(); - let max_frequency_mhz = read_sysfs_value::(cpufreq_path.join("scaling_max_freq")) - .map(|khz| khz / 1000) - .ok(); - // Temperature detection. // Should be generic enough to be able to support for multiple hardware sensors // with the possibility of extending later down the road. @@ -144,9 +132,6 @@ pub fn get_cpu_core_info( Ok(CpuCoreInfo { core_id, - current_frequency_mhz, - min_frequency_mhz, - max_frequency_mhz, usage_percent, temperature_celsius, }) From 3212bc0ad56d6425c9c81b8c1374335a2319d4c6 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Wed, 21 May 2025 18:27:09 +0300 Subject: [PATCH 2/3] cpu: store governor and available governors --- src/core.rs | 2 -- src/cpu.rs | 92 ++++++++++++++++++++++++++++++++------------------ src/fs.rs | 2 +- src/monitor.rs | 21 ------------ 4 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/core.rs b/src/core.rs index 84f1886..38c3d0c 100644 --- a/src/core.rs +++ b/src/core.rs @@ -12,8 +12,6 @@ pub struct CpuCoreInfo { pub struct CpuGlobalInfo { // System-wide CPU settings - pub current_governor: Option, - pub available_governors: Vec, pub turbo_status: Option, // true for enabled, false for disabled pub epp: Option, // Energy Performance Preference pub epb: Option, // Energy Performance Bias diff --git a/src/cpu.rs b/src/cpu.rs index 9941020..7365940 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -11,9 +11,12 @@ pub struct Cpu { pub has_cpufreq: bool, - pub frequency_mhz: u64, - pub frequency_mhz_minimum: u64, - pub frequency_mhz_maximum: u64, + pub available_governors: Vec, + pub governor: Option, + + pub frequency_mhz: Option, + pub frequency_mhz_minimum: Option, + pub frequency_mhz_maximum: Option, pub time_user: u64, pub time_nice: u64, @@ -56,9 +59,12 @@ impl Cpu { number, has_cpufreq: false, - frequency_mhz: 0, - frequency_mhz_minimum: 0, - frequency_mhz_maximum: 0, + available_governors: Vec::new(), + governor: None, + + frequency_mhz: None, + frequency_mhz_minimum: None, + frequency_mhz_maximum: None, time_user: 0, time_nice: 0, @@ -123,7 +129,11 @@ impl Cpu { self.has_cpufreq = fs::exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq")); self.rescan_times()?; - self.rescan_frequency()?; + + if self.has_cpufreq { + self.rescan_governor()?; + self.rescan_frequency()?; + } Ok(()) } @@ -189,6 +199,33 @@ impl Cpu { Ok(()) } + fn rescan_governor(&mut self) -> anyhow::Result<()> { + let Self { number, .. } = *self; + + self.available_governors = 'available_governors: { + let Some(Ok(content)) = fs::read(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors" + )) else { + break 'available_governors Vec::new(); + }; + + content + .split_whitespace() + .map(ToString::to_string) + .collect() + }; + + self.governor = Some( + fs::read(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_governor" + )) + .with_context(|| format!("failed to find {self} scaling governor"))? + .with_context(|| format!("failed to read {self} scaling governor"))?, + ); + + Ok(()) + } + fn rescan_frequency(&mut self) -> anyhow::Result<()> { let Self { number, .. } = *self; @@ -208,32 +245,19 @@ impl Cpu { .with_context(|| format!("failed to find {self} frequency maximum"))? .with_context(|| format!("failed to parse {self} frequency"))?; - self.frequency_mhz = frequency_khz / 1000; - self.frequency_mhz_minimum = frequency_khz_minimum / 1000; - self.frequency_mhz_maximum = frequency_khz_maximum / 1000; + self.frequency_mhz = Some(frequency_khz / 1000); + self.frequency_mhz_minimum = Some(frequency_khz_minimum / 1000); + self.frequency_mhz_maximum = Some(frequency_khz_maximum / 1000); Ok(()) } - pub fn get_available_governors(&self) -> Vec { - let Self { number, .. } = self; - - let Some(Ok(content)) = fs::read(format!( - "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors" - )) else { - return Vec::new(); - }; - - content - .split_whitespace() - .map(ToString::to_string) - .collect() - } - - pub fn set_governor(&self, governor: &str) -> anyhow::Result<()> { - let Self { number, .. } = self; - - let governors = self.get_available_governors(); + pub fn set_governor(&mut self, governor: &str) -> anyhow::Result<()> { + let Self { + number, + available_governors: ref governors, + .. + } = *self; if !governors .iter() @@ -253,7 +277,11 @@ impl Cpu { format!( "this probably means that {self} doesn't exist or doesn't support changing governors" ) - }) + })?; + + self.governor = Some(governor.to_owned()); + + Ok(()) } pub fn get_available_epps(&self) -> Vec { @@ -360,7 +388,7 @@ impl Cpu { format!("this probably means that {self} doesn't exist or doesn't support changing minimum frequency") })?; - self.frequency_mhz_minimum = frequency_mhz; + self.frequency_mhz_minimum = Some(frequency_mhz); Ok(()) } @@ -402,7 +430,7 @@ impl Cpu { format!("this probably means that {self} doesn't exist or doesn't support changing maximum frequency") })?; - self.frequency_mhz_maximum = frequency_mhz; + self.frequency_mhz_maximum = Some(frequency_mhz); Ok(()) } diff --git a/src/fs.rs b/src/fs.rs index 9b150b3..4c11178 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -19,7 +19,7 @@ pub fn read(path: impl AsRef) -> Option> { let path = path.as_ref(); match fs::read_to_string(path) { - Ok(string) => Some(Ok(string)), + Ok(string) => Some(Ok(string.trim().to_owned())), Err(error) if error.kind() == io::ErrorKind::NotFound => None, diff --git a/src/monitor.rs b/src/monitor.rs index 9f0c972..44a7146 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -260,25 +260,6 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { let turbo_status_path = Path::new("/sys/devices/system/cpu/intel_pstate/no_turbo"); let boost_path = Path::new("/sys/devices/system/cpu/cpufreq/boost"); - let current_governor = if cpufreq_base_path_buf.join("scaling_governor").exists() { - read_sysfs_file_trimmed(cpufreq_base_path_buf.join("scaling_governor")).ok() - } else { - None - }; - - let available_governors = if cpufreq_base_path_buf - .join("scaling_available_governors") - .exists() - { - read_sysfs_file_trimmed(cpufreq_base_path_buf.join("scaling_available_governors")) - .map_or_else( - |_| vec![], - |s| s.split_whitespace().map(String::from).collect(), - ) - } else { - vec![] - }; - let turbo_status = if turbo_status_path.exists() { // 0 means turbo enabled, 1 means disabled for intel_pstate read_sysfs_value::(turbo_status_path) @@ -325,8 +306,6 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { // Return the constructed CpuGlobalInfo CpuGlobalInfo { - current_governor, - available_governors, turbo_status, epp: energy_perf_pref, epb: energy_perf_bias, From 24fa53914d16da87c43a1828d754ac20523d19fc Mon Sep 17 00:00:00 2001 From: RGBCube Date: Wed, 21 May 2025 18:43:31 +0300 Subject: [PATCH 3/3] cpu: store EPP and EPB --- src/cpu.rs | 148 +++++++++++++++++++++++++++++++------------------ src/monitor.rs | 29 ---------- 2 files changed, 95 insertions(+), 82 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 7365940..c41a0d0 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -18,6 +18,12 @@ pub struct Cpu { pub frequency_mhz_minimum: Option, pub frequency_mhz_maximum: Option, + pub available_epps: Vec, + pub epp: Option, + + pub available_epbs: Vec, + pub epb: Option, + pub time_user: u64, pub time_nice: u64, pub time_system: u64, @@ -66,6 +72,12 @@ impl Cpu { frequency_mhz_minimum: None, frequency_mhz_maximum: None, + available_epps: Vec::new(), + epp: None, + + available_epbs: Vec::new(), + epb: None, + time_user: 0, time_nice: 0, time_system: 0, @@ -133,6 +145,8 @@ impl Cpu { if self.has_cpufreq { self.rescan_governor()?; self.rescan_frequency()?; + self.rescan_epp()?; + self.rescan_epb()?; } Ok(()) @@ -252,6 +266,75 @@ impl Cpu { Ok(()) } + fn rescan_epp(&mut self) -> anyhow::Result<()> { + let Self { number, .. } = self; + + self.available_epps = 'available_epps: { + let Some(Ok(content)) = fs::read(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_available_preferences" + )) else { + break 'available_epps Vec::new(); + }; + + content + .split_whitespace() + .map(ToString::to_string) + .collect() + }; + + self.epp = Some( + fs::read(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_preference" + )) + .with_context(|| format!("failed to find {self} EPP"))? + .with_context(|| format!("failed to read {self} EPP"))?, + ); + + Ok(()) + } + + fn rescan_epb(&mut self) -> anyhow::Result<()> { + let Self { number, .. } = self; + + self.available_epbs = if self.has_cpufreq { + vec![ + "1".to_owned(), + "2".to_owned(), + "3".to_owned(), + "4".to_owned(), + "5".to_owned(), + "6".to_owned(), + "7".to_owned(), + "8".to_owned(), + "9".to_owned(), + "10".to_owned(), + "11".to_owned(), + "12".to_owned(), + "13".to_owned(), + "14".to_owned(), + "15".to_owned(), + "performance".to_owned(), + "balance-performance".to_owned(), + "balance_performance".to_owned(), // Alternative form with underscore. + "balance-power".to_owned(), + "balance_power".to_owned(), // Alternative form with underscore. + "power".to_owned(), + ] + } else { + Vec::new() + }; + + self.epb = Some( + fs::read(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_bias" + )) + .with_context(|| format!("failed to find {self} EPB"))? + .with_context(|| format!("failed to read {self} EPB"))?, + ); + + Ok(()) + } + pub fn set_governor(&mut self, governor: &str) -> anyhow::Result<()> { let Self { number, @@ -284,25 +367,12 @@ impl Cpu { Ok(()) } - pub fn get_available_epps(&self) -> Vec { - let Self { number, .. } = self; - - let Some(Ok(content)) = fs::read(format!( - "/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_available_preferences" - )) else { - return Vec::new(); - }; - - content - .split_whitespace() - .map(ToString::to_string) - .collect() - } - - pub fn set_epp(&self, epp: &str) -> anyhow::Result<()> { - let Self { number, .. } = self; - - let epps = self.get_available_epps(); + pub fn set_epp(&mut self, epp: &str) -> anyhow::Result<()> { + let Self { + number, + available_epps: ref epps, + .. + } = *self; if !epps.iter().any(|avail_epp| avail_epp == epp) { bail!( @@ -320,42 +390,14 @@ impl Cpu { }) } - pub fn get_available_epbs(&self) -> &'static [&'static str] { - if !self.has_cpufreq { - return &[]; - } - - &[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12", - "13", - "14", - "15", - "performance", - "balance-performance", - "balance_performance", // Alternative form with underscore. - "balance-power", - "balance_power", // Alternative form with underscore. - "power", - ] - } - pub fn set_epb(&self, epb: &str) -> anyhow::Result<()> { - let Self { number, .. } = self; + let Self { + number, + available_epbs: ref epbs, + .. + } = *self; - let epbs = self.get_available_epbs(); - - if !epbs.contains(&epb) { + if !epbs.iter().any(|avail_epb| avail_epb == epb) { bail!( "EPB value '{epb}' is not available for {self}. available EPB values: {valid}", valid = epbs.join(", "), diff --git a/src/monitor.rs b/src/monitor.rs index 44a7146..609213d 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -238,25 +238,6 @@ pub fn get_all_cpu_core_info() -> anyhow::Result> { } pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { - // Find a valid CPU to read global settings from - // Try cpu0 first, then fall back to any available CPU with cpufreq - let mut cpufreq_base_path_buf = PathBuf::from("/sys/devices/system/cpu/cpu0/cpufreq/"); - - if !cpufreq_base_path_buf.exists() { - let core_count = get_real_cpus().unwrap_or_else(|e| { - eprintln!("Warning: {e}"); - 0 - }); - - for i in 0..core_count { - let test_path = PathBuf::from(format!("/sys/devices/system/cpu/cpu{i}/cpufreq/")); - if test_path.exists() { - cpufreq_base_path_buf = test_path; - break; // Exit the loop as soon as we find a valid path - } - } - } - let turbo_status_path = Path::new("/sys/devices/system/cpu/intel_pstate/no_turbo"); let boost_path = Path::new("/sys/devices/system/cpu/cpufreq/boost"); @@ -272,14 +253,6 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { None }; - // EPP (Energy Performance Preference) - let energy_perf_pref = - read_sysfs_file_trimmed(cpufreq_base_path_buf.join("energy_performance_preference")).ok(); - - // EPB (Energy Performance Bias) - let energy_perf_bias = - read_sysfs_file_trimmed(cpufreq_base_path_buf.join("energy_performance_bias")).ok(); - let platform_profile = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile").ok(); // Calculate average CPU temperature from the core temperatures @@ -307,8 +280,6 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { // Return the constructed CpuGlobalInfo CpuGlobalInfo { turbo_status, - epp: energy_perf_pref, - epb: energy_perf_bias, platform_profile, average_temperature_celsius, }