diff --git a/src/config.rs b/src/config.rs index b68485a..9ca0498 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 mut cpus = match &self.for_ { + let 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 &mut cpus { + for cpu in 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_mhz_minimum(mhz_minimum)?; + cpu.set_frequency_minimum(mhz_minimum)?; } if let Some(mhz_maximum) = self.frequency_mhz_maximum { - cpu.set_frequency_mhz_maximum(mhz_maximum)?; + cpu.set_frequency_maximum(mhz_maximum)?; } } diff --git a/src/core.rs b/src/core.rs index 38c3d0c..a3f4e33 100644 --- a/src/core.rs +++ b/src/core.rs @@ -6,12 +6,17 @@ 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, } 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 c41a0d0..736008a 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -11,19 +11,6 @@ pub struct Cpu { pub has_cpufreq: bool, - pub available_governors: Vec, - pub governor: Option, - - pub frequency_mhz: Option, - 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, @@ -65,19 +52,6 @@ impl Cpu { number, has_cpufreq: false, - available_governors: Vec::new(), - governor: None, - - frequency_mhz: None, - 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, @@ -142,13 +116,6 @@ impl Cpu { self.rescan_times()?; - if self.has_cpufreq { - self.rescan_governor()?; - self.rescan_frequency()?; - self.rescan_epp()?; - self.rescan_epb()?; - } - Ok(()) } @@ -213,134 +180,25 @@ 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; - - 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 = Some(frequency_khz / 1000); - self.frequency_mhz_minimum = Some(frequency_khz_minimum / 1000); - self.frequency_mhz_maximum = Some(frequency_khz_maximum / 1000); - - Ok(()) - } - - fn rescan_epp(&mut self) -> anyhow::Result<()> { + pub fn get_available_governors(&self) -> Vec { 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() + let Some(Ok(content)) = fs::read(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors" + )) else { + return Vec::new(); }; - 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(()) + content + .split_whitespace() + .map(ToString::to_string) + .collect() } - fn rescan_epb(&mut self) -> anyhow::Result<()> { + pub fn set_governor(&self, governor: &str) -> 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, - available_governors: ref governors, - .. - } = *self; + let governors = self.get_available_governors(); if !governors .iter() @@ -360,19 +218,28 @@ 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 set_epp(&mut self, epp: &str) -> anyhow::Result<()> { - let Self { - number, - available_epps: ref epps, - .. - } = *self; + 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(); if !epps.iter().any(|avail_epp| avail_epp == epp) { bail!( @@ -390,14 +257,42 @@ impl Cpu { }) } - pub fn set_epb(&self, epb: &str) -> anyhow::Result<()> { - let Self { - number, - available_epbs: ref epbs, - .. - } = *self; + pub fn get_available_epbs(&self) -> &'static [&'static str] { + if !self.has_cpufreq { + return &[]; + } - if !epbs.iter().any(|avail_epb| avail_epb == epb) { + &[ + "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 epbs = self.get_available_epbs(); + + if !epbs.contains(&epb) { bail!( "EPB value '{epb}' is not available for {self}. available EPB values: {valid}", valid = epbs.join(", "), @@ -413,10 +308,10 @@ impl Cpu { }) } - pub fn set_frequency_mhz_minimum(&mut self, frequency_mhz: u64) -> anyhow::Result<()> { - let Self { number, .. } = *self; + pub fn set_frequency_minimum(&self, frequency_mhz: u64) -> anyhow::Result<()> { + let Self { number, .. } = self; - self.validate_frequency_mhz_minimum(frequency_mhz)?; + self.validate_frequency_minimum(frequency_mhz)?; // We use u64 for the intermediate calculation to prevent overflow let frequency_khz = frequency_mhz * 1000; @@ -428,14 +323,10 @@ impl Cpu { ) .with_context(|| { format!("this probably means that {self} doesn't exist or doesn't support changing minimum frequency") - })?; - - self.frequency_mhz_minimum = Some(frequency_mhz); - - Ok(()) + }) } - fn validate_frequency_mhz_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { + fn validate_frequency_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { let Self { number, .. } = self; let Some(Ok(minimum_frequency_khz)) = fs::read_u64(format!( @@ -455,10 +346,10 @@ impl Cpu { Ok(()) } - pub fn set_frequency_mhz_maximum(&mut self, frequency_mhz: u64) -> anyhow::Result<()> { - let Self { number, .. } = *self; + pub fn set_frequency_maximum(&self, frequency_mhz: u64) -> anyhow::Result<()> { + let Self { number, .. } = self; - self.validate_frequency_mhz_maximum(frequency_mhz)?; + self.validate_frequency_maximum(frequency_mhz)?; // We use u64 for the intermediate calculation to prevent overflow let frequency_khz = frequency_mhz * 1000; @@ -470,14 +361,10 @@ impl Cpu { ) .with_context(|| { format!("this probably means that {self} doesn't exist or doesn't support changing maximum frequency") - })?; - - self.frequency_mhz_maximum = Some(frequency_mhz); - - Ok(()) + }) } - fn validate_frequency_mhz_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { + fn validate_frequency_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/fs.rs b/src/fs.rs index 4c11178..9b150b3 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.trim().to_owned())), + Ok(string) => Some(Ok(string)), Err(error) if error.kind() == io::ErrorKind::NotFound => None, diff --git a/src/monitor.rs b/src/monitor.rs index 609213d..5d0468b 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -21,6 +21,18 @@ 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. @@ -132,6 +144,9 @@ pub fn get_cpu_core_info( Ok(CpuCoreInfo { core_id, + current_frequency_mhz, + min_frequency_mhz, + max_frequency_mhz, usage_percent, temperature_celsius, }) @@ -238,9 +253,47 @@ 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"); + 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) @@ -253,6 +306,14 @@ 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 @@ -279,7 +340,11 @@ 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, platform_profile, average_temperature_celsius, }