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

treewide: streamline error handling; leverage thiserror and anyhow

This commit is contained in:
NotAShelf 2025-05-18 15:39:49 +03:00
parent 99fbfc0ea7
commit 32422f2b4f
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
7 changed files with 81 additions and 202 deletions

8
Cargo.lock generated
View file

@ -76,6 +76,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "anyhow"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.4.0"
@ -539,6 +545,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
name = "superfreq" name = "superfreq"
version = "0.3.1" version = "0.3.1"
dependencies = [ dependencies = [
"anyhow",
"chrono", "chrono",
"clap", "clap",
"ctrlc", "ctrlc",
@ -547,6 +554,7 @@ dependencies = [
"log", "log",
"num_cpus", "num_cpus",
"serde", "serde",
"thiserror",
"toml", "toml",
] ]

View file

@ -16,3 +16,5 @@ ctrlc = "3.4"
chrono = "0.4" chrono = "0.4"
log = "0.4" log = "0.4"
env_logger = "0.11" env_logger = "0.11"
thiserror = "2.0"
anyhow = "1.0"

View file

@ -98,37 +98,18 @@ pub struct AppConfig {
} }
// Error type for config loading // Error type for config loading
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
pub enum ConfigError { pub enum ConfigError {
Io(std::io::Error), #[error("I/O error: {0}")]
Toml(toml::de::Error), Io(#[from] std::io::Error),
#[error("TOML parsing error: {0}")]
Toml(#[from] toml::de::Error),
#[error("Configuration validation error: {0}")]
Validation(String), Validation(String),
} }
impl From<std::io::Error> for ConfigError {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl From<toml::de::Error> for ConfigError {
fn from(err: toml::de::Error) -> Self {
Self::Toml(err)
}
}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Toml(e) => write!(f, "TOML parsing error: {e}"),
Self::Validation(s) => write!(f, "Configuration validation error: {s}"),
}
}
}
impl std::error::Error for ConfigError {}
// Intermediate structs for TOML parsing // Intermediate structs for TOML parsing
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ProfileConfigToml { pub struct ProfileConfigToml {

View file

@ -109,7 +109,7 @@ pub fn set_governor(governor: &str, core_id: Option<u32>) -> Result<()> {
let (is_valid, available_governors) = is_governor_valid(governor)?; let (is_valid, available_governors) = is_governor_valid(governor)?;
if !is_valid { if !is_valid {
return Err(ControlError::InvalidValueError(format!( return Err(ControlError::InvalidGovernor(format!(
"Governor '{}' is not available on this system. Valid governors: {}", "Governor '{}' is not available on this system. Valid governors: {}",
governor, governor,
available_governors.join(", ") available_governors.join(", ")
@ -432,7 +432,7 @@ fn read_sysfs_value_as_u32(path: &str) -> Result<u32> {
content content
.trim() .trim()
.parse::<u32>() .parse::<u32>()
.map_err(|e| ControlError::ReadError(format!("Failed to parse value from {path}: {e}"))) .map_err(|e| ControlError::ParseError(format!("Failed to parse value from {path}: {e}")))
} }
fn validate_min_frequency(core_id: u32, new_min_freq_mhz: u32) -> Result<()> { fn validate_min_frequency(core_id: u32, new_min_freq_mhz: u32) -> Result<()> {

View file

@ -61,7 +61,10 @@ fn idle_multiplier(idle_secs: u64) -> f32 {
/// Calculate optimal polling interval based on system conditions and history /// Calculate optimal polling interval based on system conditions and history
/// ///
/// Returns Ok with the calculated interval, or Err if the configuration is invalid /// Returns Ok with the calculated interval, or Err if the configuration is invalid
fn compute_new(params: &IntervalParams, system_history: &SystemHistory) -> Result<u64, ControlError> { fn compute_new(
params: &IntervalParams,
system_history: &SystemHistory,
) -> Result<u64, ControlError> {
// Use the centralized validation function // Use the centralized validation function
validate_poll_intervals(params.min_interval, params.max_interval)?; validate_poll_intervals(params.min_interval, params.max_interval)?;
@ -134,8 +137,8 @@ fn compute_new(params: &IntervalParams, system_history: &SystemHistory) -> Resul
const TOTAL_WEIGHT: u128 = PREVIOUS_VALUE_WEIGHT + NEW_VALUE_WEIGHT; // 10 const TOTAL_WEIGHT: u128 = PREVIOUS_VALUE_WEIGHT + NEW_VALUE_WEIGHT; // 10
// XXX: Use u128 arithmetic to avoid overflow with large interval values // XXX: Use u128 arithmetic to avoid overflow with large interval values
let result = (cached as u128 * PREVIOUS_VALUE_WEIGHT let result = (u128::from(cached) * PREVIOUS_VALUE_WEIGHT
+ new_interval as u128 * NEW_VALUE_WEIGHT) + u128::from(new_interval) * NEW_VALUE_WEIGHT)
/ TOTAL_WEIGHT; / TOTAL_WEIGHT;
result as u64 result as u64
@ -381,20 +384,19 @@ impl SystemHistory {
fn validate_poll_intervals(min_interval: u64, max_interval: u64) -> Result<(), ControlError> { fn validate_poll_intervals(min_interval: u64, max_interval: u64) -> Result<(), ControlError> {
if min_interval < 1 { if min_interval < 1 {
return Err(ControlError::InvalidValueError( return Err(ControlError::InvalidValueError(
"min_interval must be ≥ 1".to_string() "min_interval must be ≥ 1".to_string(),
)); ));
} }
if max_interval < 1 { if max_interval < 1 {
return Err(ControlError::InvalidValueError( return Err(ControlError::InvalidValueError(
"max_interval must be ≥ 1".to_string() "max_interval must be ≥ 1".to_string(),
)); ));
} }
if max_interval >= min_interval { if max_interval >= min_interval {
Ok(()) Ok(())
} else { } else {
Err(ControlError::InvalidValueError(format!( Err(ControlError::InvalidValueError(format!(
"Invalid interval configuration: max_interval ({}) is less than min_interval ({})", "Invalid interval configuration: max_interval ({max_interval}) is less than min_interval ({min_interval})"
max_interval, min_interval
))) )))
} }
} }

View file

@ -207,7 +207,7 @@ pub fn determine_and_apply_settings(
// Let set_governor handle the validation // Let set_governor handle the validation
if let Err(e) = cpu::set_governor(governor, None) { if let Err(e) = cpu::set_governor(governor, None) {
// If the governor is not available, log a warning // If the governor is not available, log a warning
if matches!(e, ControlError::InvalidValueError(_)) if matches!(e, ControlError::InvalidGovernor(_))
|| matches!(e, ControlError::NotSupported(_)) || matches!(e, ControlError::NotSupported(_))
{ {
warn!( warn!(

View file

@ -1,194 +1,80 @@
use std::io; use std::io;
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
pub enum ControlError { pub enum ControlError {
Io(io::Error), #[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Failed to write to sysfs path: {0}")]
WriteError(String), WriteError(String),
#[error("Failed to read sysfs path: {0}")]
ReadError(String), ReadError(String),
#[error("Invalid value for setting: {0}")]
InvalidValueError(String), InvalidValueError(String),
#[error("Control action not supported: {0}")]
NotSupported(String), NotSupported(String),
#[error("Permission denied: {0}. Try running with sudo.")]
PermissionDenied(String), PermissionDenied(String),
#[error("Invalid platform control profile {0} supplied, please provide a valid one.")]
InvalidProfile(String), InvalidProfile(String),
#[error("Invalid governor: {0}")]
InvalidGovernor(String), InvalidGovernor(String),
#[error("Failed to parse value: {0}")]
ParseError(String), ParseError(String),
#[error("Path missing: {0}")]
PathMissing(String), PathMissing(String),
} }
impl From<io::Error> for ControlError { #[derive(Debug, thiserror::Error)]
fn from(err: io::Error) -> Self {
match err.kind() {
io::ErrorKind::PermissionDenied => Self::PermissionDenied(err.to_string()),
_ => Self::Io(err),
}
}
}
impl std::fmt::Display for ControlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::WriteError(s) => write!(f, "Failed to write to sysfs path: {s}"),
Self::ReadError(s) => write!(f, "Failed to read sysfs path: {s}"),
Self::InvalidValueError(s) => write!(f, "Invalid value for setting: {s}"),
Self::NotSupported(s) => write!(f, "Control action not supported: {s}"),
Self::PermissionDenied(s) => {
write!(f, "Permission denied: {s}. Try running with sudo.")
}
Self::InvalidProfile(s) => {
write!(
f,
"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::PathMissing(s) => {
write!(f, "Path missing: {s}")
}
}
}
}
impl std::error::Error for ControlError {}
#[derive(Debug)]
pub enum SysMonitorError { pub enum SysMonitorError {
Io(io::Error), #[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Failed to read sysfs path: {0}")]
ReadError(String), ReadError(String),
#[error("Failed to parse value: {0}")]
ParseError(String), ParseError(String),
#[error("Failed to parse /proc/stat: {0}")]
ProcStatParseError(String), ProcStatParseError(String),
} }
impl From<io::Error> for SysMonitorError { #[derive(Debug, thiserror::Error)]
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl std::fmt::Display for SysMonitorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::ReadError(s) => write!(f, "Failed to read sysfs path: {s}"),
Self::ParseError(s) => write!(f, "Failed to parse value: {s}"),
Self::ProcStatParseError(s) => {
write!(f, "Failed to parse /proc/stat: {s}")
}
}
}
}
impl std::error::Error for SysMonitorError {}
#[derive(Debug)]
pub enum EngineError { pub enum EngineError {
ControlError(ControlError), #[error("CPU control error: {0}")]
ControlError(#[from] ControlError),
#[error("Configuration error: {0}")]
ConfigurationError(String), ConfigurationError(String),
} }
impl From<ControlError> for EngineError {
fn from(err: ControlError) -> Self {
Self::ControlError(err)
}
}
impl std::fmt::Display for EngineError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ControlError(e) => write!(f, "CPU control error: {e}"),
Self::ConfigurationError(s) => write!(f, "Configuration error: {s}"),
}
}
}
impl std::error::Error for EngineError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ControlError(e) => Some(e),
Self::ConfigurationError(_) => None,
}
}
}
// A unified error type for the entire application // A unified error type for the entire application
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
pub enum AppError { pub enum AppError {
Control(ControlError), #[error("{0}")]
Monitor(SysMonitorError), Control(#[from] ControlError),
Engine(EngineError),
Config(crate::config::ConfigError), #[error("{0}")]
Monitor(#[from] SysMonitorError),
#[error("{0}")]
Engine(#[from] EngineError),
#[error("{0}")]
Config(#[from] crate::config::ConfigError),
#[error("{0}")]
Generic(String), Generic(String),
Io(io::Error),
}
impl From<ControlError> for AppError { #[error("I/O error: {0}")]
fn from(err: ControlError) -> Self { Io(#[from] io::Error),
Self::Control(err)
}
}
impl From<SysMonitorError> for AppError {
fn from(err: SysMonitorError) -> Self {
Self::Monitor(err)
}
}
impl From<EngineError> for AppError {
fn from(err: EngineError) -> Self {
Self::Engine(err)
}
}
impl From<crate::config::ConfigError> for AppError {
fn from(err: crate::config::ConfigError) -> Self {
Self::Config(err)
}
}
impl From<io::Error> for AppError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl From<String> for AppError {
fn from(err: String) -> Self {
Self::Generic(err)
}
}
impl From<&str> for AppError {
fn from(err: &str) -> Self {
Self::Generic(err.to_string())
}
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Control(e) => write!(f, "{e}"),
Self::Monitor(e) => write!(f, "{e}"),
Self::Engine(e) => write!(f, "{e}"),
Self::Config(e) => write!(f, "{e}"),
Self::Generic(s) => write!(f, "{s}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for AppError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Control(e) => Some(e),
Self::Monitor(e) => Some(e),
Self::Engine(e) => Some(e),
Self::Config(e) => Some(e),
Self::Generic(_) => None,
Self::Io(e) => Some(e),
}
}
} }