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

cpu: streamline governor setting; consolidate sharaed logic

This commit is contained in:
NotAShelf 2025-05-15 00:05:06 +03:00
parent 58ba603afc
commit 00805d2808
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
5 changed files with 108 additions and 127 deletions

View file

@ -246,7 +246,7 @@ fn check_and_print_sysfs_path(path: &str, description: &str) {
fn is_systemd_service_active(service_name: &str) -> Result<bool, Box<dyn Error>> { fn is_systemd_service_active(service_name: &str) -> Result<bool, Box<dyn Error>> {
let output = Command::new("systemctl") let output = Command::new("systemctl")
.arg("is-active") .arg("is-active")
.arg(format!("{}.service", service_name)) .arg(format!("{service_name}.service"))
.stdout(Stdio::piped()) // capture stdout instead of letting it print .stdout(Stdio::piped()) // capture stdout instead of letting it print
.stderr(Stdio::null()) // redirect stderr to null .stderr(Stdio::null()) // redirect stderr to null
.output()?; .output()?;

View file

@ -1,6 +1,7 @@
use crate::core::{GovernorOverrideMode, TurboSetting}; use crate::core::{GovernorOverrideMode, TurboSetting};
use crate::util::error::ControlError; use crate::util::error::ControlError;
use core::str; use core::str;
use std::path::PathBuf;
use std::{fs, io, path::Path, string::ToString}; use std::{fs, io, path::Path, string::ToString};
pub type Result<T, E = ControlError> = std::result::Result<T, E>; pub type Result<T, E = ControlError> = std::result::Result<T, E>;
@ -8,12 +9,15 @@ pub type Result<T, E = ControlError> = std::result::Result<T, E>;
// Write a value to a sysfs file // Write a value to a sysfs file
fn write_sysfs_value(path: impl AsRef<Path>, value: &str) -> Result<()> { fn write_sysfs_value(path: impl AsRef<Path>, value: &str) -> Result<()> {
let p = path.as_ref(); let p = path.as_ref();
fs::write(p, value).map_err(|e| { fs::write(p, value).map_err(|e| {
let error_msg = format!("Path: {:?}, Value: '{}', Error: {}", p.display(), value, e); let error_msg = format!("Path: {:?}, Value: '{}', Error: {}", p.display(), value, e);
if e.kind() == io::ErrorKind::PermissionDenied { match e.kind() {
ControlError::PermissionDenied(error_msg) io::ErrorKind::PermissionDenied => ControlError::PermissionDenied(error_msg),
} else { io::ErrorKind::NotFound => {
ControlError::WriteError(error_msg) 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<u32>) -> Result<()> {
// First, check if the requested governor is available on the system // First, check if the requested governor is available on the system
let available_governors = get_available_governors()?; 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!( return Err(ControlError::InvalidGovernor(format!(
"Governor '{}' is not available. Available governors: {}", "Governor '{}' is not available. Available governors: {}",
governor, governor,
@ -106,19 +113,35 @@ pub fn set_governor(governor: &str, core_id: Option<u32>) -> Result<()> {
/// Retrieves the list of available CPU governors on the system /// Retrieves the list of available CPU governors on the system
pub fn get_available_governors() -> Result<Vec<String>> { pub fn get_available_governors() -> Result<Vec<String>> {
// Check cpu0 for available governors // Prefer cpu0, fall back to first cpu with cpufreq
let path = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors"; let mut governor_path =
if !Path::new(path).exists() { 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( return Err(ControlError::NotSupported(
"Could not determine available governors".to_string(), "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 { 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 { } else {
ControlError::ReadError(format!("Failed to read from {path}: {e}")) ControlError::ReadError(format!(
"Failed to read from {}: {e}",
governor_path.display()
))
} }
})?; })?;

View file

@ -4,6 +4,38 @@ use crate::cpu::{self};
use crate::util::error::{ControlError, EngineError}; use crate::util::error::{ControlError, EngineError};
use log::{debug, info, warn}; 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<F, T>(
feature_name: &str,
value_description: &str,
apply_fn: F,
) -> Result<(), EngineError>
where
F: FnOnce() -> Result<T, ControlError>,
{
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, /// Determines the appropriate CPU profile based on power status or forced mode,
/// and applies the settings using functions from the `cpu` module. /// and applies the settings using functions from the `cpu` module.
pub fn determine_and_apply_settings( pub fn determine_and_apply_settings(
@ -18,17 +50,7 @@ pub fn determine_and_apply_settings(
override_governor.trim() override_governor.trim()
); );
// Check if the governor is available before applying // Apply the override governor setting - validation is handled by set_governor
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)?; cpu::set_governor(override_governor.trim(), None)?;
} }
@ -64,15 +86,19 @@ pub fn determine_and_apply_settings(
// Apply settings from selected_profile_config // Apply settings from selected_profile_config
if let Some(governor) = &selected_profile_config.governor { 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}'"); info!("Setting governor to '{governor}'");
cpu::set_governor(governor, None)?; // Let set_governor handle the validation
} else { if let Err(e) = cpu::set_governor(governor, None) {
let available = report.cpu_global.available_governors.join(", "); // If the governor is not available, log a warning
if matches!(e, ControlError::InvalidGovernor(_))
|| matches!(e, ControlError::NotSupported(_))
{
warn!( warn!(
"Configured governor '{governor}' is not available on this system. Available governors: {available}. Skipping." "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)?; manage_auto_turbo(report, selected_profile_config)?;
} }
_ => { _ => {
// Try to set turbo, but handle the error gracefully try_apply_feature("Turbo boost", &format!("{turbo_setting:?}"), || {
if let Err(e) = cpu::set_turbo(turbo_setting) { 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 { if let Some(epp) = &selected_profile_config.epp {
info!("Setting EPP to '{epp}'"); try_apply_feature("EPP", 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 { if let Some(epb) = &selected_profile_config.epb {
info!("Setting EPB to '{epb}'"); try_apply_feature("EPB", 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 { if let Some(min_freq) = selected_profile_config.min_freq_mhz {
info!("Setting min frequency to '{min_freq} MHz'"); try_apply_feature("min frequency", &format!("{min_freq} MHz"), || {
if let Err(e) = cpu::set_min_frequency(min_freq, None) { 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 { if let Some(max_freq) = selected_profile_config.max_freq_mhz {
info!("Setting max frequency to '{max_freq} MHz'"); try_apply_feature("max frequency", &format!("{max_freq} MHz"), || {
if let Err(e) = cpu::set_max_frequency(max_freq, None) { 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 { if let Some(profile) = &selected_profile_config.platform_profile {
info!("Setting platform profile to '{profile}'"); try_apply_feature("platform profile", profile, || {
// Try to get available platform profiles first to validate cpu::set_platform_profile(profile)
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."); debug!("Profile settings applied successfully.");

View file

@ -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/"); let mut cpufreq_base_path_buf = PathBuf::from("/sys/devices/system/cpu/cpu0/cpufreq/");
if !cpufreq_base_path_buf.exists() { if !cpufreq_base_path_buf.exists() {
// Fallback: find first available CPU with cpufreq let core_count = get_logical_core_count().unwrap_or_else(|e| {
for i in 1..=get_logical_core_count().unwrap_or(1) { eprintln!("Warning: {e}");
let test_path = format!("/sys/devices/system/cpu/cpu{}/cpufreq/", i - 1); 0
let test_path_buf = PathBuf::from(&test_path); });
if test_path_buf.exists() { 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; cpufreq_base_path_buf = test_path_buf;
break;
}
} }
} }

View file

@ -11,6 +11,7 @@ pub enum ControlError {
InvalidGovernor(String), InvalidGovernor(String),
ParseError(String), ParseError(String),
ReadError(String), ReadError(String),
PathMissing(String),
} }
impl From<io::Error> for ControlError { impl From<io::Error> for ControlError {
@ -47,6 +48,9 @@ impl std::fmt::Display for ControlError {
Self::ReadError(s) => { Self::ReadError(s) => {
write!(f, "Failed to read sysfs path: {s}") write!(f, "Failed to read sysfs path: {s}")
} }
Self::PathMissing(s) => {
write!(f, "Path missing: {s}")
}
} }
} }
} }