mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
wip unsound broken malfunctioning changes to make it compile
This commit is contained in:
parent
004e8e2a9c
commit
a14d88cee7
11 changed files with 106 additions and 591 deletions
265
src/cli/debug.rs
265
src/cli/debug.rs
|
@ -1,265 +0,0 @@
|
|||
use crate::config::AppConfig;
|
||||
use crate::cpu;
|
||||
use crate::monitor;
|
||||
use crate::util::error::AppError;
|
||||
use std::fs;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Prints comprehensive debug information about the system
|
||||
pub fn run_debug(config: &AppConfig) -> Result<(), AppError> {
|
||||
println!("=== SUPERFREQ DEBUG INFORMATION ===");
|
||||
println!("Version: {}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
// Current date and time
|
||||
println!("Timestamp: {}", jiff::Timestamp::now());
|
||||
|
||||
// Kernel information
|
||||
if let Ok(kernel_info) = get_kernel_info() {
|
||||
println!("Kernel Version: {kernel_info}");
|
||||
} else {
|
||||
println!("Kernel Version: Unable to determine");
|
||||
}
|
||||
|
||||
// System uptime
|
||||
if let Ok(uptime) = get_system_uptime() {
|
||||
println!(
|
||||
"System Uptime: {} hours, {} minutes",
|
||||
uptime.as_secs() / 3600,
|
||||
(uptime.as_secs() % 3600) / 60
|
||||
);
|
||||
} else {
|
||||
println!("System Uptime: Unable to determine");
|
||||
}
|
||||
|
||||
// Get system information
|
||||
match monitor::collect_system_report(config) {
|
||||
Ok(report) => {
|
||||
println!("\n--- SYSTEM INFORMATION ---");
|
||||
println!("CPU Model: {}", report.system_info.cpu_model);
|
||||
println!("Architecture: {}", report.system_info.architecture);
|
||||
println!(
|
||||
"Linux Distribution: {}",
|
||||
report.system_info.linux_distribution
|
||||
);
|
||||
|
||||
println!("\n--- CONFIGURATION ---");
|
||||
println!("Current Configuration: {config:#?}");
|
||||
|
||||
// Print important sysfs paths and whether they exist
|
||||
println!("\n--- SYSFS PATHS ---");
|
||||
check_and_print_sysfs_path(
|
||||
"/sys/devices/system/cpu/intel_pstate/no_turbo",
|
||||
"Intel P-State Turbo Control",
|
||||
);
|
||||
check_and_print_sysfs_path(
|
||||
"/sys/devices/system/cpu/cpufreq/boost",
|
||||
"Generic CPU Boost Control",
|
||||
);
|
||||
check_and_print_sysfs_path(
|
||||
"/sys/devices/system/cpu/amd_pstate/cpufreq/boost",
|
||||
"AMD P-State Boost Control",
|
||||
);
|
||||
check_and_print_sysfs_path(
|
||||
"/sys/firmware/acpi/platform_profile",
|
||||
"ACPI Platform Profile Control",
|
||||
);
|
||||
check_and_print_sysfs_path("/sys/class/power_supply", "Power Supply Information");
|
||||
|
||||
println!("\n--- CPU INFORMATION ---");
|
||||
println!("Current Governor: {:?}", report.cpu_global.current_governor);
|
||||
println!(
|
||||
"Available Governors: {}",
|
||||
report.cpu_global.available_governors.join(", ")
|
||||
);
|
||||
println!("Turbo Status: {:?}", report.cpu_global.turbo_status);
|
||||
println!(
|
||||
"Energy Performance Preference (EPP): {:?}",
|
||||
report.cpu_global.epp
|
||||
);
|
||||
println!("Energy Performance Bias (EPB): {:?}", report.cpu_global.epb);
|
||||
|
||||
// Add governor override information
|
||||
if let Some(override_governor) = cpu::get_governor_override() {
|
||||
println!("Governor Override: {}", override_governor.trim());
|
||||
} else {
|
||||
println!("Governor Override: None");
|
||||
}
|
||||
|
||||
println!("\n--- PLATFORM PROFILE ---");
|
||||
println!(
|
||||
"Current Platform Profile: {:?}",
|
||||
report.cpu_global.platform_profile
|
||||
);
|
||||
match cpu::get_platform_profiles() {
|
||||
Ok(profiles) => println!("Available Platform Profiles: {}", profiles.join(", ")),
|
||||
Err(_) => println!("Available Platform Profiles: Not supported on this system"),
|
||||
}
|
||||
|
||||
println!("\n--- CPU CORES DETAIL ---");
|
||||
println!("Total CPU Cores: {}", report.cpu_cores.len());
|
||||
for core in &report.cpu_cores {
|
||||
println!("Core {}:", core.core_id);
|
||||
println!(
|
||||
" Current Frequency: {} MHz",
|
||||
core.current_frequency_mhz
|
||||
.map_or_else(|| "N/A".to_string(), |f| f.to_string())
|
||||
);
|
||||
println!(
|
||||
" Min Frequency: {} MHz",
|
||||
core.min_frequency_mhz
|
||||
.map_or_else(|| "N/A".to_string(), |f| f.to_string())
|
||||
);
|
||||
println!(
|
||||
" Max Frequency: {} MHz",
|
||||
core.max_frequency_mhz
|
||||
.map_or_else(|| "N/A".to_string(), |f| f.to_string())
|
||||
);
|
||||
println!(
|
||||
" Usage: {}%",
|
||||
core.usage_percent
|
||||
.map_or_else(|| "N/A".to_string(), |u| format!("{u:.1}"))
|
||||
);
|
||||
println!(
|
||||
" Temperature: {}°C",
|
||||
core.temperature_celsius
|
||||
.map_or_else(|| "N/A".to_string(), |t| format!("{t:.1}"))
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n--- TEMPERATURE INFORMATION ---");
|
||||
println!(
|
||||
"Average CPU Temperature: {}",
|
||||
report.cpu_global.average_temperature_celsius.map_or_else(
|
||||
|| "N/A (CPU temperature sensor not detected)".to_string(),
|
||||
|t| format!("{t:.1}°C")
|
||||
)
|
||||
);
|
||||
|
||||
println!("\n--- BATTERY INFORMATION ---");
|
||||
if report.batteries.is_empty() {
|
||||
println!("No batteries found or all are ignored.");
|
||||
} else {
|
||||
for battery in &report.batteries {
|
||||
println!("Battery: {}", battery.name);
|
||||
println!(" AC Connected: {}", battery.ac_connected);
|
||||
println!(
|
||||
" Charging State: {}",
|
||||
battery.charging_state.as_deref().unwrap_or("N/A")
|
||||
);
|
||||
println!(
|
||||
" Capacity: {}%",
|
||||
battery
|
||||
.capacity_percent
|
||||
.map_or_else(|| "N/A".to_string(), |c| c.to_string())
|
||||
);
|
||||
println!(
|
||||
" Power Rate: {} W",
|
||||
battery
|
||||
.power_rate_watts
|
||||
.map_or_else(|| "N/A".to_string(), |p| format!("{p:.2}"))
|
||||
);
|
||||
println!(
|
||||
" Charge Start Threshold: {}",
|
||||
battery
|
||||
.charge_start_threshold
|
||||
.map_or_else(|| "N/A".to_string(), |t| t.to_string())
|
||||
);
|
||||
println!(
|
||||
" Charge Stop Threshold: {}",
|
||||
battery
|
||||
.charge_stop_threshold
|
||||
.map_or_else(|| "N/A".to_string(), |t| t.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n--- SYSTEM LOAD ---");
|
||||
println!(
|
||||
"Load Average (1 min): {:.2}",
|
||||
report.system_load.load_avg_1min
|
||||
);
|
||||
println!(
|
||||
"Load Average (5 min): {:.2}",
|
||||
report.system_load.load_avg_5min
|
||||
);
|
||||
println!(
|
||||
"Load Average (15 min): {:.2}",
|
||||
report.system_load.load_avg_15min
|
||||
);
|
||||
|
||||
println!("\n--- DAEMON STATUS ---");
|
||||
// Simple check for daemon status - can be expanded later
|
||||
let daemon_status = fs::metadata("/var/run/superfreq.pid").is_ok();
|
||||
println!("Daemon Running: {daemon_status}");
|
||||
|
||||
// Check for systemd service status
|
||||
if let Ok(systemd_status) = is_systemd_service_active("superfreq") {
|
||||
println!("Systemd Service Active: {systemd_status}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(AppError::Monitor(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get kernel version information
|
||||
fn get_kernel_info() -> Result<String, AppError> {
|
||||
let output = Command::new("uname")
|
||||
.arg("-r")
|
||||
.output()
|
||||
.map_err(AppError::Io)?;
|
||||
|
||||
let kernel_version = String::from_utf8(output.stdout)
|
||||
.map_err(|e| AppError::Generic(format!("Failed to parse kernel version: {e}")))?;
|
||||
Ok(kernel_version.trim().to_string())
|
||||
}
|
||||
|
||||
/// Get system uptime
|
||||
fn get_system_uptime() -> Result<Duration, AppError> {
|
||||
let uptime_str = fs::read_to_string("/proc/uptime").map_err(AppError::Io)?;
|
||||
let uptime_secs = uptime_str
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.ok_or_else(|| AppError::Generic("Invalid format in /proc/uptime file".to_string()))?
|
||||
.parse::<f64>()
|
||||
.map_err(|e| AppError::Generic(format!("Failed to parse uptime from /proc/uptime: {e}")))?;
|
||||
|
||||
Ok(Duration::from_secs_f64(uptime_secs))
|
||||
}
|
||||
|
||||
/// Check if a sysfs path exists and print its status
|
||||
fn check_and_print_sysfs_path(path: &str, description: &str) {
|
||||
let exists = std::path::Path::new(path).exists();
|
||||
println!(
|
||||
"{}: {} ({})",
|
||||
description,
|
||||
path,
|
||||
if exists { "Exists" } else { "Not Found" }
|
||||
);
|
||||
}
|
||||
|
||||
/// Check if a systemd service is active
|
||||
fn is_systemd_service_active(service_name: &str) -> Result<bool, AppError> {
|
||||
let output = Command::new("systemctl")
|
||||
.arg("is-active")
|
||||
.arg(format!("{service_name}.service"))
|
||||
.stdout(Stdio::piped()) // capture stdout instead of letting it print
|
||||
.stderr(Stdio::null()) // redirect stderr to null
|
||||
.output()
|
||||
.map_err(AppError::Io)?;
|
||||
|
||||
// Check if the command executed successfully
|
||||
if !output.status.success() {
|
||||
// Command failed - service is either not found or not active
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Command executed successfully, now check the output content
|
||||
let status = String::from_utf8(output.stdout)
|
||||
.map_err(|e| AppError::Generic(format!("Failed to parse systemctl output: {e}")))?;
|
||||
|
||||
// Explicitly verify the output is "active"
|
||||
Ok(status.trim() == "active")
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
pub mod debug;
|
|
@ -2,7 +2,9 @@
|
|||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::config::types::{AppConfig, AppConfigToml, ConfigError, DaemonConfig, ProfileConfig};
|
||||
use anyhow::Context as _;
|
||||
|
||||
use crate::config::types::{AppConfig, AppConfigToml, DaemonConfig, ProfileConfig};
|
||||
|
||||
/// The primary function to load application configuration from a specific path or from default locations.
|
||||
///
|
||||
|
@ -14,22 +16,23 @@ use crate::config::types::{AppConfig, AppConfigToml, ConfigError, DaemonConfig,
|
|||
///
|
||||
/// * `Ok(AppConfig)` - Successfully loaded configuration
|
||||
/// * `Err(ConfigError)` - Error loading or parsing configuration
|
||||
pub fn load_config() -> Result<AppConfig, ConfigError> {
|
||||
pub fn load_config() -> anyhow::Result<AppConfig> {
|
||||
load_config_from_path(None)
|
||||
}
|
||||
|
||||
/// Load configuration from a specific path or try default paths
|
||||
pub fn load_config_from_path(specific_path: Option<&str>) -> Result<AppConfig, ConfigError> {
|
||||
pub fn load_config_from_path(specific_path: Option<&str>) -> anyhow::Result<AppConfig> {
|
||||
// If a specific path is provided, only try that one
|
||||
if let Some(path_str) = specific_path {
|
||||
let path = Path::new(path_str);
|
||||
if path.exists() {
|
||||
return load_and_parse_config(path);
|
||||
}
|
||||
return Err(ConfigError::Io(std::io::Error::new(
|
||||
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("Specified config file not found: {}", path.display()),
|
||||
)));
|
||||
))?;
|
||||
}
|
||||
|
||||
// Check for SUPERFREQ_CONFIG environment variable
|
||||
|
@ -79,10 +82,16 @@ pub fn load_config_from_path(specific_path: Option<&str>) -> Result<AppConfig, C
|
|||
}
|
||||
|
||||
/// Load and parse a configuration file
|
||||
fn load_and_parse_config(path: &Path) -> Result<AppConfig, ConfigError> {
|
||||
let contents = fs::read_to_string(path).map_err(ConfigError::Io)?;
|
||||
fn load_and_parse_config(path: &Path) -> anyhow::Result<AppConfig> {
|
||||
let contents = fs::read_to_string(path).with_context(|| {
|
||||
format!(
|
||||
"failed to read config file from '{path}'",
|
||||
path = path.display(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let toml_app_config = toml::from_str::<AppConfigToml>(&contents).map_err(ConfigError::Toml)?;
|
||||
let toml_app_config =
|
||||
toml::from_str::<AppConfigToml>(&contents).context("failed to parse config toml")?;
|
||||
|
||||
// Handle inheritance of values from global to profile configs
|
||||
let mut charger_profile = toml_app_config.charger.clone();
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
use anyhow::bail;
|
||||
// Configuration types and structures for superfreq
|
||||
use crate::core::TurboSetting;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// Defines constant-returning functions used for default values.
|
||||
/// This hopefully reduces repetition since we have way too many default functions
|
||||
/// that just return constants.
|
||||
/// This hopefully reduces repetition since we have way too many
|
||||
/// default functions that just return constants.
|
||||
macro_rules! default_const {
|
||||
($name:ident, $type:ty, $value:expr) => {
|
||||
($($name:ident -> $type:ty = $value:expr;)*) => {
|
||||
$(
|
||||
const fn $name() -> $type {
|
||||
$value
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -20,34 +22,21 @@ pub struct PowerSupplyChargeThresholds {
|
|||
pub stop: u8,
|
||||
}
|
||||
|
||||
impl PowerSupplyChargeThresholds {
|
||||
pub fn new(start: u8, stop: u8) -> Result<Self, ConfigError> {
|
||||
impl TryFrom<(u8, u8)> for PowerSupplyChargeThresholds {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from((start, stop): (u8, u8)) -> anyhow::Result<Self> {
|
||||
if stop == 0 {
|
||||
return Err(ConfigError::Validation(
|
||||
"Stop threshold must be greater than 0%".to_string(),
|
||||
));
|
||||
bail!("stop threshold must be greater than 0%");
|
||||
}
|
||||
if start >= stop {
|
||||
return Err(ConfigError::Validation(format!(
|
||||
"Start threshold ({start}) must be less than stop threshold ({stop})"
|
||||
)));
|
||||
bail!("start threshold ({start}) must be less than stop threshold ({stop})");
|
||||
}
|
||||
if stop > 100 {
|
||||
return Err(ConfigError::Validation(format!(
|
||||
"Stop threshold ({stop}) cannot exceed 100%"
|
||||
)));
|
||||
bail!("stop threshold ({stop}) cannot exceed 100%");
|
||||
}
|
||||
|
||||
Ok(Self { start, stop })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(u8, u8)> for PowerSupplyChargeThresholds {
|
||||
type Error = ConfigError;
|
||||
|
||||
fn try_from(values: (u8, u8)) -> Result<Self, Self::Error> {
|
||||
let (start, stop) = values;
|
||||
Self::new(start, stop)
|
||||
Ok(PowerSupplyChargeThresholds { start, stop })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +44,7 @@ impl TryFrom<(u8, u8)> for PowerSupplyChargeThresholds {
|
|||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ProfileConfig {
|
||||
pub governor: Option<String>,
|
||||
pub turbo: Option<TurboSetting>,
|
||||
pub turbo: Option<bool>,
|
||||
pub epp: Option<String>, // Energy Performance Preference (EPP)
|
||||
pub epb: Option<String>, // Energy Performance Bias (EPB) - usually an integer, but string for flexibility from sysfs
|
||||
pub min_freq_mhz: Option<u32>,
|
||||
|
@ -73,7 +62,7 @@ impl Default for ProfileConfig {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
governor: Some("schedutil".to_string()), // common sensible default (?)
|
||||
turbo: Some(TurboSetting::Auto),
|
||||
turbo: None,
|
||||
epp: None, // defaults depend on governor and system
|
||||
epb: None, // defaults depend on governor and system
|
||||
min_freq_mhz: None, // no override
|
||||
|
@ -97,19 +86,6 @@ pub struct AppConfig {
|
|||
pub daemon: DaemonConfig,
|
||||
}
|
||||
|
||||
// Error type for config loading
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("TOML parsing error: {0}")]
|
||||
Toml(#[from] toml::de::Error),
|
||||
|
||||
#[error("Configuration validation error: {0}")]
|
||||
Validation(String),
|
||||
}
|
||||
|
||||
// Intermediate structs for TOML parsing
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ProfileConfigToml {
|
||||
|
@ -178,22 +154,14 @@ pub const DEFAULT_LOAD_THRESHOLD_LOW: f32 = 30.0; // disable turbo if load is be
|
|||
pub const DEFAULT_TEMP_THRESHOLD_HIGH: f32 = 75.0; // disable turbo if temperature is above this
|
||||
pub const DEFAULT_INITIAL_TURBO_STATE: bool = false; // by default, start with turbo disabled
|
||||
|
||||
default_const!(
|
||||
default_load_threshold_high,
|
||||
f32,
|
||||
DEFAULT_LOAD_THRESHOLD_HIGH
|
||||
);
|
||||
default_const!(default_load_threshold_low, f32, DEFAULT_LOAD_THRESHOLD_LOW);
|
||||
default_const!(
|
||||
default_temp_threshold_high,
|
||||
f32,
|
||||
DEFAULT_TEMP_THRESHOLD_HIGH
|
||||
);
|
||||
default_const!(
|
||||
default_initial_turbo_state,
|
||||
bool,
|
||||
DEFAULT_INITIAL_TURBO_STATE
|
||||
);
|
||||
default_const! {
|
||||
default_load_threshold_high -> f32 = DEFAULT_LOAD_THRESHOLD_HIGH;
|
||||
default_load_threshold_low -> f32 = DEFAULT_LOAD_THRESHOLD_LOW;
|
||||
|
||||
default_temp_threshold_high -> f32 = DEFAULT_TEMP_THRESHOLD_HIGH;
|
||||
|
||||
default_initial_turbo_state -> bool = DEFAULT_INITIAL_TURBO_STATE;
|
||||
}
|
||||
|
||||
impl Default for TurboAutoSettings {
|
||||
fn default() -> Self {
|
||||
|
@ -213,10 +181,10 @@ impl From<ProfileConfigToml> for ProfileConfig {
|
|||
turbo: toml_config
|
||||
.turbo
|
||||
.and_then(|s| match s.to_lowercase().as_str() {
|
||||
"always" => Some(TurboSetting::Always),
|
||||
"auto" => Some(TurboSetting::Auto),
|
||||
"never" => Some(TurboSetting::Never),
|
||||
_ => None,
|
||||
"always" => Some(true),
|
||||
"auto" => None,
|
||||
"never" => Some(false),
|
||||
_ => panic!("invalid turbo value: {s}, must be one of: always, auto, never"),
|
||||
}),
|
||||
epp: toml_config.epp,
|
||||
epb: toml_config.epb,
|
||||
|
@ -270,14 +238,16 @@ impl Default for DaemonConfig {
|
|||
}
|
||||
}
|
||||
|
||||
default_const!(default_poll_interval_sec, u64, 5);
|
||||
default_const!(default_adaptive_interval, bool, false);
|
||||
default_const!(default_min_poll_interval_sec, u64, 1);
|
||||
default_const!(default_max_poll_interval_sec, u64, 30);
|
||||
default_const!(default_throttle_on_battery, bool, true);
|
||||
default_const!(default_log_level, LogLevel, LogLevel::Info);
|
||||
default_const!(default_stats_file_path, Option<String>, None);
|
||||
default_const!(default_enable_auto_turbo, bool, true);
|
||||
default_const! {
|
||||
default_poll_interval_sec -> u64 = 5;
|
||||
default_adaptive_interval -> bool = false;
|
||||
default_min_poll_interval_sec -> u64 = 1;
|
||||
default_max_poll_interval_sec -> u64 = 30;
|
||||
default_throttle_on_battery -> bool = true;
|
||||
default_log_level -> LogLevel = LogLevel::Info;
|
||||
default_stats_file_path -> Option<String> = None;
|
||||
default_enable_auto_turbo -> bool = true;
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct DaemonConfigToml {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use crate::config::{AppConfig, LogLevel};
|
||||
use anyhow::Context;
|
||||
use anyhow::bail;
|
||||
|
||||
use crate::config::AppConfig;
|
||||
use crate::core::SystemReport;
|
||||
use crate::engine;
|
||||
use crate::monitor;
|
||||
use crate::util::error::{AppError, ControlError};
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
@ -60,10 +62,7 @@ fn idle_multiplier(idle_secs: u64) -> f32 {
|
|||
/// Calculate optimal polling interval based on system conditions and history
|
||||
///
|
||||
/// 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) -> anyhow::Result<u64> {
|
||||
// Use the centralized validation function
|
||||
validate_poll_intervals(params.min_interval, params.max_interval)?;
|
||||
|
||||
|
@ -361,7 +360,7 @@ impl SystemHistory {
|
|||
&self,
|
||||
config: &AppConfig,
|
||||
on_battery: bool,
|
||||
) -> Result<u64, ControlError> {
|
||||
) -> anyhow::Result<u64> {
|
||||
let params = IntervalParams {
|
||||
base_interval: config.daemon.poll_interval_sec,
|
||||
min_interval: config.daemon.min_poll_interval_sec,
|
||||
|
@ -380,37 +379,31 @@ impl SystemHistory {
|
|||
|
||||
/// Validates that poll interval configuration is consistent
|
||||
/// Returns Ok if configuration is valid, Err with a descriptive message if invalid
|
||||
fn validate_poll_intervals(min_interval: u64, max_interval: u64) -> Result<(), ControlError> {
|
||||
fn validate_poll_intervals(min_interval: u64, max_interval: u64) -> anyhow::Result<()> {
|
||||
if min_interval < 1 {
|
||||
return Err(ControlError::InvalidValueError(
|
||||
"min_interval must be ≥ 1".to_string(),
|
||||
));
|
||||
bail!("min_interval must be ≥ 1");
|
||||
}
|
||||
if max_interval < 1 {
|
||||
return Err(ControlError::InvalidValueError(
|
||||
"max_interval must be ≥ 1".to_string(),
|
||||
));
|
||||
bail!("max_interval must be ≥ 1");
|
||||
}
|
||||
if max_interval >= min_interval {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ControlError::InvalidValueError(format!(
|
||||
bail!(
|
||||
"Invalid interval configuration: max_interval ({max_interval}) is less than min_interval ({min_interval})"
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the daemon
|
||||
pub fn run_daemon(config: AppConfig) -> Result<(), AppError> {
|
||||
pub fn run_daemon(config: AppConfig) -> anyhow::Result<()> {
|
||||
log::info!("Starting superfreq daemon...");
|
||||
|
||||
// Validate critical configuration values before proceeding
|
||||
if let Err(err) = validate_poll_intervals(
|
||||
validate_poll_intervals(
|
||||
config.daemon.min_poll_interval_sec,
|
||||
config.daemon.max_poll_interval_sec,
|
||||
) {
|
||||
return Err(AppError::Control(err));
|
||||
}
|
||||
)?;
|
||||
|
||||
// Create a flag that will be set to true when a signal is received
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
|
@ -421,7 +414,7 @@ pub fn run_daemon(config: AppConfig) -> Result<(), AppError> {
|
|||
log::info!("Received shutdown signal, exiting...");
|
||||
r.store(false, Ordering::SeqCst);
|
||||
})
|
||||
.map_err(|e| AppError::Generic(format!("Error setting Ctrl-C handler: {e}")))?;
|
||||
.context("failed to set Ctrl-C handler")?;
|
||||
|
||||
log::info!(
|
||||
"Daemon initialized with poll interval: {}s",
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::config::{AppConfig, ProfileConfig, TurboAutoSettings};
|
||||
use crate::core::{OperationalMode, SystemReport, TurboSetting};
|
||||
use crate::core::{OperationalMode, SystemReport};
|
||||
use crate::cpu::{self};
|
||||
use crate::power_supply;
|
||||
use crate::util::error::{ControlError, EngineError};
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
|
@ -119,30 +118,14 @@ impl TurboHysteresis {
|
|||
/// 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>(
|
||||
fn try_apply_feature<F: FnOnce() -> anyhow::Result<()>, T>(
|
||||
feature_name: &str,
|
||||
value_description: &str,
|
||||
apply_fn: F,
|
||||
) -> Result<(), EngineError>
|
||||
where
|
||||
F: FnOnce() -> Result<T, ControlError>,
|
||||
{
|
||||
) -> anyhow::Result<()> {
|
||||
log::info!("Setting {feature_name} to '{value_description}'");
|
||||
|
||||
match apply_fn() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
if matches!(e, ControlError::NotSupported(_)) {
|
||||
log::warn!(
|
||||
"{feature_name} setting is not supported on this system. Skipping {feature_name} configuration."
|
||||
);
|
||||
Ok(())
|
||||
} else {
|
||||
// Propagate all other errors, including InvalidValueError
|
||||
Err(EngineError::ControlError(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
apply_fn()
|
||||
}
|
||||
|
||||
/// Determines the appropriate CPU profile based on power status or forced mode,
|
||||
|
@ -151,19 +134,19 @@ pub fn determine_and_apply_settings(
|
|||
report: &SystemReport,
|
||||
config: &AppConfig,
|
||||
force_mode: Option<OperationalMode>,
|
||||
) -> Result<(), EngineError> {
|
||||
// First, check if there's a governor override set
|
||||
if let Some(override_governor) = cpu::get_governor_override() {
|
||||
log::info!(
|
||||
"Governor override is active: '{}'. Setting governor.",
|
||||
override_governor.trim()
|
||||
);
|
||||
) -> anyhow::Result<()> {
|
||||
// // First, check if there's a governor override set
|
||||
// if let Some(override_governor) = cpu::get_governor_override() {
|
||||
// log::info!(
|
||||
// "Governor override is active: '{}'. Setting governor.",
|
||||
// override_governor.trim()
|
||||
// );
|
||||
|
||||
// Apply the override governor setting
|
||||
try_apply_feature("override governor", override_governor.trim(), || {
|
||||
cpu::set_governor(override_governor.trim(), None)
|
||||
})?;
|
||||
}
|
||||
// // Apply the override governor setting
|
||||
// try_apply_feature("override governor", override_governor.trim(), || {
|
||||
// cpu::set_governor(override_governor.trim(), None)
|
||||
// })?;
|
||||
// }
|
||||
|
||||
// Determine AC/Battery status once, early in the function
|
||||
// For desktops (no batteries), we should always use the AC power profile
|
||||
|
@ -203,17 +186,11 @@ pub fn determine_and_apply_settings(
|
|||
// Apply settings from selected_profile_config
|
||||
if let Some(governor) = &selected_profile_config.governor {
|
||||
log::info!("Setting governor to '{governor}'");
|
||||
for cpu in cpu::Cpu::all()? {
|
||||
// Let set_governor handle the validation
|
||||
if let Err(e) = cpu::set_governor(governor, None) {
|
||||
if let Err(error) = cpu.set_governor(governor) {
|
||||
// If the governor is not available, log a warning
|
||||
if matches!(e, ControlError::InvalidGovernor(_))
|
||||
|| matches!(e, ControlError::NotSupported(_))
|
||||
{
|
||||
log::warn!(
|
||||
"Configured governor '{governor}' is not available on this system. Skipping."
|
||||
);
|
||||
} else {
|
||||
return Err(e.into());
|
||||
log::warn!("{error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -297,7 +274,7 @@ fn manage_auto_turbo(
|
|||
report: &SystemReport,
|
||||
config: &ProfileConfig,
|
||||
on_ac_power: bool,
|
||||
) -> Result<(), EngineError> {
|
||||
) -> anyhow::Result<()> {
|
||||
// Get the auto turbo settings from the config
|
||||
let turbo_settings = &config.turbo_auto_settings;
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
mod cli;
|
||||
mod config;
|
||||
mod core;
|
||||
mod cpu;
|
||||
|
@ -6,7 +5,6 @@ mod daemon;
|
|||
mod engine;
|
||||
mod monitor;
|
||||
mod power_supply;
|
||||
mod util;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser as _;
|
||||
|
@ -162,7 +160,7 @@ fn real_main() -> anyhow::Result<()> {
|
|||
} => {
|
||||
let power_supplies = match for_ {
|
||||
Some(names) => {
|
||||
let power_supplies = Vec::with_capacity(names.len());
|
||||
let mut power_supplies = Vec::with_capacity(names.len());
|
||||
|
||||
for name in names {
|
||||
power_supplies.push(power_supply::PowerSupply::from_name(name)?);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use crate::config::AppConfig;
|
||||
use crate::core::{BatteryInfo, CpuCoreInfo, CpuGlobalInfo, SystemInfo, SystemLoad, SystemReport};
|
||||
use crate::cpu::get_real_cpus;
|
||||
use crate::util::error::SysMonitorError;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
|
@ -12,10 +10,8 @@ use std::{
|
|||
time::SystemTime,
|
||||
};
|
||||
|
||||
pub type Result<T, E = SysMonitorError> = std::result::Result<T, E>;
|
||||
|
||||
// Read a sysfs file to a string, trimming whitespace
|
||||
fn read_sysfs_file_trimmed(path: impl AsRef<Path>) -> Result<String> {
|
||||
fn read_sysfs_file_trimmed(path: impl AsRef<Path>) -> anyhow::Result<String> {
|
||||
fs::read_to_string(path.as_ref())
|
||||
.map(|s| s.trim().to_string())
|
||||
.map_err(|e| {
|
||||
|
@ -24,7 +20,7 @@ fn read_sysfs_file_trimmed(path: impl AsRef<Path>) -> Result<String> {
|
|||
}
|
||||
|
||||
// Read a sysfs file and parse it to a specific type
|
||||
fn read_sysfs_value<T: FromStr>(path: impl AsRef<Path>) -> Result<T> {
|
||||
fn read_sysfs_value<T: FromStr>(path: impl AsRef<Path>) -> anyhow::Result<T> {
|
||||
let content = read_sysfs_file_trimmed(path.as_ref())?;
|
||||
content.parse::<T>().map_err(|_| {
|
||||
SysMonitorError::ParseError(format!(
|
||||
|
@ -76,7 +72,7 @@ impl CpuTimes {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_all_cpu_times() -> Result<HashMap<u32, CpuTimes>> {
|
||||
fn read_all_cpu_times() -> anyhow::Result<HashMap<u32, CpuTimes>> {
|
||||
let content = fs::read_to_string("/proc/stat").map_err(SysMonitorError::Io)?;
|
||||
let mut cpu_times_map = HashMap::new();
|
||||
|
||||
|
@ -156,7 +152,7 @@ pub fn get_cpu_core_info(
|
|||
core_id: u32,
|
||||
prev_times: &CpuTimes,
|
||||
current_times: &CpuTimes,
|
||||
) -> Result<CpuCoreInfo> {
|
||||
) -> anyhow::Result<CpuCoreInfo> {
|
||||
let cpufreq_path = PathBuf::from(format!("/sys/devices/system/cpu/cpu{core_id}/cpufreq/"));
|
||||
|
||||
let current_frequency_mhz = read_sysfs_value::<u32>(cpufreq_path.join("scaling_cur_freq"))
|
||||
|
@ -358,7 +354,7 @@ fn get_fallback_temperature(hw_path: &Path) -> Option<f32> {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn get_all_cpu_core_info() -> Result<Vec<CpuCoreInfo>> {
|
||||
pub fn get_all_cpu_core_info() -> anyhow::Result<Vec<CpuCoreInfo>> {
|
||||
let initial_cpu_times = read_all_cpu_times()?;
|
||||
thread::sleep(Duration::from_millis(250)); // interval for CPU usage calculation
|
||||
let final_cpu_times = read_all_cpu_times()?;
|
||||
|
@ -486,7 +482,7 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_battery_info(config: &AppConfig) -> Result<Vec<BatteryInfo>> {
|
||||
pub fn get_battery_info(config: &AppConfig) -> anyhow::Result<Vec<BatteryInfo>> {
|
||||
let mut batteries = Vec::new();
|
||||
let power_supply_path = Path::new("/sys/class/power_supply");
|
||||
|
||||
|
@ -682,7 +678,7 @@ fn is_likely_desktop_system() -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
pub fn get_system_load() -> Result<SystemLoad> {
|
||||
pub fn get_system_load() -> anyhow::Result<SystemLoad> {
|
||||
let loadavg_str = read_sysfs_file_trimmed("/proc/loadavg")?;
|
||||
let parts: Vec<&str> = loadavg_str.split_whitespace().collect();
|
||||
if parts.len() < 3 {
|
||||
|
@ -707,7 +703,7 @@ pub fn get_system_load() -> Result<SystemLoad> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn collect_system_report(config: &AppConfig) -> Result<SystemReport> {
|
||||
pub fn collect_system_report(config: &AppConfig) -> anyhow::Result<SystemReport> {
|
||||
let system_info = get_system_info();
|
||||
let cpu_cores = get_all_cpu_core_info()?;
|
||||
let cpu_global = get_cpu_global_info(&cpu_cores);
|
||||
|
@ -724,7 +720,7 @@ pub fn collect_system_report(config: &AppConfig) -> Result<SystemReport> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_cpu_model() -> Result<String> {
|
||||
pub fn get_cpu_model() -> anyhow::Result<String> {
|
||||
let path = Path::new("/proc/cpuinfo");
|
||||
let content = fs::read_to_string(path).map_err(|_| {
|
||||
SysMonitorError::ReadError(format!("Cannot read contents of {}.", path.display()))
|
||||
|
@ -743,7 +739,7 @@ pub fn get_cpu_model() -> Result<String> {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn get_linux_distribution() -> Result<String> {
|
||||
pub fn get_linux_distribution() -> anyhow::Result<String> {
|
||||
let os_release_path = Path::new("/etc/os-release");
|
||||
let content = fs::read_to_string(os_release_path).map_err(|_| {
|
||||
SysMonitorError::ReadError(format!(
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
use std::io;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ControlError {
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error("Failed to write to sysfs path: {0}")]
|
||||
WriteError(String),
|
||||
|
||||
#[error("Failed to read sysfs path: {0}")]
|
||||
ReadError(String),
|
||||
|
||||
#[error("Invalid value for setting: {0}")]
|
||||
InvalidValueError(String),
|
||||
|
||||
#[error("Control action not supported: {0}")]
|
||||
NotSupported(String),
|
||||
|
||||
#[error("Permission denied: {0}. Try running with sudo.")]
|
||||
PermissionDenied(String),
|
||||
|
||||
#[error("Invalid platform control profile {0} supplied, please provide a valid one.")]
|
||||
InvalidProfile(String),
|
||||
|
||||
#[error("Invalid governor: {0}")]
|
||||
InvalidGovernor(String),
|
||||
|
||||
#[error("Failed to parse value: {0}")]
|
||||
ParseError(String),
|
||||
|
||||
#[error("Path missing: {0}")]
|
||||
PathMissing(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SysMonitorError {
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error("Failed to read sysfs path: {0}")]
|
||||
ReadError(String),
|
||||
|
||||
#[error("Failed to parse value: {0}")]
|
||||
ParseError(String),
|
||||
|
||||
#[error("Failed to parse /proc/stat: {0}")]
|
||||
ProcStatParseError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum EngineError {
|
||||
#[error("CPU control error: {0}")]
|
||||
ControlError(#[from] ControlError),
|
||||
|
||||
#[error("Configuration error: {0}")]
|
||||
ConfigurationError(String),
|
||||
}
|
||||
|
||||
// A unified error type for the entire application
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AppError {
|
||||
#[error("{0}")]
|
||||
Control(#[from] ControlError),
|
||||
|
||||
#[error("{0}")]
|
||||
Monitor(#[from] SysMonitorError),
|
||||
|
||||
#[error("{0}")]
|
||||
Engine(#[from] EngineError),
|
||||
|
||||
#[error("{0}")]
|
||||
Config(#[from] crate::config::ConfigError),
|
||||
|
||||
#[error("{0}")]
|
||||
Generic(String),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
pub mod error;
|
||||
pub mod sysfs;
|
|
@ -1,80 +0,0 @@
|
|||
use crate::util::error::ControlError;
|
||||
use std::{fs, io, path::Path};
|
||||
|
||||
/// Write a value to a sysfs file with consistent error handling
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The file path to write to
|
||||
/// * `value` - The string value to write
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `ControlError` variant based on the specific error:
|
||||
/// - `ControlError::PermissionDenied` if permission is denied
|
||||
/// - `ControlError::PathMissing` if the path doesn't exist
|
||||
/// - `ControlError::WriteError` for other I/O errors
|
||||
pub fn write_sysfs_value(path: impl AsRef<Path>, value: &str) -> Result<(), ControlError> {
|
||||
let p = path.as_ref();
|
||||
|
||||
fs::write(p, value).map_err(|e| {
|
||||
let error_msg = format!("Path: {:?}, Value: '{}', Error: {}", p.display(), value, e);
|
||||
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),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Read a value from a sysfs file with consistent error handling
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The file path to read from
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the trimmed contents of the file as a String
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a `ControlError` variant based on the specific error:
|
||||
/// - `ControlError::PermissionDenied` if permission is denied
|
||||
/// - `ControlError::PathMissing` if the path doesn't exist
|
||||
/// - `ControlError::ReadError` for other I/O errors
|
||||
pub fn read_sysfs_value(path: impl AsRef<Path>) -> Result<String, ControlError> {
|
||||
let p = path.as_ref();
|
||||
fs::read_to_string(p)
|
||||
.map_err(|e| {
|
||||
let error_msg = format!("Path: {:?}, Error: {}", p.display(), e);
|
||||
match e.kind() {
|
||||
io::ErrorKind::PermissionDenied => ControlError::PermissionDenied(error_msg),
|
||||
io::ErrorKind::NotFound => {
|
||||
ControlError::PathMissing(format!("Path '{}' does not exist", p.display()))
|
||||
}
|
||||
_ => ControlError::ReadError(error_msg),
|
||||
}
|
||||
})
|
||||
.map(|s| s.trim().to_string())
|
||||
}
|
||||
|
||||
/// Safely check if a path exists and is writable
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - The file path to check
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns true if the path exists and is writable, false otherwise
|
||||
pub fn path_exists_and_writable(path: &Path) -> bool {
|
||||
if !path.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to open the file with write access to verify write permission
|
||||
fs::OpenOptions::new().write(true).open(path).is_ok()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue