diff --git a/Cargo.lock b/Cargo.lock index 59edb23..14dcaa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clap" version = "4.5.38" @@ -110,6 +116,25 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix", + "windows-sys", +] + +[[package]] +name = "daemonize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" +dependencies = [ + "libc", +] + [[package]] name = "dirs" version = "6.0.0" @@ -204,6 +229,18 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -295,6 +332,8 @@ name = "superfreq" version = "0.1.0" dependencies = [ "clap", + "ctrlc", + "daemonize", "dirs", "num_cpus", "serde", diff --git a/Cargo.toml b/Cargo.toml index 8e458e6..97f5926 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ toml = "0.8" dirs = "6.0" clap = { version = "4.0", features = ["derive"] } num_cpus = "1.16" +ctrlc = "3.4" diff --git a/src/config.rs b/src/config.rs index 6a9d4fd..962e801 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,18 +1,19 @@ -use serde::Deserialize; -use std::path::{Path, PathBuf}; -use std::fs; use crate::core::{OperationalMode, TurboSetting}; +use serde::Deserialize; +use std::fs; +use std::path::{Path, PathBuf}; // Structs for configuration using serde::Deserialize #[derive(Deserialize, Debug, Clone)] pub struct ProfileConfig { pub governor: Option, pub turbo: Option, - pub epp: Option, // Energy Performance Preference (EPP) - pub epb: Option, // Energy Performance Bias (EPB) - usually an integer, but string for flexibility from sysfs + pub epp: Option, // Energy Performance Preference (EPP) + pub epb: Option, // Energy Performance Bias (EPB) - usually an integer, but string for flexibility from sysfs pub min_freq_mhz: Option, pub max_freq_mhz: Option, pub platform_profile: Option, + pub turbo_auto_settings: Option, } impl Default for ProfileConfig { @@ -20,11 +21,12 @@ impl Default for ProfileConfig { ProfileConfig { governor: Some("schedutil".to_string()), // common sensible default (?) turbo: Some(TurboSetting::Auto), - epp: None, // defaults depend on governor and system - epb: None, // defaults depend on governor and system - min_freq_mhz: None, // no override - max_freq_mhz: None, // no override + epp: None, // defaults depend on governor and system + epb: None, // defaults depend on governor and system + min_freq_mhz: None, // no override + max_freq_mhz: None, // no override platform_profile: None, // no override + turbo_auto_settings: Some(TurboAutoSettings::default()), } } } @@ -90,7 +92,9 @@ pub fn load_config() -> Result { 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."); + eprintln!( + "Warning: Could not determine home directory. User-specific config will not be loaded." + ); } // System-wide path @@ -108,7 +112,8 @@ pub fn load_config() -> Result { 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, + 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, }; @@ -176,22 +181,59 @@ impl Default for ProfileConfigToml { } } +#[derive(Deserialize, Debug, Clone)] +pub struct TurboAutoSettings { + #[serde(default = "default_load_threshold_high")] + pub load_threshold_high: f32, + #[serde(default = "default_load_threshold_low")] + pub load_threshold_low: f32, + #[serde(default = "default_temp_threshold_high")] + pub temp_threshold_high: f32, +} + +// Default thresholds for Auto turbo mode +pub const DEFAULT_LOAD_THRESHOLD_HIGH: f32 = 70.0; // Enable turbo if load is above this +pub const DEFAULT_LOAD_THRESHOLD_LOW: f32 = 30.0; // Disable turbo if load is below this +pub const DEFAULT_TEMP_THRESHOLD_HIGH: f32 = 75.0; // Disable turbo if temperature is above this + +fn default_load_threshold_high() -> f32 { + DEFAULT_LOAD_THRESHOLD_HIGH +} +fn default_load_threshold_low() -> f32 { + DEFAULT_LOAD_THRESHOLD_LOW +} +fn default_temp_threshold_high() -> f32 { + DEFAULT_TEMP_THRESHOLD_HIGH +} + +impl Default for TurboAutoSettings { + fn default() -> Self { + TurboAutoSettings { + load_threshold_high: DEFAULT_LOAD_THRESHOLD_HIGH, + load_threshold_low: DEFAULT_LOAD_THRESHOLD_LOW, + temp_threshold_high: DEFAULT_TEMP_THRESHOLD_HIGH, + } + } +} impl From for ProfileConfig { fn from(toml_config: ProfileConfigToml) -> Self { ProfileConfig { governor: toml_config.governor, - 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, - }), + 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, + }), epp: toml_config.epp, epb: toml_config.epb, min_freq_mhz: toml_config.min_freq_mhz, max_freq_mhz: toml_config.max_freq_mhz, platform_profile: toml_config.platform_profile, + turbo_auto_settings: Some(TurboAutoSettings::default()), } } } diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 0000000..d302d07 --- /dev/null +++ b/src/daemon.rs @@ -0,0 +1,61 @@ +use crate::config::AppConfig; +use crate::engine; +use crate::monitor; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::{Duration, Instant}; + +/// Run the daemon in foreground mode +pub fn run_background(config: AppConfig) -> Result<(), Box> { + println!("Starting superfreq daemon in foreground mode..."); + + // Create a flag that will be set to true when a signal is received + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + + // Set up signal handlers + ctrlc::set_handler(move || { + println!("Received shutdown signal, exiting..."); + r.store(false, Ordering::SeqCst); + }) + .expect("Error setting Ctrl-C handler"); + + println!( + "Daemon initialized with poll interval: {}s", + config.poll_interval_sec + ); + + // Main loop + while running.load(Ordering::SeqCst) { + let start_time = Instant::now(); + + match monitor::collect_system_report(&config) { + Ok(report) => { + println!("Collected system report, applying settings..."); + match engine::determine_and_apply_settings(&report, &config, None) { + Ok(()) => { + println!("Successfully applied system settings"); + } + Err(e) => { + eprintln!("Error applying system settings: {}", e); + } + } + } + Err(e) => { + eprintln!("Error collecting system report: {}", e); + } + } + + // Sleep for the remaining time in the poll interval + let elapsed = start_time.elapsed(); + let poll_duration = Duration::from_secs(config.poll_interval_sec); + if elapsed < poll_duration { + let sleep_time = poll_duration - elapsed; + println!("Sleeping for {}s until next cycle", sleep_time.as_secs()); + std::thread::sleep(sleep_time); + } + } + + println!("Daemon stopped"); + Ok(()) +} diff --git a/src/engine.rs b/src/engine.rs index d322aaa..3b16af7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,5 +1,5 @@ -use crate::core::{SystemReport, OperationalMode, TurboSetting}; -use crate::config::{AppConfig, ProfileConfig}; +use crate::config::{AppConfig, ProfileConfig, TurboAutoSettings}; +use crate::core::{OperationalMode, SystemReport, TurboSetting}; use crate::cpu::{self, ControlError}; #[derive(Debug)] @@ -57,8 +57,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().map_or(false, |b| b.ac_connected); if on_ac_power { println!("Engine: On AC power, selecting Charger profile."); @@ -80,7 +80,13 @@ pub fn determine_and_apply_settings( if let Some(turbo_setting) = selected_profile_config.turbo { println!("Engine: Setting turbo to '{:?}'", turbo_setting); - cpu::set_turbo(turbo_setting)?; + match turbo_setting { + TurboSetting::Auto => { + println!("Engine: Managing turbo in auto mode based on system conditions"); + manage_auto_turbo(report, selected_profile_config)?; + } + _ => cpu::set_turbo(turbo_setting)?, + } } if let Some(epp) = &selected_profile_config.epp { @@ -89,7 +95,7 @@ pub fn determine_and_apply_settings( } if let Some(epb) = &selected_profile_config.epb { - println!("Engine: Setting EPB to '{}'", epb); + println!("Engine: Setting EPB to '{}'", epb); cpu::set_epb(epb, None)?; } @@ -111,4 +117,86 @@ pub fn determine_and_apply_settings( println!("Engine: Profile settings applied successfully."); Ok(()) -} \ No newline at end of file +} + +fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<(), EngineError> { + // Get the auto turbo settings from the config, or use defaults + let turbo_settings = config.turbo_auto_settings.clone().unwrap_or_default(); + + // Get average CPU temperature and CPU load + let cpu_temp = report.cpu_global.average_temperature_celsius; + + // Check if we have CPU usage data available + let avg_cpu_usage = if !report.cpu_cores.is_empty() { + let sum: f32 = report + .cpu_cores + .iter() + .filter_map(|core| core.usage_percent) + .sum(); + let count = report + .cpu_cores + .iter() + .filter(|core| core.usage_percent.is_some()) + .count(); + + if count > 0 { + Some(sum / count as f32) + } else { + None + } + } else { + None + }; + + // 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 + (Some(temp), _) if temp >= turbo_settings.temp_threshold_high => { + println!( + "Engine: Auto Turbo: Disabled due to high temperature ({:.1}°C >= {:.1}°C)", + temp, turbo_settings.temp_threshold_high + ); + false + } + // If load is high enough, enable turbo (unless temp already caused it to disable) + (_, Some(usage)) if usage >= turbo_settings.load_threshold_high => { + println!( + "Engine: Auto Turbo: Enabled due to high CPU load ({:.1}% >= {:.1}%)", + usage, turbo_settings.load_threshold_high + ); + true + } + // If load is low, disable turbo + (_, Some(usage)) if usage <= turbo_settings.load_threshold_low => { + println!( + "Engine: Auto Turbo: Disabled due to low CPU load ({:.1}% <= {:.1}%)", + usage, turbo_settings.load_threshold_low + ); + false + } + // In intermediate load scenarios or if we can't determine, leave turbo in current state + // For now, we'll disable it as a safe default + _ => { + println!("Engine: Auto Turbo: Disabled (default for indeterminate state)"); + false + } + }; + + // Apply the turbo setting + let turbo_setting = if enable_turbo { + TurboSetting::Always + } else { + TurboSetting::Never + }; + + match cpu::set_turbo(turbo_setting) { + Ok(_) => { + println!( + "Engine: Auto Turbo: Successfully set turbo to {}", + if enable_turbo { "enabled" } else { "disabled" } + ); + Ok(()) + } + Err(e) => Err(EngineError::ControlError(e)), + } +} diff --git a/src/main.rs b/src/main.rs index 3a668a5..4c5cb11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod config; mod core; mod cpu; +mod daemon; mod engine; mod monitor; @@ -19,6 +20,8 @@ struct Cli { enum Commands { /// Display current system information Info, + /// Run as a daemon in the background + Daemon, /// Set CPU governor SetGovernor { governor: String, @@ -183,6 +186,7 @@ fn main() { } Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile) .map_err(|e| Box::new(e) as Box), + Some(Commands::Daemon) => daemon::run_background(config), None => { println!("Welcome to superfreq! Use --help for commands."); println!("Current effective configuration: {:?}", config);