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:
parent
58ba603afc
commit
00805d2808
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>> {
|
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()?;
|
||||||
|
|
45
src/cpu.rs
45
src/cpu.rs
|
@ -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()
|
||||||
|
))
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
161
src/engine.rs
161
src/engine.rs
|
@ -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.");
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue