From dde938b638d951171c7df6ff042f35e9a240b39a Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 14 May 2025 00:15:18 +0300 Subject: [PATCH 1/4] cli: more command logic to a dedicated module; add debug cmd --- src/cli/debug.rs | 166 ++++++++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 1 + src/config/watcher.rs | 73 +++++++++++++++++++ src/engine.rs | 4 +- src/main.rs | 4 + src/monitor.rs | 9 ++- 6 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 src/cli/debug.rs create mode 100644 src/cli/mod.rs create mode 100644 src/config/watcher.rs diff --git a/src/cli/debug.rs b/src/cli/debug.rs new file mode 100644 index 0000000..3e5ac54 --- /dev/null +++ b/src/cli/debug.rs @@ -0,0 +1,166 @@ +use crate::config::AppConfig; +use crate::conflict; +use crate::cpu; +use crate::monitor; +use std::error::Error; +use std::fs; + +/// Prints comprehensive debug information about the system +pub fn run_debug(config: &AppConfig) -> Result<(), Box> { + println!("=== SUPERFREQ DEBUG INFORMATION ==="); + println!("Version: {}", env!("CARGO_PKG_VERSION")); + + // Current date and time + let now = std::time::SystemTime::now(); + println!("Timestamp: {now:?}"); + + // Get system information and conflicts + 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:#?}"); + + 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--- CONFLICT DETECTION ---"); + let conflicts = conflict::detect_conflicts(); + println!("{}", conflicts.get_conflict_message()); + + 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}"); + + Ok(()) + } + Err(e) => Err(Box::new(e) as Box), + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..2f36523 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1 @@ +pub mod debug; diff --git a/src/config/watcher.rs b/src/config/watcher.rs new file mode 100644 index 0000000..78ece3c --- /dev/null +++ b/src/config/watcher.rs @@ -0,0 +1,73 @@ +use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; +use std::path::Path; +use std::sync::mpsc::{channel, Receiver}; +use std::time::Duration; +use std::thread; +use std::error::Error; + +use crate::config::{load_config, AppConfig}; + +/// Watches a configuration file for changes and reloads it when modified +pub struct ConfigWatcher { + rx: Receiver>, + _watcher: RecommendedWatcher, // keep watcher alive while watching + config_path: String, +} + +impl ConfigWatcher { + /// Initialize a new config watcher for the given path + pub fn new(config_path: &str) -> Result { + let (tx, rx) = channel(); + + // Create a watcher with default config + let mut watcher = RecommendedWatcher::new(tx, Config::default())?; + + // Start watching the config file + watcher.watch(Path::new(config_path), RecursiveMode::NonRecursive)?; + + Ok(Self { + rx, + _watcher: watcher, + config_path: config_path.to_string(), + }) + } + + /// Check for config file changes and reload if necessary + /// + /// # Returns + /// + /// `Some(AppConfig)` if the config was reloaded, `None`` otherwise + pub fn check_for_changes(&self) -> Option>> { + // Non-blocking check for file events + match self.rx.try_recv() { + Ok(Ok(event)) => { + // Only process write/modify events + if matches!(event.kind, EventKind::Modify(_)) { + // Add a small delay to ensure the file write is complete + thread::sleep(Duration::from_millis(100)); + + // Attempt to reload the config + match load_config() { + Ok(config) => { + println!("Configuration file changed. Reloaded configuration."); + Some(Ok(config)) + } + Err(e) => { + eprintln!("Error reloading configuration: {e}"); + Some(Err(Box::new(e))) + } + } + } else { + None + } + } + // No events or channel errors + _ => None, + } + } + + /// Get the path of the config file being watched + pub const fn config_path(&self) -> &String { + &self.config_path + } +} \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs index c898256..4f031d2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -67,8 +67,8 @@ pub fn determine_and_apply_settings( // If no batteries, assume AC power (desktop). // Otherwise, check the ac_connected status from the (first) battery. // XXX: This relies on the setting ac_connected in BatteryInfo being set correctly. - let on_ac_power = report.batteries.is_empty() - || report.batteries.first().map_or(false, |b| b.ac_connected); + let on_ac_power = + report.batteries.is_empty() || report.batteries.first().is_some_and(|b| b.ac_connected); if on_ac_power { println!("Engine: On AC power, selecting Charger profile."); diff --git a/src/main.rs b/src/main.rs index 83fb35c..eb8afbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod cli; mod config; mod conflict; mod core; @@ -45,6 +46,8 @@ enum Commands { #[clap(value_enum)] setting: TurboSetting, }, + /// Display comprehensive debug information + Debug, /// Set Energy Performance Preference (EPP) SetEpp { epp: String, @@ -202,6 +205,7 @@ fn main() { Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile) .map_err(|e| Box::new(e) as Box), Some(Commands::Daemon { verbose }) => daemon::run_daemon(config, verbose), + Some(Commands::Debug) => cli::debug::run_debug(&config), None => { println!("Welcome to superfreq! Use --help for commands."); println!("Current effective configuration: {config:?}"); diff --git a/src/monitor.rs b/src/monitor.rs index a91bd19..18795d8 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -417,7 +417,14 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { None }; - let available_governors = get_platform_profiles().unwrap_or_else(|_| vec![]); + let available_governors = if cpufreq_base.join("scaling_available_governors").exists() { + read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors")).map_or_else( + |_| vec![], + |s| s.split_whitespace().map(String::from).collect(), + ) + } else { + vec![] + }; let turbo_status = if turbo_status_path.exists() { // 0 means turbo enabled, 1 means disabled for intel_pstate From 9f7d86ff01d33f9caf9a479980a1da7c4ff42117 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 14 May 2025 01:17:42 +0300 Subject: [PATCH 2/4] config: modularize; add config watcher and move error variants --- Cargo.lock | 173 ++++++++++++++++++++++++++++- Cargo.toml | 1 + src/config/load.rs | 76 +++++++++++++ src/config/mod.rs | 9 ++ src/{config.rs => config/types.rs} | 81 +------------- src/cpu.rs | 2 - src/daemon.rs | 49 +++++++- src/engine.rs | 39 ++----- src/monitor.rs | 11 +- src/util/error.rs | 34 ++++++ 10 files changed, 349 insertions(+), 126 deletions(-) create mode 100644 src/config/load.rs create mode 100644 src/config/mod.rs rename src/{config.rs => config/types.rs} (69%) diff --git a/Cargo.lock b/Cargo.lock index 4ac1b6f..6afa2b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -49,9 +49,15 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", - "windows-sys", + "windows-sys 0.59.0", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -123,7 +129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" dependencies = [ "nix", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -144,7 +150,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -153,6 +159,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -192,12 +219,52 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "libc" version = "0.2.172" @@ -210,28 +277,75 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.9.0", "libc", + "redox_syscall", ] +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "notify" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +dependencies = [ + "bitflags 2.9.0", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.59.0", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +dependencies = [ + "serde", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -272,6 +386,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags 2.9.0", +] + [[package]] name = "redox_users" version = "0.5.0" @@ -283,6 +406,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.219" @@ -325,6 +457,7 @@ dependencies = [ "clap", "ctrlc", "dirs", + "notify", "num_cpus", "serde", "toml", @@ -414,12 +547,40 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 97f5926..d53a8b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ dirs = "6.0" clap = { version = "4.0", features = ["derive"] } num_cpus = "1.16" ctrlc = "3.4" +notify = { version = "8.0.0", features = ["serde"] } diff --git a/src/config/load.rs b/src/config/load.rs new file mode 100644 index 0000000..4e463b5 --- /dev/null +++ b/src/config/load.rs @@ -0,0 +1,76 @@ +// Configuration loading functionality +use std::fs; +use std::path::PathBuf; + +use crate::config::types::{AppConfig, AppConfigToml, ConfigError, DaemonConfig, ProfileConfig}; + +// The primary function to load application configuration. +// It tries user-specific and then system-wide TOML files. +// Falls back to default settings if no file is found or if parsing fails. +pub fn load_config() -> Result { + let mut config_paths: Vec = Vec::new(); + + // User-specific path + if let Some(home_dir) = dirs::home_dir() { + let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml"); + config_paths.push(user_config_path); + } else { + eprintln!( + "Warning: Could not determine home directory. User-specific config will not be loaded." + ); + } + + // System-wide path + let system_config_path = PathBuf::from("/etc/auto_cpufreq_rs/config.toml"); + config_paths.push(system_config_path); + + for path in config_paths { + if path.exists() { + println!("Attempting to load config from: {}", path.display()); + match fs::read_to_string(&path) { + Ok(contents) => { + match toml::from_str::(&contents) { + Ok(toml_app_config) => { + // Convert AppConfigToml to AppConfig + let app_config = AppConfig { + charger: ProfileConfig::from(toml_app_config.charger), + battery: ProfileConfig::from(toml_app_config.battery), + battery_charge_thresholds: toml_app_config.battery_charge_thresholds, + ignored_power_supplies: toml_app_config.ignored_power_supplies, + poll_interval_sec: toml_app_config.poll_interval_sec, + daemon: DaemonConfig { + poll_interval_sec: toml_app_config.daemon.poll_interval_sec, + adaptive_interval: toml_app_config.daemon.adaptive_interval, + min_poll_interval_sec: toml_app_config.daemon.min_poll_interval_sec, + max_poll_interval_sec: toml_app_config.daemon.max_poll_interval_sec, + throttle_on_battery: toml_app_config.daemon.throttle_on_battery, + log_level: toml_app_config.daemon.log_level, + stats_file_path: toml_app_config.daemon.stats_file_path, + }, + }; + return Ok(app_config); + } + Err(e) => { + eprintln!("Error parsing config file {}: {}", path.display(), e); + } + } + } + Err(e) => { + eprintln!("Error reading config file {}: {}", path.display(), e); + } + } + } + } + + println!("No configuration file found or all failed to parse. Using default configuration."); + // Construct default AppConfig by converting default AppConfigToml + let default_toml_config = AppConfigToml::default(); + Ok(AppConfig { + charger: ProfileConfig::from(default_toml_config.charger), + battery: ProfileConfig::from(default_toml_config.battery), + battery_charge_thresholds: default_toml_config.battery_charge_thresholds, + ignored_power_supplies: default_toml_config.ignored_power_supplies, + poll_interval_sec: default_toml_config.poll_interval_sec, + daemon: DaemonConfig::default(), + }) +} \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..3524422 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,9 @@ +pub mod watcher; + +// Re-export all configuration types and functions +pub use self::types::*; +pub use self::load::*; + +// Internal organization of config submodules +mod types; +mod load; \ No newline at end of file diff --git a/src/config.rs b/src/config/types.rs similarity index 69% rename from src/config.rs rename to src/config/types.rs index 9cc1eac..8342552 100644 --- a/src/config.rs +++ b/src/config/types.rs @@ -1,7 +1,6 @@ +// Configuration types and structures for superfreq use crate::core::TurboSetting; use serde::Deserialize; -use std::fs; -use std::path::PathBuf; // Structs for configuration using serde::Deserialize #[derive(Deserialize, Debug, Clone)] @@ -83,82 +82,6 @@ impl std::fmt::Display for ConfigError { impl std::error::Error for ConfigError {} -// The primary function to load application configuration. -// It tries user-specific and then system-wide TOML files. -// Falls back to default settings if no file is found or if parsing fails. -pub fn load_config() -> Result { - let mut config_paths: Vec = Vec::new(); - - // User-specific path - if let Some(home_dir) = dirs::home_dir() { - let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml"); - config_paths.push(user_config_path); - } else { - eprintln!( - "Warning: Could not determine home directory. User-specific config will not be loaded." - ); - } - - // System-wide path - let system_config_path = PathBuf::from("/etc/auto_cpufreq_rs/config.toml"); - config_paths.push(system_config_path); - - for path in config_paths { - if path.exists() { - println!("Attempting to load config from: {}", path.display()); - match fs::read_to_string(&path) { - Ok(contents) => { - match toml::from_str::(&contents) { - Ok(toml_app_config) => { - // Convert AppConfigToml to AppConfig - let app_config = AppConfig { - charger: ProfileConfig::from(toml_app_config.charger), - battery: ProfileConfig::from(toml_app_config.battery), - battery_charge_thresholds: toml_app_config - .battery_charge_thresholds, - ignored_power_supplies: toml_app_config.ignored_power_supplies, - poll_interval_sec: toml_app_config.poll_interval_sec, - daemon: DaemonConfig { - poll_interval_sec: toml_app_config.daemon.poll_interval_sec, - adaptive_interval: toml_app_config.daemon.adaptive_interval, - min_poll_interval_sec: toml_app_config - .daemon - .min_poll_interval_sec, - max_poll_interval_sec: toml_app_config - .daemon - .max_poll_interval_sec, - throttle_on_battery: toml_app_config.daemon.throttle_on_battery, - log_level: toml_app_config.daemon.log_level, - stats_file_path: toml_app_config.daemon.stats_file_path, - }, - }; - return Ok(app_config); - } - Err(e) => { - eprintln!("Error parsing config file {}: {}", path.display(), e); - } - } - } - Err(e) => { - eprintln!("Error reading config file {}: {}", path.display(), e); - } - } - } - } - - println!("No configuration file found or all failed to parse. Using default configuration."); - // Construct default AppConfig by converting default AppConfigToml - let default_toml_config = AppConfigToml::default(); - Ok(AppConfig { - charger: ProfileConfig::from(default_toml_config.charger), - battery: ProfileConfig::from(default_toml_config.battery), - battery_charge_thresholds: default_toml_config.battery_charge_thresholds, - ignored_power_supplies: default_toml_config.ignored_power_supplies, - poll_interval_sec: default_toml_config.poll_interval_sec, - daemon: DaemonConfig::default(), - }) -} - // Intermediate structs for TOML parsing #[derive(Deserialize, Debug, Clone)] pub struct ProfileConfigToml { @@ -350,4 +273,4 @@ impl Default for DaemonConfigToml { stats_file_path: default_stats_file_path(), } } -} +} \ No newline at end of file diff --git a/src/cpu.rs b/src/cpu.rs index 30165bd..85c10cf 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -3,8 +3,6 @@ use crate::util::error::ControlError; use core::str; use std::{fs, io, path::Path, string::ToString}; -impl std::error::Error for ControlError {} - pub type Result = std::result::Result; // Write a value to a sysfs file diff --git a/src/daemon.rs b/src/daemon.rs index bcf00ee..c966af6 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,4 +1,5 @@ use crate::config::{AppConfig, LogLevel}; +use crate::config::watcher::ConfigWatcher; use crate::conflict; use crate::core::SystemReport; use crate::engine; @@ -10,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; /// Run the daemon -pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box> { +pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box> { // Set effective log level based on config and verbose flag let effective_log_level = if verbose { LogLevel::Debug @@ -63,6 +64,34 @@ pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box = match config_file_path { + Some(path) => { + match ConfigWatcher::new(&path) { + Ok(watcher) => { + println!("Watching config file: {path}"); + Some(watcher) + }, + Err(e) => { + eprintln!("Failed to initialize config file watcher: {e}"); + None + } + } + }, + None => None, + }; + // Variables for adaptive polling let mut current_poll_interval = config.daemon.poll_interval_sec; let mut last_settings_change = Instant::now(); @@ -72,6 +101,24 @@ pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box { + if verbose { + println!("Config file changed, updating configuration"); + } + config = new_config; + }, + Err(e) => { + eprintln!("Error loading new configuration: {e}"); + // Continue with existing config + } + } + } + } + match monitor::collect_system_report(&config) { Ok(report) => { log_message( diff --git a/src/engine.rs b/src/engine.rs index 4f031d2..768a192 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,37 +1,7 @@ use crate::config::{AppConfig, ProfileConfig}; use crate::core::{OperationalMode, SystemReport, TurboSetting}; use crate::cpu::{self}; -use crate::util::error::ControlError; - -#[derive(Debug)] -pub enum EngineError { - ControlError(ControlError), - ConfigurationError(String), -} - -impl From 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, - } - } -} +use crate::util::error::EngineError; /// Determines the appropriate CPU profile based on power status or forced mode, /// and applies the settings using functions from the `cpu` module. @@ -158,6 +128,13 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() } }; + // Validate the configuration to ensure it's usable + if turbo_settings.load_threshold_high <= turbo_settings.load_threshold_low { + return Err(EngineError::ConfigurationError( + "Invalid turbo auto settings: high threshold must be greater than low threshold".to_string() + )); + } + // Decision logic for enabling/disabling turbo let enable_turbo = match (cpu_temp, avg_cpu_usage) { // If temperature is too high, disable turbo regardless of load diff --git a/src/monitor.rs b/src/monitor.rs index 18795d8..8d1b3d1 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,11 +1,10 @@ use crate::config::AppConfig; use crate::core::{BatteryInfo, CpuCoreInfo, CpuGlobalInfo, SystemInfo, SystemLoad, SystemReport}; -use crate::cpu::{get_logical_core_count, get_platform_profiles}; -use crate::util::error::ControlError; +use crate::cpu::get_logical_core_count; use crate::util::error::SysMonitorError; use std::{ collections::HashMap, - fs, io, + fs, path::{Path, PathBuf}, str::FromStr, thread, @@ -13,8 +12,6 @@ use std::{ time::SystemTime, }; -impl std::error::Error for SysMonitorError {} - pub type Result = std::result::Result; // Read a sysfs file to a string, trimming whitespace @@ -417,8 +414,8 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { None }; - let available_governors = if cpufreq_base.join("scaling_available_governors").exists() { - read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors")).map_or_else( + let available_governors = if cpufreq_base_path.join("scaling_available_governors").exists() { + read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_available_governors")).map_or_else( |_| vec![], |s| s.split_whitespace().map(String::from).collect(), ) diff --git a/src/util/error.rs b/src/util/error.rs index 1dc49a9..3ee0a86 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -39,6 +39,8 @@ impl std::fmt::Display for ControlError { } } +impl std::error::Error for ControlError {} + #[derive(Debug)] pub enum SysMonitorError { Io(io::Error), @@ -67,3 +69,35 @@ impl std::fmt::Display for SysMonitorError { } } } + +impl std::error::Error for SysMonitorError {} + +#[derive(Debug)] +pub enum EngineError { + ControlError(ControlError), + ConfigurationError(String), +} + +impl From 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, + } + } +} From 262c70fb85c97f98d71ad3d9d41f428b8b6b4187 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 14 May 2025 01:23:50 +0300 Subject: [PATCH 3/4] cpu: support AMD properly --- src/cpu.rs | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 85c10cf..09290cf 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -102,23 +102,56 @@ pub fn set_turbo(setting: TurboSetting) -> Result<()> { let value_boost = match setting { TurboSetting::Always => "1", // boost = 1 means turbo is enabled TurboSetting::Never => "0", // boost = 0 means turbo is disabled - TurboSetting::Auto => return Err(ControlError::InvalidValueError("Turbo Auto cannot be directly set via intel_pstate/no_turbo or cpufreq/boost. System default.".to_string())), + TurboSetting::Auto => return Err(ControlError::InvalidValueError("Turbo Auto cannot be directly set via intel_pstate/no_turbo or cpufreq/boost. System default.".to_string())), }; - + + // AMD specific paths + let amd_pstate_path = "/sys/devices/system/cpu/amd_pstate/cpufreq/boost"; + let msr_boost_path = "/sys/devices/system/cpu/cpufreq/amd_pstate_enable_boost"; + + // Path priority (from most to least specific) let pstate_path = "/sys/devices/system/cpu/intel_pstate/no_turbo"; let boost_path = "/sys/devices/system/cpu/cpufreq/boost"; + // Try each boost control path in order of specificity if Path::new(pstate_path).exists() { write_sysfs_value(pstate_path, value_pstate) + } else if Path::new(amd_pstate_path).exists() { + write_sysfs_value(amd_pstate_path, value_boost) + } else if Path::new(msr_boost_path).exists() { + write_sysfs_value(msr_boost_path, value_boost) } else if Path::new(boost_path).exists() { write_sysfs_value(boost_path, value_boost) } else { - Err(ControlError::NotSupported( - "Neither intel_pstate/no_turbo nor cpufreq/boost found.".to_string(), - )) + // Also try per-core cpufreq boost for some AMD systems + let result = try_set_per_core_boost(value_boost)?; + if result { + Ok(()) + } else { + Err(ControlError::NotSupported( + "No supported CPU boost control mechanism found.".to_string(), + )) + } } } +/// Try to set boost on a per-core basis for systems that support it +fn try_set_per_core_boost(value: &str) -> Result { + let mut success = false; + let num_cores = get_logical_core_count()?; + + for core_id in 0..num_cores { + let boost_path = format!("/sys/devices/system/cpu/cpu{}/cpufreq/boost", core_id); + + if Path::new(&boost_path).exists() { + write_sysfs_value(&boost_path, value)?; + success = true; + } + } + + Ok(success) +} + pub fn set_epp(epp: &str, core_id: Option) -> Result<()> { let action = |id: u32| { let path = format!("/sys/devices/system/cpu/cpu{id}/cpufreq/energy_performance_preference"); From 498d179aa8e54701ea54795aa7ce6122defe2843 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 14 May 2025 01:38:55 +0300 Subject: [PATCH 4/4] config: improve watcher; debounce --- src/config/load.rs | 104 ++++++++++++++++++++++++++---------------- src/config/mod.rs | 4 +- src/config/types.rs | 14 +++--- src/config/watcher.rs | 80 ++++++++++++++++++++------------ src/cpu.rs | 12 ++--- src/daemon.rs | 74 ++++++++++++++++++++---------- src/engine.rs | 3 +- src/monitor.rs | 5 +- 8 files changed, 186 insertions(+), 110 deletions(-) diff --git a/src/config/load.rs b/src/config/load.rs index 4e463b5..f0fedce 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -1,18 +1,43 @@ // Configuration loading functionality use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::config::types::{AppConfig, AppConfigToml, ConfigError, DaemonConfig, ProfileConfig}; -// The primary function to load application configuration. -// It tries user-specific and then system-wide TOML files. -// Falls back to default settings if no file is found or if parsing fails. +/// The primary function to load application configuration from a specific path or from default locations. +/// +/// # Arguments +/// +/// * `specific_path` - If provided, only attempts to load from this path and errors if not found +/// +/// # Returns +/// +/// * `Ok(AppConfig)` - Successfully loaded configuration +/// * `Err(ConfigError)` - Error loading or parsing configuration pub fn load_config() -> Result { + 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 { + // 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::IoError(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Specified config file not found: {}", path.display()), + ))); + } + + // Otherwise try the standard paths let mut config_paths: Vec = Vec::new(); // User-specific path if let Some(home_dir) = dirs::home_dir() { - let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml"); + let user_config_path = home_dir.join(".config/superfreq/config.toml"); config_paths.push(user_config_path); } else { eprintln!( @@ -20,43 +45,18 @@ pub fn load_config() -> Result { ); } - // System-wide path - let system_config_path = PathBuf::from("/etc/auto_cpufreq_rs/config.toml"); - config_paths.push(system_config_path); + // System-wide paths + config_paths.push(PathBuf::from("/etc/superfreq/config.toml")); + config_paths.push(PathBuf::from("/etc/superfreq.toml")); for path in config_paths { if path.exists() { - println!("Attempting to load config from: {}", path.display()); - match fs::read_to_string(&path) { - Ok(contents) => { - match toml::from_str::(&contents) { - Ok(toml_app_config) => { - // Convert AppConfigToml to AppConfig - let app_config = AppConfig { - charger: ProfileConfig::from(toml_app_config.charger), - battery: ProfileConfig::from(toml_app_config.battery), - battery_charge_thresholds: toml_app_config.battery_charge_thresholds, - ignored_power_supplies: toml_app_config.ignored_power_supplies, - poll_interval_sec: toml_app_config.poll_interval_sec, - daemon: DaemonConfig { - poll_interval_sec: toml_app_config.daemon.poll_interval_sec, - adaptive_interval: toml_app_config.daemon.adaptive_interval, - min_poll_interval_sec: toml_app_config.daemon.min_poll_interval_sec, - max_poll_interval_sec: toml_app_config.daemon.max_poll_interval_sec, - throttle_on_battery: toml_app_config.daemon.throttle_on_battery, - log_level: toml_app_config.daemon.log_level, - stats_file_path: toml_app_config.daemon.stats_file_path, - }, - }; - return Ok(app_config); - } - Err(e) => { - eprintln!("Error parsing config file {}: {}", path.display(), e); - } - } - } + println!("Loading config from: {}", path.display()); + match load_and_parse_config(&path) { + Ok(config) => return Ok(config), Err(e) => { - eprintln!("Error reading config file {}: {}", path.display(), e); + eprintln!("Error with config file {}: {}", path.display(), e); + // Continue trying other files } } } @@ -73,4 +73,30 @@ pub fn load_config() -> Result { poll_interval_sec: default_toml_config.poll_interval_sec, daemon: DaemonConfig::default(), }) -} \ No newline at end of file +} + +/// Load and parse a configuration file +fn load_and_parse_config(path: &Path) -> Result { + let contents = fs::read_to_string(path).map_err(ConfigError::IoError)?; + + let toml_app_config = + toml::from_str::(&contents).map_err(ConfigError::TomlError)?; + + // Convert AppConfigToml to AppConfig + Ok(AppConfig { + charger: ProfileConfig::from(toml_app_config.charger), + battery: ProfileConfig::from(toml_app_config.battery), + battery_charge_thresholds: toml_app_config.battery_charge_thresholds, + ignored_power_supplies: toml_app_config.ignored_power_supplies, + poll_interval_sec: toml_app_config.poll_interval_sec, + daemon: DaemonConfig { + poll_interval_sec: toml_app_config.daemon.poll_interval_sec, + adaptive_interval: toml_app_config.daemon.adaptive_interval, + min_poll_interval_sec: toml_app_config.daemon.min_poll_interval_sec, + max_poll_interval_sec: toml_app_config.daemon.max_poll_interval_sec, + throttle_on_battery: toml_app_config.daemon.throttle_on_battery, + log_level: toml_app_config.daemon.log_level, + stats_file_path: toml_app_config.daemon.stats_file_path, + }, + }) +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 3524422..b386b52 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,9 +1,9 @@ pub mod watcher; // Re-export all configuration types and functions -pub use self::types::*; pub use self::load::*; +pub use self::types::*; // Internal organization of config submodules +mod load; mod types; -mod load; \ No newline at end of file diff --git a/src/config/types.rs b/src/config/types.rs index 8342552..3f4f8ed 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -51,29 +51,29 @@ const fn default_poll_interval_sec() -> u64 { // Error type for config loading #[derive(Debug)] pub enum ConfigError { - Io(std::io::Error), - Toml(toml::de::Error), + IoError(std::io::Error), + TomlError(toml::de::Error), NoValidConfigFound, HomeDirNotFound, } impl From for ConfigError { fn from(err: std::io::Error) -> Self { - Self::Io(err) + Self::IoError(err) } } impl From for ConfigError { fn from(err: toml::de::Error) -> Self { - Self::Toml(err) + Self::TomlError(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::IoError(e) => write!(f, "I/O error: {e}"), + Self::TomlError(e) => write!(f, "TOML parsing error: {e}"), Self::NoValidConfigFound => write!(f, "No valid configuration file found."), Self::HomeDirNotFound => write!(f, "Could not determine user home directory."), } @@ -273,4 +273,4 @@ impl Default for DaemonConfigToml { stats_file_path: default_stats_file_path(), } } -} \ No newline at end of file +} diff --git a/src/config/watcher.rs b/src/config/watcher.rs index 78ece3c..8998281 100644 --- a/src/config/watcher.rs +++ b/src/config/watcher.rs @@ -1,17 +1,18 @@ use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; -use std::path::Path; -use std::sync::mpsc::{channel, Receiver}; -use std::time::Duration; -use std::thread; use std::error::Error; +use std::path::Path; +use std::sync::mpsc::{Receiver, TryRecvError, channel}; +use std::thread; +use std::time::Duration; -use crate::config::{load_config, AppConfig}; +use crate::config::{AppConfig, load_config_from_path}; /// Watches a configuration file for changes and reloads it when modified pub struct ConfigWatcher { rx: Receiver>, _watcher: RecommendedWatcher, // keep watcher alive while watching config_path: String, + last_event_time: std::time::Instant, } impl ConfigWatcher { @@ -29,6 +30,7 @@ impl ConfigWatcher { rx, _watcher: watcher, config_path: config_path.to_string(), + last_event_time: std::time::Instant::now(), }) } @@ -36,33 +38,53 @@ impl ConfigWatcher { /// /// # Returns /// - /// `Some(AppConfig)` if the config was reloaded, `None`` otherwise - pub fn check_for_changes(&self) -> Option>> { - // Non-blocking check for file events - match self.rx.try_recv() { - Ok(Ok(event)) => { - // Only process write/modify events - if matches!(event.kind, EventKind::Modify(_)) { - // Add a small delay to ensure the file write is complete - thread::sleep(Duration::from_millis(100)); + /// `Some(AppConfig)` if the config was reloaded, `None` otherwise + pub fn check_for_changes(&mut self) -> Option>> { + // Process all pending events before deciding to reload + let mut should_reload = false; - // Attempt to reload the config - match load_config() { - Ok(config) => { - println!("Configuration file changed. Reloaded configuration."); - Some(Ok(config)) - } - Err(e) => { - eprintln!("Error reloading configuration: {e}"); - Some(Err(Box::new(e))) - } + loop { + match self.rx.try_recv() { + Ok(Ok(event)) => { + // Only process write/modify events + if matches!(event.kind, EventKind::Modify(_)) { + should_reload = true; + self.last_event_time = std::time::Instant::now(); } - } else { - None + } + Ok(Err(e)) => { + // File watcher error, log but continue + eprintln!("Error watching config file: {e}"); + } + Err(TryRecvError::Empty) => { + // No more events + break; + } + Err(TryRecvError::Disconnected) => { + // Channel disconnected, watcher is dead + eprintln!("Config watcher channel disconnected"); + return None; } } - // No events or channel errors - _ => None, + } + + // Debounce rapid file changes (e.g., from editors that write multiple times) + if should_reload { + // Wait to ensure file writing is complete + let debounce_time = Duration::from_millis(250); + let time_since_last_event = self.last_event_time.elapsed(); + + if time_since_last_event < debounce_time { + thread::sleep(debounce_time - time_since_last_event); + } + + // Attempt to reload the config from the specific path being watched + match load_config_from_path(Some(&self.config_path)) { + Ok(config) => Some(Ok(config)), + Err(e) => Some(Err(Box::new(e))), + } + } else { + None } } @@ -70,4 +92,4 @@ impl ConfigWatcher { pub const fn config_path(&self) -> &String { &self.config_path } -} \ No newline at end of file +} diff --git a/src/cpu.rs b/src/cpu.rs index 09290cf..ac8092c 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -104,11 +104,11 @@ pub fn set_turbo(setting: TurboSetting) -> Result<()> { TurboSetting::Never => "0", // boost = 0 means turbo is disabled TurboSetting::Auto => return Err(ControlError::InvalidValueError("Turbo Auto cannot be directly set via intel_pstate/no_turbo or cpufreq/boost. System default.".to_string())), }; - + // AMD specific paths let amd_pstate_path = "/sys/devices/system/cpu/amd_pstate/cpufreq/boost"; let msr_boost_path = "/sys/devices/system/cpu/cpufreq/amd_pstate_enable_boost"; - + // Path priority (from most to least specific) let pstate_path = "/sys/devices/system/cpu/intel_pstate/no_turbo"; let boost_path = "/sys/devices/system/cpu/cpufreq/boost"; @@ -139,16 +139,16 @@ pub fn set_turbo(setting: TurboSetting) -> Result<()> { fn try_set_per_core_boost(value: &str) -> Result { let mut success = false; let num_cores = get_logical_core_count()?; - + for core_id in 0..num_cores { - let boost_path = format!("/sys/devices/system/cpu/cpu{}/cpufreq/boost", core_id); - + let boost_path = format!("/sys/devices/system/cpu/cpu{core_id}/cpufreq/boost"); + if Path::new(&boost_path).exists() { write_sysfs_value(&boost_path, value)?; success = true; } } - + Ok(success) } diff --git a/src/daemon.rs b/src/daemon.rs index c966af6..b31a7af 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,5 +1,5 @@ -use crate::config::{AppConfig, LogLevel}; use crate::config::watcher::ConfigWatcher; +use crate::config::{AppConfig, LogLevel}; use crate::conflict; use crate::core::SystemReport; use crate::engine; @@ -65,31 +65,45 @@ pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box = match config_file_path { - Some(path) => { - match ConfigWatcher::new(&path) { - Ok(watcher) => { - println!("Watching config file: {path}"); - Some(watcher) - }, - Err(e) => { - eprintln!("Failed to initialize config file watcher: {e}"); - None - } - } - }, - None => None, + + let mut config_watcher = if let Some(path) = config_file_path { match ConfigWatcher::new(&path) { + Ok(watcher) => { + log_message( + &effective_log_level, + LogLevel::Info, + &format!("Watching config file: {path}"), + ); + Some(watcher) + } + Err(e) => { + log_message( + &effective_log_level, + LogLevel::Warning, + &format!("Failed to initialize config file watcher: {e}"), + ); + None + } + } } else { + log_message( + &effective_log_level, + LogLevel::Warning, + "No config file found to watch for changes.", + ); + None }; // Variables for adaptive polling @@ -102,17 +116,27 @@ pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box { - if verbose { - println!("Config file changed, updating configuration"); - } + log_message( + &effective_log_level, + LogLevel::Info, + "Config file changed, updating configuration", + ); config = new_config; - }, + // Reset polling interval after config change + current_poll_interval = config.daemon.poll_interval_sec; + // Record this as a settings change for adaptive polling purposes + last_settings_change = Instant::now(); + } Err(e) => { - eprintln!("Error loading new configuration: {e}"); + log_message( + &effective_log_level, + LogLevel::Error, + &format!("Error loading new configuration: {e}"), + ); // Continue with existing config } } diff --git a/src/engine.rs b/src/engine.rs index 768a192..8611e47 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -131,7 +131,8 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() // Validate the configuration to ensure it's usable if turbo_settings.load_threshold_high <= turbo_settings.load_threshold_low { return Err(EngineError::ConfigurationError( - "Invalid turbo auto settings: high threshold must be greater than low threshold".to_string() + "Invalid turbo auto settings: high threshold must be greater than low threshold" + .to_string(), )); } diff --git a/src/monitor.rs b/src/monitor.rs index 8d1b3d1..259e872 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -414,7 +414,10 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { None }; - let available_governors = if cpufreq_base_path.join("scaling_available_governors").exists() { + let available_governors = if cpufreq_base_path + .join("scaling_available_governors") + .exists() + { read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_available_governors")).map_or_else( |_| vec![], |s| s.split_whitespace().map(String::from).collect(),