From 1645c921d799d6c996b5adb96a0d0b8eae3647d9 Mon Sep 17 00:00:00 2001 From: Bloxx12 Date: Wed, 14 May 2025 11:33:49 +0200 Subject: [PATCH 1/5] config: follow XDG base spec moves /etc/superfreq/config.toml to /etc/xdg/superfreq/config.toml to follow xdg base spec more closely. --- README.md | 2 +- src/config/load.rs | 2 +- src/cpu.rs | 2 +- src/daemon.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 88ebe6a..5669106 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ sudo superfreq set-max-freq 2800 --core-id 1 Superfreq uses TOML configuration files. Default locations: -- `/etc/superfreq/config.toml` +- `/etc/xdg/superfreq/config.toml` - `/etc/superfreq.toml` You can also specify a custom path by setting the `SUPERFREQ_CONFIG` environment diff --git a/src/config/load.rs b/src/config/load.rs index f0fedce..30deabe 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -46,7 +46,7 @@ pub fn load_config_from_path(specific_path: Option<&str>) -> Result Result<()> { // Create directory if it doesn't exist - let dir_path = Path::new("/etc/superfreq"); + let dir_path = Path::new("/etc/xdg/superfreq"); if !dir_path.exists() { fs::create_dir_all(dir_path).map_err(|e| { if e.kind() == io::ErrorKind::PermissionDenied { diff --git a/src/daemon.rs b/src/daemon.rs index b755226..fc94800 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -65,7 +65,7 @@ pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box Date: Wed, 14 May 2025 12:41:36 +0200 Subject: [PATCH 2/5] nix: fix module assertion --- nix/module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/module.nix b/nix/module.nix index 5014f21..5f43bf5 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -55,7 +55,7 @@ in { ''; } { - assertion = !config.programs.auto-cpufreq.enable; + assertion = !config.services.auto-cpufreq.enable; message = '' You have set programs.auto-cpufreq.enable = true; which conflicts with Superfreq. From 58ba603afcbf9fd88ceba4b917c52006ae3a6e34 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 14 May 2025 18:58:06 +0300 Subject: [PATCH 3/5] cpu: handle sysfs errors more gracefully --- src/cpu.rs | 44 +++++++++++++++++ src/engine.rs | 118 ++++++++++++++++++++++++++++++++++++++++++---- src/monitor.rs | 43 +++++++++++------ src/util/error.rs | 12 +++++ 4 files changed, 193 insertions(+), 24 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index ac8092c..e8d4bb6 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -79,6 +79,17 @@ where } pub fn set_governor(governor: &str, core_id: Option) -> Result<()> { + // First, check if the requested governor is available on the system + let available_governors = get_available_governors()?; + + if !available_governors.contains(&governor.to_string()) { + return Err(ControlError::InvalidGovernor(format!( + "Governor '{}' is not available. Available governors: {}", + governor, + available_governors.join(", ") + ))); + } + let action = |id: u32| { let path = format!("/sys/devices/system/cpu/cpu{id}/cpufreq/scaling_governor"); if Path::new(&path).exists() { @@ -93,6 +104,39 @@ pub fn set_governor(governor: &str, core_id: Option) -> Result<()> { core_id.map_or_else(|| for_each_cpu_core(action), action) } +/// Retrieves the list of available CPU governors on the system +pub fn get_available_governors() -> Result> { + // Check cpu0 for available governors + let path = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"; + if !Path::new(path).exists() { + return Err(ControlError::NotSupported( + "Could not determine available governors".to_string(), + )); + } + + let content = fs::read_to_string(path).map_err(|e| { + if e.kind() == io::ErrorKind::PermissionDenied { + ControlError::PermissionDenied(format!("Permission denied reading from {path}")) + } else { + ControlError::ReadError(format!("Failed to read from {path}: {e}")) + } + })?; + + // Parse the space-separated list of governors + let governors = content + .split_whitespace() + .map(ToString::to_string) + .collect::>(); + + if governors.is_empty() { + return Err(ControlError::ParseError( + "No available governors found".to_string(), + )); + } + + Ok(governors) +} + pub fn set_turbo(setting: TurboSetting) -> Result<()> { let value_pstate = match setting { TurboSetting::Always => "0", // no_turbo = 0 means turbo is enabled diff --git a/src/engine.rs b/src/engine.rs index c83d2f2..4f94ff6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,8 +1,8 @@ use crate::config::{AppConfig, ProfileConfig}; use crate::core::{OperationalMode, SystemReport, TurboSetting}; use crate::cpu::{self}; -use crate::util::error::EngineError; -use log::{debug, info}; +use crate::util::error::{ControlError, EngineError}; +use log::{debug, info, warn}; /// Determines the appropriate CPU profile based on power status or forced mode, /// and applies the settings using functions from the `cpu` module. @@ -17,6 +17,18 @@ pub fn determine_and_apply_settings( "Governor override is active: '{}'. Setting governor.", override_governor.trim() ); + + // Check if the governor is available before applying + let available_governors = report.cpu_global.available_governors.clone(); + if !available_governors.contains(&override_governor.trim().to_string()) { + return Err(EngineError::ConfigurationError(format!( + "Governor '{}' from override file is not available on this system. Available governors: {}", + override_governor.trim(), + available_governors.join(", ") + ))); + } + + // Apply the override governor setting cpu::set_governor(override_governor.trim(), None)?; } @@ -52,8 +64,16 @@ pub fn determine_and_apply_settings( // Apply settings from selected_profile_config if let Some(governor) = &selected_profile_config.governor { - info!("Setting governor to '{governor}'"); - cpu::set_governor(governor, None)?; + // Check if the governor is available before trying to set it + if report.cpu_global.available_governors.contains(governor) { + info!("Setting governor to '{governor}'"); + cpu::set_governor(governor, None)?; + } else { + let available = report.cpu_global.available_governors.join(", "); + warn!( + "Configured governor '{governor}' is not available on this system. Available governors: {available}. Skipping." + ); + } } if let Some(turbo_setting) = selected_profile_config.turbo { @@ -63,33 +83,111 @@ pub fn determine_and_apply_settings( debug!("Managing turbo in auto mode based on system conditions"); manage_auto_turbo(report, selected_profile_config)?; } - _ => cpu::set_turbo(turbo_setting)?, + _ => { + // Try to set turbo, but handle the error gracefully + if let Err(e) = cpu::set_turbo(turbo_setting) { + // If the feature is not supported, just log a warning instead of failing + if matches!(e, ControlError::NotSupported(_)) { + warn!( + "Turbo boost control is not supported on this system. Skipping turbo setting." + ); + } else { + // For other errors, propagate them + return Err(EngineError::ControlError(e)); + } + } + } } } if let Some(epp) = &selected_profile_config.epp { info!("Setting EPP to '{epp}'"); - cpu::set_epp(epp, None)?; + // Try to set EPP, but handle the error gracefully + if let Err(e) = cpu::set_epp(epp, None) { + // If the feature is not supported, just log a warning instead of failing + if matches!(e, ControlError::NotSupported(_)) + || e.to_string().contains("No such file or directory") + { + warn!("EPP setting is not supported on this system. Skipping EPP configuration."); + } else { + return Err(EngineError::ControlError(e)); + } + } } if let Some(epb) = &selected_profile_config.epb { info!("Setting EPB to '{epb}'"); - cpu::set_epb(epb, None)?; + // Try to set EPB, but handle the error gracefully + if let Err(e) = cpu::set_epb(epb, None) { + // If the feature is not supported, just log a warning instead of failing + if matches!(e, ControlError::NotSupported(_)) + || e.to_string().contains("No such file or directory") + { + warn!("EPB setting is not supported on this system. Skipping EPB configuration."); + } else { + return Err(EngineError::ControlError(e)); + } + } } if let Some(min_freq) = selected_profile_config.min_freq_mhz { info!("Setting min frequency to '{min_freq} MHz'"); - cpu::set_min_frequency(min_freq, None)?; + if let Err(e) = cpu::set_min_frequency(min_freq, None) { + // If the feature is not supported, just log a warning instead of failing + if matches!(e, ControlError::NotSupported(_)) + || e.to_string().contains("No such file or directory") + { + warn!( + "CPU frequency control is not supported on this system. Skipping min frequency setting." + ); + } else { + return Err(EngineError::ControlError(e)); + } + } } if let Some(max_freq) = selected_profile_config.max_freq_mhz { info!("Setting max frequency to '{max_freq} MHz'"); - cpu::set_max_frequency(max_freq, None)?; + if let Err(e) = cpu::set_max_frequency(max_freq, None) { + // If the feature is not supported, just log a warning instead of failing + if matches!(e, ControlError::NotSupported(_)) + || e.to_string().contains("No such file or directory") + { + warn!( + "CPU frequency control is not supported on this system. Skipping max frequency setting." + ); + } else { + return Err(EngineError::ControlError(e)); + } + } } if let Some(profile) = &selected_profile_config.platform_profile { info!("Setting platform profile to '{profile}'"); - cpu::set_platform_profile(profile)?; + // Try to get available platform profiles first to validate + match cpu::get_platform_profiles() { + Ok(available_profiles) => { + if available_profiles.contains(profile) { + cpu::set_platform_profile(profile)?; + } else { + warn!( + "Platform profile '{}' is not available. Available profiles: {}. Skipping.", + profile, + available_profiles.join(", ") + ); + } + } + Err(e) => { + // If platform profiles are not supported, log a warning and continue + if matches!(e, ControlError::NotSupported(_)) { + warn!( + "Platform profile control is not supported on this system. Skipping profile setting." + ); + } else { + return Err(EngineError::ControlError(e)); + } + } + } } debug!("Profile settings applied successfully."); diff --git a/src/monitor.rs b/src/monitor.rs index 259e872..91997a1 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -403,25 +403,40 @@ pub fn get_all_cpu_core_info() -> Result> { } pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { - // FIXME: Assume global settings can be read from cpu0 or are consistent. - // This might not work properly for heterogeneous systems (e.g. big.LITTLE) - let cpufreq_base_path = Path::new("/sys/devices/system/cpu/cpu0/cpufreq/"); + // 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() { + // Fallback: find first available CPU with cpufreq + for i in 1..=get_logical_core_count().unwrap_or(1) { + let test_path = format!("/sys/devices/system/cpu/cpu{}/cpufreq/", i - 1); + let test_path_buf = PathBuf::from(&test_path); + if test_path_buf.exists() { + cpufreq_base_path_buf = test_path_buf; + break; + } + } + } + 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.join("scaling_governor").exists() { - read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_governor")).ok() + + 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 + let available_governors = if cpufreq_base_path_buf .join("scaling_available_governors") .exists() { - read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_available_governors")).map_or_else( - |_| vec![], - |s| s.split_whitespace().map(String::from).collect(), - ) + 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![] }; @@ -437,17 +452,16 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { } else { None }; + // EPP (Energy Performance Preference) let energy_perf_pref = - read_sysfs_file_trimmed(cpufreq_base_path.join("energy_performance_preference")).ok(); + 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.join("energy_performance_bias")).ok(); + 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(); - let _platform_profile_choices = - read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile_choices").ok(); // Calculate average CPU temperature from the core temperatures let average_temperature_celsius = if cpu_cores.is_empty() { @@ -471,6 +485,7 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { } }; + // Return the constructed CpuGlobalInfo CpuGlobalInfo { current_governor, available_governors, diff --git a/src/util/error.rs b/src/util/error.rs index 3ee0a86..7b92b8f 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -8,6 +8,9 @@ pub enum ControlError { NotSupported(String), PermissionDenied(String), InvalidProfile(String), + InvalidGovernor(String), + ParseError(String), + ReadError(String), } impl From for ControlError { @@ -35,6 +38,15 @@ impl std::fmt::Display for ControlError { "Invalid platform control profile {s} supplied, please provide a valid one." ) } + Self::InvalidGovernor(s) => { + write!(f, "Invalid governor: {s}") + } + Self::ParseError(s) => { + write!(f, "Failed to parse value: {s}") + } + Self::ReadError(s) => { + write!(f, "Failed to read sysfs path: {s}") + } } } } From 00805d2808e529377b47f4cfb3017d37838e34ff Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 15 May 2025 00:05:06 +0300 Subject: [PATCH 4/5] cpu: streamline governor setting; consolidate sharaed logic --- src/cli/debug.rs | 2 +- src/cpu.rs | 45 ++++++++++--- src/engine.rs | 167 +++++++++++++++++----------------------------- src/monitor.rs | 17 ++--- src/util/error.rs | 4 ++ 5 files changed, 108 insertions(+), 127 deletions(-) diff --git a/src/cli/debug.rs b/src/cli/debug.rs index c34565d..86371cf 100644 --- a/src/cli/debug.rs +++ b/src/cli/debug.rs @@ -246,7 +246,7 @@ fn check_and_print_sysfs_path(path: &str, description: &str) { fn is_systemd_service_active(service_name: &str) -> Result> { let output = Command::new("systemctl") .arg("is-active") - .arg(format!("{}.service", service_name)) + .arg(format!("{service_name}.service")) .stdout(Stdio::piped()) // capture stdout instead of letting it print .stderr(Stdio::null()) // redirect stderr to null .output()?; diff --git a/src/cpu.rs b/src/cpu.rs index e8d4bb6..f8dd3f4 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,6 +1,7 @@ use crate::core::{GovernorOverrideMode, TurboSetting}; use crate::util::error::ControlError; use core::str; +use std::path::PathBuf; use std::{fs, io, path::Path, string::ToString}; pub type Result = std::result::Result; @@ -8,12 +9,15 @@ pub type Result = std::result::Result; // Write a value to a sysfs file fn write_sysfs_value(path: impl AsRef, value: &str) -> Result<()> { let p = path.as_ref(); + fs::write(p, value).map_err(|e| { let error_msg = format!("Path: {:?}, Value: '{}', Error: {}", p.display(), value, e); - if e.kind() == io::ErrorKind::PermissionDenied { - ControlError::PermissionDenied(error_msg) - } else { - ControlError::WriteError(error_msg) + match e.kind() { + io::ErrorKind::PermissionDenied => ControlError::PermissionDenied(error_msg), + io::ErrorKind::NotFound => { + ControlError::PathMissing(format!("Path '{}' does not exist", p.display())) + } + _ => ControlError::WriteError(error_msg), } }) } @@ -82,7 +86,10 @@ pub fn set_governor(governor: &str, core_id: Option) -> Result<()> { // First, check if the requested governor is available on the system let available_governors = get_available_governors()?; - if !available_governors.contains(&governor.to_string()) { + if !available_governors + .iter() + .any(|g| g.eq_ignore_ascii_case(governor)) + { return Err(ControlError::InvalidGovernor(format!( "Governor '{}' is not available. Available governors: {}", governor, @@ -106,19 +113,35 @@ pub fn set_governor(governor: &str, core_id: Option) -> Result<()> { /// Retrieves the list of available CPU governors on the system pub fn get_available_governors() -> Result> { - // Check cpu0 for available governors - let path = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"; - if !Path::new(path).exists() { + // Prefer cpu0, fall back to first cpu with cpufreq + let mut governor_path = + PathBuf::from("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"); + if !governor_path.exists() { + let core_count = get_logical_core_count()?; + let candidate = (0..core_count) + .map(|i| format!("/sys/devices/system/cpu/cpu{i}/cpufreq/scaling_available_governors")) + .find(|path| Path::new(path).exists()); + if let Some(path) = candidate { + governor_path = path.into(); + } + } + if !governor_path.exists() { return Err(ControlError::NotSupported( "Could not determine available governors".to_string(), )); } - let content = fs::read_to_string(path).map_err(|e| { + let content = fs::read_to_string(&governor_path).map_err(|e| { if e.kind() == io::ErrorKind::PermissionDenied { - ControlError::PermissionDenied(format!("Permission denied reading from {path}")) + ControlError::PermissionDenied(format!( + "Permission denied reading from {}", + governor_path.display() + )) } else { - ControlError::ReadError(format!("Failed to read from {path}: {e}")) + ControlError::ReadError(format!( + "Failed to read from {}: {e}", + governor_path.display() + )) } })?; diff --git a/src/engine.rs b/src/engine.rs index 4f94ff6..be49b66 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,6 +4,38 @@ use crate::cpu::{self}; use crate::util::error::{ControlError, EngineError}; use log::{debug, info, warn}; +/// Try applying a CPU feature and handle common error cases. Centralizes the where we +/// previously did: +/// 1. Try to apply a feature setting +/// 2. If not supported, log a warning and continue +/// 3. If other error, propagate the error +fn try_apply_feature( + feature_name: &str, + value_description: &str, + apply_fn: F, +) -> Result<(), EngineError> +where + F: FnOnce() -> Result, +{ + info!("Setting {feature_name} to '{value_description}'"); + + match apply_fn() { + Ok(_) => Ok(()), + Err(e) => { + if matches!(e, ControlError::NotSupported(_)) + || matches!(e, ControlError::PathMissing(_)) + { + warn!( + "{feature_name} setting is not supported on this system. Skipping {feature_name} configuration." + ); + Ok(()) + } else { + Err(EngineError::ControlError(e)) + } + } + } +} + /// Determines the appropriate CPU profile based on power status or forced mode, /// and applies the settings using functions from the `cpu` module. pub fn determine_and_apply_settings( @@ -18,17 +50,7 @@ pub fn determine_and_apply_settings( override_governor.trim() ); - // Check if the governor is available before applying - let available_governors = report.cpu_global.available_governors.clone(); - if !available_governors.contains(&override_governor.trim().to_string()) { - return Err(EngineError::ConfigurationError(format!( - "Governor '{}' from override file is not available on this system. Available governors: {}", - override_governor.trim(), - available_governors.join(", ") - ))); - } - - // Apply the override governor setting + // Apply the override governor setting - validation is handled by set_governor cpu::set_governor(override_governor.trim(), None)?; } @@ -64,15 +86,19 @@ pub fn determine_and_apply_settings( // Apply settings from selected_profile_config if let Some(governor) = &selected_profile_config.governor { - // Check if the governor is available before trying to set it - if report.cpu_global.available_governors.contains(governor) { - info!("Setting governor to '{governor}'"); - cpu::set_governor(governor, None)?; - } else { - let available = report.cpu_global.available_governors.join(", "); - warn!( - "Configured governor '{governor}' is not available on this system. Available governors: {available}. Skipping." - ); + info!("Setting governor to '{governor}'"); + // Let set_governor handle the validation + if let Err(e) = cpu::set_governor(governor, None) { + // If the governor is not available, log a warning + if matches!(e, ControlError::InvalidGovernor(_)) + || matches!(e, ControlError::NotSupported(_)) + { + warn!( + "Configured governor '{governor}' is not available on this system. Skipping." + ); + } else { + return Err(e.into()); + } } } @@ -84,110 +110,37 @@ pub fn determine_and_apply_settings( manage_auto_turbo(report, selected_profile_config)?; } _ => { - // Try to set turbo, but handle the error gracefully - if let Err(e) = cpu::set_turbo(turbo_setting) { - // If the feature is not supported, just log a warning instead of failing - if matches!(e, ControlError::NotSupported(_)) { - warn!( - "Turbo boost control is not supported on this system. Skipping turbo setting." - ); - } else { - // For other errors, propagate them - return Err(EngineError::ControlError(e)); - } - } + try_apply_feature("Turbo boost", &format!("{turbo_setting:?}"), || { + cpu::set_turbo(turbo_setting) + })?; } } } if let Some(epp) = &selected_profile_config.epp { - info!("Setting EPP to '{epp}'"); - // Try to set EPP, but handle the error gracefully - if let Err(e) = cpu::set_epp(epp, None) { - // If the feature is not supported, just log a warning instead of failing - if matches!(e, ControlError::NotSupported(_)) - || e.to_string().contains("No such file or directory") - { - warn!("EPP setting is not supported on this system. Skipping EPP configuration."); - } else { - return Err(EngineError::ControlError(e)); - } - } + try_apply_feature("EPP", epp, || cpu::set_epp(epp, None))?; } if let Some(epb) = &selected_profile_config.epb { - info!("Setting EPB to '{epb}'"); - // Try to set EPB, but handle the error gracefully - if let Err(e) = cpu::set_epb(epb, None) { - // If the feature is not supported, just log a warning instead of failing - if matches!(e, ControlError::NotSupported(_)) - || e.to_string().contains("No such file or directory") - { - warn!("EPB setting is not supported on this system. Skipping EPB configuration."); - } else { - return Err(EngineError::ControlError(e)); - } - } + try_apply_feature("EPB", epb, || cpu::set_epb(epb, None))?; } if let Some(min_freq) = selected_profile_config.min_freq_mhz { - info!("Setting min frequency to '{min_freq} MHz'"); - if let Err(e) = cpu::set_min_frequency(min_freq, None) { - // If the feature is not supported, just log a warning instead of failing - if matches!(e, ControlError::NotSupported(_)) - || e.to_string().contains("No such file or directory") - { - warn!( - "CPU frequency control is not supported on this system. Skipping min frequency setting." - ); - } else { - return Err(EngineError::ControlError(e)); - } - } + try_apply_feature("min frequency", &format!("{min_freq} MHz"), || { + cpu::set_min_frequency(min_freq, None) + })?; } if let Some(max_freq) = selected_profile_config.max_freq_mhz { - info!("Setting max frequency to '{max_freq} MHz'"); - if let Err(e) = cpu::set_max_frequency(max_freq, None) { - // If the feature is not supported, just log a warning instead of failing - if matches!(e, ControlError::NotSupported(_)) - || e.to_string().contains("No such file or directory") - { - warn!( - "CPU frequency control is not supported on this system. Skipping max frequency setting." - ); - } else { - return Err(EngineError::ControlError(e)); - } - } + try_apply_feature("max frequency", &format!("{max_freq} MHz"), || { + cpu::set_max_frequency(max_freq, None) + })?; } if let Some(profile) = &selected_profile_config.platform_profile { - info!("Setting platform profile to '{profile}'"); - // Try to get available platform profiles first to validate - match cpu::get_platform_profiles() { - Ok(available_profiles) => { - if available_profiles.contains(profile) { - cpu::set_platform_profile(profile)?; - } else { - warn!( - "Platform profile '{}' is not available. Available profiles: {}. Skipping.", - profile, - available_profiles.join(", ") - ); - } - } - Err(e) => { - // If platform profiles are not supported, log a warning and continue - if matches!(e, ControlError::NotSupported(_)) { - warn!( - "Platform profile control is not supported on this system. Skipping profile setting." - ); - } else { - return Err(EngineError::ControlError(e)); - } - } - } + try_apply_feature("platform profile", profile, || { + cpu::set_platform_profile(profile) + })?; } debug!("Profile settings applied successfully."); diff --git a/src/monitor.rs b/src/monitor.rs index 91997a1..a3b6b83 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -408,14 +408,15 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { let mut cpufreq_base_path_buf = PathBuf::from("/sys/devices/system/cpu/cpu0/cpufreq/"); if !cpufreq_base_path_buf.exists() { - // Fallback: find first available CPU with cpufreq - for i in 1..=get_logical_core_count().unwrap_or(1) { - let test_path = format!("/sys/devices/system/cpu/cpu{}/cpufreq/", i - 1); - let test_path_buf = PathBuf::from(&test_path); - if test_path_buf.exists() { - cpufreq_base_path_buf = test_path_buf; - break; - } + let core_count = get_logical_core_count().unwrap_or_else(|e| { + eprintln!("Warning: {e}"); + 0 + }); + let path = (0..core_count) + .map(|i| PathBuf::from(format!("/sys/devices/system/cpu/cpu{i}/cpufreq/"))) + .find(|path| path.exists()); + if let Some(test_path_buf) = path { + cpufreq_base_path_buf = test_path_buf; } } diff --git a/src/util/error.rs b/src/util/error.rs index 7b92b8f..4f9391f 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -11,6 +11,7 @@ pub enum ControlError { InvalidGovernor(String), ParseError(String), ReadError(String), + PathMissing(String), } impl From for ControlError { @@ -47,6 +48,9 @@ impl std::fmt::Display for ControlError { Self::ReadError(s) => { write!(f, "Failed to read sysfs path: {s}") } + Self::PathMissing(s) => { + write!(f, "Path missing: {s}") + } } } } From 723ce1b7b8bd70a9671bfb7cddf82563eab8dd5c Mon Sep 17 00:00:00 2001 From: diniamo Date: Thu, 15 May 2025 20:41:53 +0200 Subject: [PATCH 5/5] nix: add package option, avoid empty config, move to services --- nix/module.nix | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/nix/module.nix b/nix/module.nix index 5f43bf5..76d298f 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -5,18 +5,21 @@ inputs: { ... }: let inherit (lib.modules) mkIf; - inherit (lib.options) mkOption mkEnableOption; + inherit (lib.options) mkOption mkEnableOption mkPackageOption; inherit (lib.types) submodule; + inherit (lib.lists) optional; inherit (lib.meta) getExe; - cfg = config.programs.superfreq; - - defaultPackage = inputs.self.packages.${pkgs.stdenv.system}.default; + cfg = config.services.superfreq; format = pkgs.formats.toml {}; + cfgFile = format.generate "superfreq-config.toml" cfg.settings; in { - options.programs.superfreq = { + options.services.superfreq = { enable = mkEnableOption "Automatic CPU speed & power optimizer for Linux"; + package = mkPackageOption inputs.self.packages.${pkgs.stdenv.system} "superfreq" { + pkgsText = "self.packages.\${pkgs.stdenv.system}"; + }; settings = mkOption { default = {}; @@ -26,23 +29,18 @@ in { }; config = mkIf cfg.enable { - environment.systemPackages = [defaultPackage]; + environment.systemPackages = [cfg.package]; - systemd = { - packages = [defaultPackage]; - services.superfreq = { - wantedBy = ["multi-user.target"]; - serviceConfig = let - cfgFile = format.generate "superfreq-config.toml" cfg.settings; - in { - Environment = ["SUPERFREQ_CONFIG=${cfgFile}"]; - WorkingDirectory = ""; - ExecStart = "${getExe defaultPackage} daemon --verbose"; - Restart = "on-failure"; + systemd.services.superfreq = { + wantedBy = ["multi-user.target"]; + serviceConfig = { + Environment = optional (cfg.settings != {}) ["SUPERFREQ_CONFIG=${cfgFile}"]; + WorkingDirectory = ""; + ExecStart = "${getExe cfg.package} daemon --verbose"; + Restart = "on-failure"; - RuntimeDirectory = "superfreq"; - RuntimeDirectoryMode = "0755"; - }; + RuntimeDirectory = "superfreq"; + RuntimeDirectoryMode = "0755"; }; }; @@ -57,7 +55,7 @@ in { { assertion = !config.services.auto-cpufreq.enable; message = '' - You have set programs.auto-cpufreq.enable = true; + You have set services.auto-cpufreq.enable = true; which conflicts with Superfreq. ''; }