mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
Merge pull request #19 from NotAShelf/better-sysfs-errs
cpu: streamline governor setting; consolidate sharaed logic
This commit is contained in:
commit
5531358da2
5 changed files with 108 additions and 127 deletions
|
@ -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>> {
|
||||
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()?;
|
||||
|
|
45
src/cpu.rs
45
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<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
|
||||
fn write_sysfs_value(path: impl AsRef<Path>, 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<u32>) -> 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<u32>) -> Result<()> {
|
|||
|
||||
/// Retrieves the list of available CPU governors on the system
|
||||
pub fn get_available_governors() -> Result<Vec<String>> {
|
||||
// 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()
|
||||
))
|
||||
}
|
||||
})?;
|
||||
|
||||
|
|
167
src/engine.rs
167
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<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,
|
||||
/// 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.");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ pub enum ControlError {
|
|||
InvalidGovernor(String),
|
||||
ParseError(String),
|
||||
ReadError(String),
|
||||
PathMissing(String),
|
||||
}
|
||||
|
||||
impl From<io::Error> 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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue