mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 08:57:46 +00:00
treewide: streamline error handling; leverage thiserror and anyhow
This commit is contained in:
parent
99fbfc0ea7
commit
32422f2b4f
7 changed files with 81 additions and 202 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue