From 23c526a61e0c5f28dffe6faf2eb360c6714a4530 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 09:10:38 +0300 Subject: [PATCH 01/31] cpu: clean up dynamic CPU turbo management --- src/config/types.rs | 6 +++++- src/cpu.rs | 12 ++++++++++-- src/engine.rs | 42 +++++++++++++++++++++++++++++++++++------- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/config/types.rs b/src/config/types.rs index 6446e3e..297b65b 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -124,6 +124,7 @@ pub struct ProfileConfigToml { pub min_freq_mhz: Option, pub max_freq_mhz: Option, pub platform_profile: Option, + pub turbo_auto_settings: Option, #[serde(skip_serializing_if = "Option::is_none")] pub battery_charge_thresholds: Option, } @@ -151,6 +152,7 @@ impl Default for ProfileConfigToml { min_freq_mhz: None, max_freq_mhz: None, platform_profile: None, + turbo_auto_settings: None, battery_charge_thresholds: None, } } @@ -208,7 +210,9 @@ impl From for ProfileConfig { 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()), + turbo_auto_settings: toml_config + .turbo_auto_settings + .or_else(|| Some(TurboAutoSettings::default())), battery_charge_thresholds: toml_config.battery_charge_thresholds, } } diff --git a/src/cpu.rs b/src/cpu.rs index eeb4dfa..cbd37f8 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,6 +1,7 @@ use crate::core::{GovernorOverrideMode, TurboSetting}; use crate::util::error::ControlError; use core::str; +use log::debug; use std::{fs, io, path::Path, string::ToString}; pub type Result = std::result::Result; @@ -216,12 +217,19 @@ pub fn set_turbo(setting: TurboSetting) -> Result<()> { let value_pstate = match setting { TurboSetting::Always => "0", // no_turbo = 0 means turbo is enabled TurboSetting::Never => "1", // no_turbo = 1 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())), + // Auto mode is handled at the engine level, not directly at the sysfs level + TurboSetting::Auto => { + debug!("Turbo Auto mode is managed by engine logic based on system conditions"); + return Ok(()); + } }; 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 => { + debug!("Turbo Auto mode is managed by engine logic based on system conditions"); + return Ok(()); + } }; // AMD specific paths diff --git a/src/engine.rs b/src/engine.rs index 791fa5a..4eb3e6b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,6 +4,7 @@ use crate::core::{OperationalMode, SystemReport, TurboSetting}; use crate::cpu::{self}; use crate::util::error::{ControlError, EngineError}; use log::{debug, info, warn}; +use std::sync::atomic::{AtomicBool, Ordering}; /// Try applying a CPU feature and handle common error cases. Centralizes the where we /// previously did: @@ -172,6 +173,10 @@ pub fn determine_and_apply_settings( Ok(()) } +// Keep track of current auto turbo state for hysteresis using thread-safe atomics +static PREVIOUS_TURBO_STATE: AtomicBool = AtomicBool::new(false); +static TURBO_STATE_INITIALIZED: AtomicBool = AtomicBool::new(false); + 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(); @@ -204,10 +209,18 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() } }; - // Decision logic for enabling/disabling turbo - let enable_turbo = match (cpu_temp, avg_cpu_usage) { + // Get previous state safely using atomic operations + let has_previous_state = TURBO_STATE_INITIALIZED.load(Ordering::Relaxed); + let previous_turbo_enabled = if has_previous_state { + Some(PREVIOUS_TURBO_STATE.load(Ordering::Relaxed)) + } else { + None + }; + + // Decision logic for enabling/disabling turbo with hysteresis + let enable_turbo = match (cpu_temp, avg_cpu_usage, previous_turbo_enabled) { // If temperature is too high, disable turbo regardless of load - (Some(temp), _) if temp >= turbo_settings.temp_threshold_high => { + (Some(temp), _, _) if temp >= turbo_settings.temp_threshold_high => { info!( "Auto Turbo: Disabled due to high temperature ({:.1}°C >= {:.1}°C)", temp, turbo_settings.temp_threshold_high @@ -215,7 +228,7 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() false } // If load is high enough, enable turbo (unless temp already caused it to disable) - (_, Some(usage)) if usage >= turbo_settings.load_threshold_high => { + (_, Some(usage), _) if usage >= turbo_settings.load_threshold_high => { info!( "Auto Turbo: Enabled due to high CPU load ({:.1}% >= {:.1}%)", usage, turbo_settings.load_threshold_high @@ -223,21 +236,36 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() true } // If load is low, disable turbo - (_, Some(usage)) if usage <= turbo_settings.load_threshold_low => { + (_, Some(usage), _) if usage <= turbo_settings.load_threshold_low => { info!( "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 + // In intermediate load range, maintain previous state (hysteresis) + (_, Some(usage), Some(prev_state)) + if usage > turbo_settings.load_threshold_low + && usage < turbo_settings.load_threshold_high => + { + info!( + "Auto Turbo: Maintaining previous state ({}) due to intermediate load ({:.1}%)", + if prev_state { "enabled" } else { "disabled" }, + usage + ); + prev_state + } + // In indeterminate states or unknown previous state, default to disabled _ => { info!("Auto Turbo: Disabled (default for indeterminate state)"); false } }; + // Save the current state for next time using atomic operations + PREVIOUS_TURBO_STATE.store(enable_turbo, Ordering::Relaxed); + TURBO_STATE_INITIALIZED.store(true, Ordering::Relaxed); + // Apply the turbo setting let turbo_setting = if enable_turbo { TurboSetting::Always From 9935ae9fe355cc3c6128eae8909a992ecfbf8be8 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 11:11:24 +0300 Subject: [PATCH 02/31] engine: allow users to explicitly disable auto-turbo --- src/config/types.rs | 10 ++++++++++ src/engine.rs | 11 +++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/config/types.rs b/src/config/types.rs index 297b65b..dba5c49 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -51,6 +51,7 @@ pub struct ProfileConfig { pub max_freq_mhz: Option, pub platform_profile: Option, pub turbo_auto_settings: Option, + pub enable_auto_turbo: bool, #[serde(skip_serializing_if = "Option::is_none")] pub battery_charge_thresholds: Option, } @@ -66,6 +67,7 @@ impl Default for ProfileConfig { max_freq_mhz: None, // no override platform_profile: None, // no override turbo_auto_settings: Some(TurboAutoSettings::default()), + enable_auto_turbo: default_enable_auto_turbo(), battery_charge_thresholds: None, } } @@ -125,6 +127,8 @@ pub struct ProfileConfigToml { pub max_freq_mhz: Option, pub platform_profile: Option, pub turbo_auto_settings: Option, + #[serde(default = "default_enable_auto_turbo")] + pub enable_auto_turbo: bool, #[serde(skip_serializing_if = "Option::is_none")] pub battery_charge_thresholds: Option, } @@ -153,6 +157,7 @@ impl Default for ProfileConfigToml { max_freq_mhz: None, platform_profile: None, turbo_auto_settings: None, + enable_auto_turbo: default_enable_auto_turbo(), battery_charge_thresholds: None, } } @@ -213,6 +218,7 @@ impl From for ProfileConfig { turbo_auto_settings: toml_config .turbo_auto_settings .or_else(|| Some(TurboAutoSettings::default())), + enable_auto_turbo: toml_config.enable_auto_turbo, battery_charge_thresholds: toml_config.battery_charge_thresholds, } } @@ -286,6 +292,10 @@ const fn default_stats_file_path() -> Option { None } +const fn default_enable_auto_turbo() -> bool { + true +} + #[derive(Deserialize, Serialize, Debug, Clone)] pub struct DaemonConfigToml { #[serde(default = "default_poll_interval_sec")] diff --git a/src/engine.rs b/src/engine.rs index 4eb3e6b..cb56254 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -113,8 +113,15 @@ pub fn determine_and_apply_settings( info!("Setting turbo to '{turbo_setting:?}'"); match turbo_setting { TurboSetting::Auto => { - debug!("Managing turbo in auto mode based on system conditions"); - manage_auto_turbo(report, selected_profile_config)?; + if selected_profile_config.enable_auto_turbo { + debug!("Managing turbo in auto mode based on system conditions"); + manage_auto_turbo(report, selected_profile_config)?; + } else { + debug!("Auto turbo management disabled by configuration, using system default behavior"); + try_apply_feature("Turbo boost", "system default (Auto)", || { + cpu::set_turbo(turbo_setting) + })?; + } } _ => { try_apply_feature("Turbo boost", &format!("{turbo_setting:?}"), || { From 7a708c60d7cefdbc8c3fd75fd3ca5f8acee36af3 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 11:41:42 +0300 Subject: [PATCH 03/31] docs: mention new configuration options; document dynamic turbo mode --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0488b8f..fd4a5ac 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ but most common usecases are already implemented. and turbo boost - **Intelligent Power Management**: Different profiles for AC and battery operation +- **Dynamic Turbo Boost Control**: Automatically enables/disables turbo based on + CPU load and temperature - **Fine-tuned Controls**: Adjust energy performance preferences, biases, and frequency limits - **Per-core Control**: Apply settings globally or to specific CPU cores @@ -150,6 +152,10 @@ variable. governor = "performance" # Turbo boost setting: "always", "auto", or "never" turbo = "auto" +# Enable or disable automatic turbo management (when turbo = "auto") +enable_auto_turbo = true +# Custom thresholds for auto turbo management +turbo_auto_settings = { load_threshold_high = 70.0, load_threshold_low = 30.0, temp_threshold_high = 75.0 } # Energy Performance Preference epp = "performance" # Energy Performance Bias (0-15 scale or named value) @@ -166,6 +172,9 @@ max_freq_mhz = 3500 [battery] governor = "powersave" turbo = "auto" +# More conservative auto turbo settings on battery +enable_auto_turbo = true +turbo_auto_settings = { load_threshold_high = 80.0, load_threshold_low = 40.0, temp_threshold_high = 70.0 } epp = "power" epb = "balance_power" platform_profile = "low-power" @@ -209,6 +218,27 @@ Those are the more advanced features of Superfreq that some users might be more inclined to use than others. If you have a use-case that is not covered, please create an issue. +### Dynamic Turbo Boost Management + +When using `turbo = "auto"` with `enable_auto_turbo = true`, Superfreq +dynamically controls CPU turbo boost based on: + +- **CPU Load Thresholds**: Enables turbo when load exceeds `load_threshold_high` + (default 70%), disables when below `load_threshold_low` (default 30%) +- **Temperature Protection**: Automatically disables turbo when CPU temperature + exceeds `temp_threshold_high` (default 75°C) +- **Hysteresis Control**: Prevents rapid toggling by maintaining previous state + when load is between thresholds +- **Profile-Specific Settings**: Configure different thresholds for battery vs. + AC power + +This feature optimizes performance and power consumption by providing maximum +performance for demanding tasks while conserving energy during light workloads. + +> [!TIP] +> You can disable this logic with `enable_auto_turbo = false` to let the system +> handle turbo boost natively when `turbo = "auto"`. + ### Adaptive Polling Superfreq includes a "sophisticated" (euphemism for complicated) adaptive @@ -275,7 +305,8 @@ the codebase as they stand. ### Setup -You will need Cargo and Rust installed on your system. Rust 1.80 or later is required. +You will need Cargo and Rust installed on your system. Rust 1.80 or later is +required. A `.envrc` is provided, and it's usage is encouraged for Nix users. Alternatively, you may use Nix for a reproducible developer environment From 1b31dd9e1e14db3f1ce1566d23e5a96e0676e5c4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 11:48:09 +0300 Subject: [PATCH 04/31] cli: pad output to prevent misaligned text --- src/engine.rs | 4 +++- src/main.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index cb56254..3faa849 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -117,7 +117,9 @@ pub fn determine_and_apply_settings( debug!("Managing turbo in auto mode based on system conditions"); manage_auto_turbo(report, selected_profile_config)?; } else { - debug!("Auto turbo management disabled by configuration, using system default behavior"); + debug!( + "Auto turbo management disabled by configuration, using system default behavior" + ); try_apply_feature("Turbo boost", "system default (Auto)", || { cpu::set_turbo(turbo_setting) })?; diff --git a/src/main.rs b/src/main.rs index 6e26645..f6303dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,7 +140,7 @@ fn main() -> Result<(), AppError> { format_section("CPU Global Info"); println!( - "Current Governor: {}", + "Current Governor: {}", report .cpu_global .current_governor @@ -148,11 +148,11 @@ fn main() -> Result<(), AppError> { .unwrap_or("N/A") ); println!( - "Available Governors: {}", + "Available Governors: {}", // 21 length baseline report.cpu_global.available_governors.join(", ") ); println!( - "Turbo Status: {}", + "Turbo Status: {}", match report.cpu_global.turbo_status { Some(true) => "Enabled", Some(false) => "Disabled", @@ -161,15 +161,15 @@ fn main() -> Result<(), AppError> { ); println!( - "EPP: {}", + "EPP: {}", report.cpu_global.epp.as_deref().unwrap_or("N/A") ); println!( - "EPB: {}", + "EPB: {}", report.cpu_global.epb.as_deref().unwrap_or("N/A") ); println!( - "Platform Profile: {}", + "Platform Profile: {}", report .cpu_global .platform_profile @@ -177,7 +177,7 @@ fn main() -> Result<(), AppError> { .unwrap_or("N/A") ); println!( - "CPU Temperature: {}", + "CPU Temperature: {}", report.cpu_global.average_temperature_celsius.map_or_else( || "N/A (No sensor detected)".to_string(), |t| format!("{t:.1}°C") From 806cf64d29a25dbdb7a8c14ed23434cde9364081 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 14:25:37 +0300 Subject: [PATCH 05/31] engine: improve atomic operations for turbo state management --- src/engine.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 3faa849..c305567 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -219,9 +219,9 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() }; // Get previous state safely using atomic operations - let has_previous_state = TURBO_STATE_INITIALIZED.load(Ordering::Relaxed); + let has_previous_state = TURBO_STATE_INITIALIZED.load(Ordering::Acquire); let previous_turbo_enabled = if has_previous_state { - Some(PREVIOUS_TURBO_STATE.load(Ordering::Relaxed)) + Some(PREVIOUS_TURBO_STATE.load(Ordering::Acquire)) } else { None }; @@ -272,8 +272,8 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() }; // Save the current state for next time using atomic operations - PREVIOUS_TURBO_STATE.store(enable_turbo, Ordering::Relaxed); - TURBO_STATE_INITIALIZED.store(true, Ordering::Relaxed); + PREVIOUS_TURBO_STATE.store(enable_turbo, Ordering::Release); + TURBO_STATE_INITIALIZED.store(true, Ordering::Release); // Apply the turbo setting let turbo_setting = if enable_turbo { From cbaab9f160e1775370c11aeb2ed83c57ee6bf99d Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 17:42:10 +0300 Subject: [PATCH 06/31] cpu: simplify turbo management --- src/cpu.rs | 19 ++++++++++--------- src/engine.rs | 6 ++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index cbd37f8..a5f7c28 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -213,23 +213,24 @@ fn get_available_governors() -> Result> { )) } +// FIXME: I think the Auto Turbo behaviour is still pretty confusing for the end-user +// who might not have read the documentation in detail. We could just make the program +// more verbose here, but I think this is a fundamental design flaw that I will want +// to refactor in the future. For now though, I think this is a good-ish solution. pub fn set_turbo(setting: TurboSetting) -> Result<()> { let value_pstate = match setting { TurboSetting::Always => "0", // no_turbo = 0 means turbo is enabled TurboSetting::Never => "1", // no_turbo = 1 means turbo is disabled - // Auto mode is handled at the engine level, not directly at the sysfs level - TurboSetting::Auto => { - debug!("Turbo Auto mode is managed by engine logic based on system conditions"); - return Ok(()); - } + // For Auto, we need to enable the hardware default (which is turbo enabled) + // and we reset to the system default when explicitly set to Auto + TurboSetting::Auto => "0", // Set to enabled (default hardware state) when Auto is requested }; 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 => { - debug!("Turbo Auto mode is managed by engine logic based on system conditions"); - return Ok(()); - } + // For Auto, we need to enable the hardware default (which is turbo enabled) + // and we reset to the system default when explicitly set to Auto + TurboSetting::Auto => "1", // Set to enabled (default hardware state) when Auto is requested }; // AMD specific paths diff --git a/src/engine.rs b/src/engine.rs index c305567..9c93e92 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -118,10 +118,12 @@ pub fn determine_and_apply_settings( manage_auto_turbo(report, selected_profile_config)?; } else { debug!( - "Auto turbo management disabled by configuration, using system default behavior" + "Superfreq's dynamic turbo management is disabled by configuration. Ensuring system uses its default behavior for automatic turbo control." ); + // Make sure the system is set to its default automatic turbo mode. + // This is important if turbo was previously forced off. try_apply_feature("Turbo boost", "system default (Auto)", || { - cpu::set_turbo(turbo_setting) + cpu::set_turbo(TurboSetting::Auto) })?; } } From 2be0f41924716f8796749532380e8e40d277f6e3 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 17:43:18 +0300 Subject: [PATCH 07/31] docs: clarify turbo management options --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd4a5ac..6357795 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ Superfreq is a modern CPU frequency and power management utility for Linux systems. It provides intelligent control of CPU governors, frequencies, and power-saving features, helping optimize both performance and battery life. -It is greatly inspired by auto_cpufreq, but rewritten from ground up to provide +It is greatly inspired by auto-cpufreq, but rewritten from ground up to provide a smoother experience with a more efficient and more correct codebase. Some -features are omitted, and it is _not_ a drop-in replacement for auto_cpufreq, +features are omitted, and it is _not_ a drop-in replacement for auto-cpufreq, but most common usecases are already implemented. ## Features @@ -239,6 +239,22 @@ performance for demanding tasks while conserving energy during light workloads. > You can disable this logic with `enable_auto_turbo = false` to let the system > handle turbo boost natively when `turbo = "auto"`. +#### Turbo Boost Behavior Table + +The table below explains how different combinations of `turbo` and +`enable_auto_turbo` settings affect CPU turbo behavior: + +| Setting | `enable_auto_turbo = true` | `enable_auto_turbo = false` | +| ------------------ | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `turbo = "always"` | **Always enabled**
Turbo is always active regardless of CPU load or temperature | **Always enabled**
Turbo is always active regardless of CPU load or temperature | +| `turbo = "never"` | **Always disabled**
Turbo is always disabled regardless of CPU load or temperature | **Always disabled**
Turbo is always disabled regardless of CPU load or temperature | +| `turbo = "auto"` | **Dynamically managed**
Superfreq enables/disables turbo based on CPU load and temperature thresholds | **System default**
Turbo is reset to system's default enabled state and is managed by the hardware/kernel | + +> [!NOTE] +> When `turbo = "auto"` and `enable_auto_turbo = false`, Superfreq ensures that +> any previous turbo state restrictions are removed, allowing the +> hardware/kernel to manage turbo behavior according to its default algorithms. + ### Adaptive Polling Superfreq includes a "sophisticated" (euphemism for complicated) adaptive From 65e380635f2ff5ad148e37f9ae9b880b275cef08 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 18:27:08 +0300 Subject: [PATCH 08/31] cpu: remove unused import --- src/cpu.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cpu.rs b/src/cpu.rs index a5f7c28..919303b 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,7 +1,6 @@ use crate::core::{GovernorOverrideMode, TurboSetting}; use crate::util::error::ControlError; use core::str; -use log::debug; use std::{fs, io, path::Path, string::ToString}; pub type Result = std::result::Result; From aec7c6b9d43ab96fd325c8b574689f00d2dfcfbf Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 18:44:31 +0300 Subject: [PATCH 09/31] config: set default turbo auto settings --- src/config/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/types.rs b/src/config/types.rs index dba5c49..83d268e 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -156,7 +156,7 @@ impl Default for ProfileConfigToml { min_freq_mhz: None, max_freq_mhz: None, platform_profile: None, - turbo_auto_settings: None, + turbo_auto_settings: Some(TurboAutoSettings::default()), enable_auto_turbo: default_enable_auto_turbo(), battery_charge_thresholds: None, } From 9c692dd103564a4e041b7db43e752f546d21ae53 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 19:04:11 +0300 Subject: [PATCH 10/31] meta: populate Cargo manifest --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index aed276a..e606e85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "superfreq" +description = "Modern CPU frequency and power management utility for Linux" version = "0.2.0" edition = "2024" +authors = ["NotAShelf "] +rust-version = "1.85" [dependencies] serde = { version = "1.0", features = ["derive"] } From a1c8190c287235c214c6ea90a38a6d3ee6adf84c Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 19:04:27 +0300 Subject: [PATCH 11/31] docs: update minimum required rust version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6357795..6adba66 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ the codebase as they stand. ### Setup -You will need Cargo and Rust installed on your system. Rust 1.80 or later is +You will need Cargo and Rust installed on your system. Rust 1.85 or later is required. A `.envrc` is provided, and it's usage is encouraged for Nix users. From 04b01aa070d6911366f1833e5998d71413ba7b2d Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 May 2025 19:06:07 +0300 Subject: [PATCH 12/31] cpu: enhance document `set_turbo` function and `Auto` mode behavior --- src/cpu.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index 919303b..b5b3704 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -212,24 +212,44 @@ fn get_available_governors() -> Result> { )) } -// FIXME: I think the Auto Turbo behaviour is still pretty confusing for the end-user -// who might not have read the documentation in detail. We could just make the program -// more verbose here, but I think this is a fundamental design flaw that I will want -// to refactor in the future. For now though, I think this is a good-ish solution. +/// Controls CPU turbo boost behavior by writing to appropriate sysfs files. +/// +/// # Parameters +/// +/// * `setting` - The desired turbo boost setting: +/// - `TurboSetting::Always`: Forces turbo boost to be always enabled +/// - `TurboSetting::Never`: Forces turbo boost to be always disabled +/// - `TurboSetting::Auto`: Has two distinct behaviors depending on the context: +/// 1. When called directly from user commands: Resets turbo to system default (enabled) +/// 2. When used with `enable_auto_turbo=true` in config: Managed dynamically by the engine +/// +/// # Turbo Auto Mode Explained +/// +/// When `TurboSetting::Auto` is used: +/// - This function writes the same values as `TurboSetting::Always` to reset the hardware +/// to its default state (turbo enabled) +/// - The actual dynamic management happens at a higher level in the engine module +/// when `enable_auto_turbo=true` +/// - With `enable_auto_turbo=false`, the system's native turbo management takes over +/// +/// +/// # Returns +/// +/// * `Result<()>` - Success or an error if the operation failed pub fn set_turbo(setting: TurboSetting) -> Result<()> { let value_pstate = match setting { TurboSetting::Always => "0", // no_turbo = 0 means turbo is enabled TurboSetting::Never => "1", // no_turbo = 1 means turbo is disabled // For Auto, we need to enable the hardware default (which is turbo enabled) // and we reset to the system default when explicitly set to Auto - TurboSetting::Auto => "0", // Set to enabled (default hardware state) when Auto is requested + TurboSetting::Auto => "0", // set to enabled (default hardware state) when Auto is requested }; let value_boost = match setting { TurboSetting::Always => "1", // boost = 1 means turbo is enabled TurboSetting::Never => "0", // boost = 0 means turbo is disabled // For Auto, we need to enable the hardware default (which is turbo enabled) // and we reset to the system default when explicitly set to Auto - TurboSetting::Auto => "1", // Set to enabled (default hardware state) when Auto is requested + TurboSetting::Auto => "1", // set to enabled (default hardware state) when Auto is requested }; // AMD specific paths From a084f4e7b18412df1350a1ca21bad311c5cabdb2 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 02:01:53 +0300 Subject: [PATCH 13/31] cpu: implement TurboHysteresis for managing turbo boost state Hysteresis mechanism to prevent state bleeding across different engine instances. --- src/engine.rs | 67 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 9c93e92..91bc5aa 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -6,6 +6,46 @@ use crate::util::error::{ControlError, EngineError}; use log::{debug, info, warn}; use std::sync::atomic::{AtomicBool, Ordering}; +/// Manage turbo boost hysteresis state. +/// Contains the state needed to implement hysteresis +/// for the dynamic turbo management feature +struct TurboHysteresis { + /// Whether turbo was enabled in the previous cycle + previous_state: AtomicBool, + /// Whether the hysteresis state has been initialized + initialized: AtomicBool, +} + +impl TurboHysteresis { + const fn new() -> Self { + Self { + previous_state: AtomicBool::new(false), + initialized: AtomicBool::new(false), + } + } + + /// Get the previous turbo state, if initialized + fn get_previous_state(&self) -> Option { + if self.initialized.load(Ordering::Acquire) { + Some(self.previous_state.load(Ordering::Acquire)) + } else { + None + } + } + + /// Update the turbo state for hysteresis + fn update_state(&self, new_state: bool) { + self.previous_state.store(new_state, Ordering::Release); + self.initialized.store(true, Ordering::Release); + } +} + +// Thread-local instance of TurboHysteresis for each profile +thread_local! { + static CHARGER_TURBO_HYSTERESIS: TurboHysteresis = const { TurboHysteresis::new() }; + static BATTERY_TURBO_HYSTERESIS: TurboHysteresis = const { TurboHysteresis::new() }; +} + /// Try applying a CPU feature and handle common error cases. Centralizes the where we /// previously did: /// 1. Try to apply a feature setting @@ -184,10 +224,6 @@ pub fn determine_and_apply_settings( Ok(()) } -// Keep track of current auto turbo state for hysteresis using thread-safe atomics -static PREVIOUS_TURBO_STATE: AtomicBool = AtomicBool::new(false); -static TURBO_STATE_INITIALIZED: AtomicBool = AtomicBool::new(false); - 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(); @@ -220,12 +256,16 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() } }; - // Get previous state safely using atomic operations - let has_previous_state = TURBO_STATE_INITIALIZED.load(Ordering::Acquire); - let previous_turbo_enabled = if has_previous_state { - Some(PREVIOUS_TURBO_STATE.load(Ordering::Acquire)) + // Use the appropriate hysteresis object based on whether we're on battery or AC power + // We don't have direct access to the AppConfig here, so we need to make a determination + // based on other factors like whether turbo_auto_settings are more conservative (typically battery profile) + // or by checking if temperature thresholds are lower (typically battery profile) + let is_on_ac = report.batteries.iter().any(|b| b.ac_connected); + + let previous_turbo_enabled = if is_on_ac { + CHARGER_TURBO_HYSTERESIS.with(TurboHysteresis::get_previous_state) } else { - None + BATTERY_TURBO_HYSTERESIS.with(TurboHysteresis::get_previous_state) }; // Decision logic for enabling/disabling turbo with hysteresis @@ -273,9 +313,12 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() } }; - // Save the current state for next time using atomic operations - PREVIOUS_TURBO_STATE.store(enable_turbo, Ordering::Release); - TURBO_STATE_INITIALIZED.store(true, Ordering::Release); + // Save the current state for next time using the appropriate thread-local hysteresis object + if is_on_ac { + CHARGER_TURBO_HYSTERESIS.with(|h| h.update_state(enable_turbo)); + } else { + BATTERY_TURBO_HYSTERESIS.with(|h| h.update_state(enable_turbo)); + } // Apply the turbo setting let turbo_setting = if enable_turbo { From e6d8f8c2dad6a4fab1c02b98e604e56e724b11c6 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 02:25:35 +0300 Subject: [PATCH 14/31] engine: allow configuring initial turbo state --- src/config/types.rs | 7 +++++++ src/engine.rs | 45 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/config/types.rs b/src/config/types.rs index 83d268e..b64064e 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -171,12 +171,15 @@ pub struct TurboAutoSettings { pub load_threshold_low: f32, #[serde(default = "default_temp_threshold_high")] pub temp_threshold_high: f32, + #[serde(default = "default_initial_turbo_state")] + pub initial_turbo_state: bool, } // 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 +pub const DEFAULT_INITIAL_TURBO_STATE: bool = false; // by default, start with turbo disabled const fn default_load_threshold_high() -> f32 { DEFAULT_LOAD_THRESHOLD_HIGH @@ -187,6 +190,9 @@ const fn default_load_threshold_low() -> f32 { const fn default_temp_threshold_high() -> f32 { DEFAULT_TEMP_THRESHOLD_HIGH } +const fn default_initial_turbo_state() -> bool { + DEFAULT_INITIAL_TURBO_STATE +} impl Default for TurboAutoSettings { fn default() -> Self { @@ -194,6 +200,7 @@ impl Default for TurboAutoSettings { load_threshold_high: DEFAULT_LOAD_THRESHOLD_HIGH, load_threshold_low: DEFAULT_LOAD_THRESHOLD_LOW, temp_threshold_high: DEFAULT_TEMP_THRESHOLD_HIGH, + initial_turbo_state: DEFAULT_INITIAL_TURBO_STATE, } } } diff --git a/src/engine.rs b/src/engine.rs index 91bc5aa..c545970 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -33,6 +33,16 @@ impl TurboHysteresis { } } + /// Initialize the state with a specific value if not already initialized + fn initialize_with(&self, initial_state: bool) -> bool { + if !self.initialized.load(Ordering::Acquire) { + self.previous_state.store(initial_state, Ordering::Release); + self.initialized.store(true, Ordering::Release); + return initial_state; + } + self.previous_state.load(Ordering::Acquire) + } + /// Update the turbo state for hysteresis fn update_state(&self, new_state: bool) { self.previous_state.store(new_state, Ordering::Release); @@ -257,15 +267,27 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() }; // Use the appropriate hysteresis object based on whether we're on battery or AC power - // We don't have direct access to the AppConfig here, so we need to make a determination - // based on other factors like whether turbo_auto_settings are more conservative (typically battery profile) - // or by checking if temperature thresholds are lower (typically battery profile) let is_on_ac = report.batteries.iter().any(|b| b.ac_connected); + // Get the previous state or initialize with the configured initial state let previous_turbo_enabled = if is_on_ac { - CHARGER_TURBO_HYSTERESIS.with(TurboHysteresis::get_previous_state) + CHARGER_TURBO_HYSTERESIS.with(|h| { + if let Some(state) = h.get_previous_state() { + Some(state) + } else { + // Initialize with the configured initial state and return it + Some(h.initialize_with(turbo_settings.initial_turbo_state)) + } + }) } else { - BATTERY_TURBO_HYSTERESIS.with(TurboHysteresis::get_previous_state) + BATTERY_TURBO_HYSTERESIS.with(|h| { + if let Some(state) = h.get_previous_state() { + Some(state) + } else { + // Initialize with the configured initial state and return it + Some(h.initialize_with(turbo_settings.initial_turbo_state)) + } + }) }; // Decision logic for enabling/disabling turbo with hysteresis @@ -306,10 +328,17 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() ); prev_state } - // In indeterminate states or unknown previous state, default to disabled + // In indeterminate states or unknown previous state, use the configured initial state _ => { - info!("Auto Turbo: Disabled (default for indeterminate state)"); - false + info!( + "Auto Turbo: Using configured initial state ({})", + if turbo_settings.initial_turbo_state { + "enabled" + } else { + "disabled" + } + ); + turbo_settings.initial_turbo_state } }; From ceeb33b17dab11558abfce1a342a1c35c6d3f2ae Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 02:29:45 +0300 Subject: [PATCH 15/31] docs: update example config --- README.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6adba66..c1aff74 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,12 @@ turbo = "auto" # Enable or disable automatic turbo management (when turbo = "auto") enable_auto_turbo = true # Custom thresholds for auto turbo management -turbo_auto_settings = { load_threshold_high = 70.0, load_threshold_low = 30.0, temp_threshold_high = 75.0 } +turbo_auto_settings = { + load_threshold_high = 70.0, + load_threshold_low = 30.0, + temp_threshold_high = 75.0, + initial_turbo_state = false, # whether turbo should be initially enabled (false = disabled) +} # Energy Performance Preference epp = "performance" # Energy Performance Bias (0-15 scale or named value) @@ -174,7 +179,12 @@ governor = "powersave" turbo = "auto" # More conservative auto turbo settings on battery enable_auto_turbo = true -turbo_auto_settings = { load_threshold_high = 80.0, load_threshold_low = 40.0, temp_threshold_high = 70.0 } +turbo_auto_settings = { + load_threshold_high = 80.0, + load_threshold_low = 40.0, + temp_threshold_high = 70.0, + initial_turbo_state = false, # start with turbo disabled on battery for power savings +} epp = "power" epb = "balance_power" platform_profile = "low-power" @@ -229,6 +239,8 @@ dynamically controls CPU turbo boost based on: exceeds `temp_threshold_high` (default 75°C) - **Hysteresis Control**: Prevents rapid toggling by maintaining previous state when load is between thresholds +- **Configurable Initial State**: Sets the initial turbo state via + `initial_turbo_state` (default: disabled) before system load data is available - **Profile-Specific Settings**: Configure different thresholds for battery vs. AC power @@ -314,10 +326,11 @@ While reporting issues, please attach the results from `superfreq debug`. Contributions to Superfreq are always welcome! Whether it's bug reports, feature requests, or code contributions, please feel free to contribute. -If you are looking to reimplement features from auto_cpufreq, please consider -opening an issue first and let us know what you have in mind. Certain features -(such as the system tray) are deliberately ignored, and might not be desired in -the codebase as they stand. +> [!NOTE] +> If you are looking to reimplement features from auto-cpufreq, please consider +> opening an issue first and let us know what you have in mind. Certain features +> (such as the system tray) are deliberately ignored, and might not be desired +> in the codebase as they stand. Please discuss those features with us first :) ### Setup @@ -332,9 +345,9 @@ nix develop ``` Non-Nix users may get the appropriate Cargo and Rust versions from their package -manager. +manager, or using something like Rustup. -### Formatting +### Formatting & Lints Please make sure to run _at least_ `cargo fmt` inside the repository to make sure all of your code is properly formatted. For Nix code, please use Alejandra. From 8866eb4485d3831b667ae59ed8899552e5776857 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 02:39:45 +0300 Subject: [PATCH 16/31] cpu: fix turbo management on systems without batteries (desktops) --- src/config/types.rs | 2 +- src/engine.rs | 86 +++++++++++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/config/types.rs b/src/config/types.rs index b64064e..5d7e59e 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -156,7 +156,7 @@ impl Default for ProfileConfigToml { min_freq_mhz: None, max_freq_mhz: None, platform_profile: None, - turbo_auto_settings: Some(TurboAutoSettings::default()), + turbo_auto_settings: None, enable_auto_turbo: default_enable_auto_turbo(), battery_charge_thresholds: None, } diff --git a/src/engine.rs b/src/engine.rs index c545970..38c4400 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -5,6 +5,40 @@ use crate::cpu::{self}; use crate::util::error::{ControlError, EngineError}; use log::{debug, info, warn}; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Mutex, OnceLock}; + +/// A struct to track turbo boost state for AC and battery power modes +struct TurboHysteresisStates { + /// State for when on AC power + charger: TurboHysteresis, + /// State for when on battery power + battery: TurboHysteresis, +} + +impl TurboHysteresisStates { + const fn new() -> Self { + Self { + charger: TurboHysteresis::new(), + battery: TurboHysteresis::new(), + } + } + + const fn get_for_power_state(&self, is_on_ac: bool) -> &TurboHysteresis { + if is_on_ac { + &self.charger + } else { + &self.battery + } + } +} + +/// Create a global instance of `TurboHysteresisStates` protected by a mutex +static TURBO_STATES: OnceLock> = OnceLock::new(); + +/// Get or initialize the global turbo states +fn get_turbo_states() -> &'static Mutex { + TURBO_STATES.get_or_init(|| Mutex::new(TurboHysteresisStates::new())) +} /// Manage turbo boost hysteresis state. /// Contains the state needed to implement hysteresis @@ -50,12 +84,6 @@ impl TurboHysteresis { } } -// Thread-local instance of TurboHysteresis for each profile -thread_local! { - static CHARGER_TURBO_HYSTERESIS: TurboHysteresis = const { TurboHysteresis::new() }; - static BATTERY_TURBO_HYSTERESIS: TurboHysteresis = const { TurboHysteresis::new() }; -} - /// Try applying a CPU feature and handle common error cases. Centralizes the where we /// previously did: /// 1. Try to apply a feature setting @@ -267,27 +295,25 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() }; // Use the appropriate hysteresis object based on whether we're on battery or AC power - let is_on_ac = report.batteries.iter().any(|b| b.ac_connected); + // For systems without batteries (desktops), we should always use the AC power hysteresis + let is_on_ac = if report.batteries.is_empty() { + // No batteries means desktop/server, always on AC + true + } else { + // Check if any battery reports AC connected + report.batteries.iter().any(|b| b.ac_connected) + }; // Get the previous state or initialize with the configured initial state - let previous_turbo_enabled = if is_on_ac { - CHARGER_TURBO_HYSTERESIS.with(|h| { - if let Some(state) = h.get_previous_state() { - Some(state) - } else { - // Initialize with the configured initial state and return it - Some(h.initialize_with(turbo_settings.initial_turbo_state)) - } - }) - } else { - BATTERY_TURBO_HYSTERESIS.with(|h| { - if let Some(state) = h.get_previous_state() { - Some(state) - } else { - // Initialize with the configured initial state and return it - Some(h.initialize_with(turbo_settings.initial_turbo_state)) - } - }) + let previous_turbo_enabled = { + let turbo_states = get_turbo_states().lock().unwrap(); + let hysteresis = turbo_states.get_for_power_state(is_on_ac); + if let Some(state) = hysteresis.get_previous_state() { + Some(state) + } else { + // Initialize with the configured initial state and return it + Some(hysteresis.initialize_with(turbo_settings.initial_turbo_state)) + } }; // Decision logic for enabling/disabling turbo with hysteresis @@ -342,11 +368,11 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() } }; - // Save the current state for next time using the appropriate thread-local hysteresis object - if is_on_ac { - CHARGER_TURBO_HYSTERESIS.with(|h| h.update_state(enable_turbo)); - } else { - BATTERY_TURBO_HYSTERESIS.with(|h| h.update_state(enable_turbo)); + // Save the current state for next time + { + let turbo_states = get_turbo_states().lock().unwrap(); + let hysteresis = turbo_states.get_for_power_state(is_on_ac); + hysteresis.update_state(enable_turbo); } // Apply the turbo setting From c570a327abef636774016fb21694c125d02247f0 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 03:15:34 +0300 Subject: [PATCH 17/31] engine: fix race condition --- src/engine.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 38c4400..f36d7c3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -68,13 +68,26 @@ impl TurboHysteresis { } /// Initialize the state with a specific value if not already initialized + /// Uses atomic `compare_exchange` to ensure only one thread can initialize the state fn initialize_with(&self, initial_state: bool) -> bool { - if !self.initialized.load(Ordering::Acquire) { - self.previous_state.store(initial_state, Ordering::Release); - self.initialized.store(true, Ordering::Release); - return initial_state; + // Try to atomically change initialized from false to true + // This ensures only one thread can win the initialization race + match self.initialized.compare_exchange( + false, // expected: not initialized + true, // desired: mark as initialized + Ordering::AcqRel, // success ordering: acquire+release for memory visibility + Ordering::Acquire, // failure ordering: just need to acquire the current value + ) { + Ok(_) => { + // We won the race to initialize - set the initial state + self.previous_state.store(initial_state, Ordering::Release); + initial_state + } + Err(_) => { + // Another thread already initialized it - read the current state + self.previous_state.load(Ordering::Acquire) + } } - self.previous_state.load(Ordering::Acquire) } /// Update the turbo state for hysteresis From 72c2842227c4889d4eb1886d560d9d1f044bfdf4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 03:19:09 +0300 Subject: [PATCH 18/31] engine: maintain previous state when CPU data is missing --- src/engine.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index f36d7c3..ac1d7ca 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -369,15 +369,25 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() } // In indeterminate states or unknown previous state, use the configured initial state _ => { - info!( - "Auto Turbo: Using configured initial state ({})", - if turbo_settings.initial_turbo_state { - "enabled" - } else { - "disabled" - } - ); - turbo_settings.initial_turbo_state + // If we have a previous state, maintain it for hysteresis even if load data is missing + if let Some(prev_state) = previous_turbo_enabled { + info!( + "Auto Turbo: Maintaining previous state ({}) due to missing CPU data", + if prev_state { "enabled" } else { "disabled" } + ); + prev_state + } else { + // No previous state exists, fall back to configured initial state + info!( + "Auto Turbo: Using configured initial state ({})", + if turbo_settings.initial_turbo_state { + "enabled" + } else { + "disabled" + } + ); + turbo_settings.initial_turbo_state + } } }; From d671a9c73ff21e15d7e6ad2b1215696bb109f147 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 03:24:25 +0300 Subject: [PATCH 19/31] engine: deduplicate `on_ac` detection logic --- src/engine.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index ac1d7ca..d4c7ab1 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -206,7 +206,15 @@ pub fn determine_and_apply_settings( TurboSetting::Auto => { if selected_profile_config.enable_auto_turbo { debug!("Managing turbo in auto mode based on system conditions"); - manage_auto_turbo(report, selected_profile_config)?; + // Determine AC status and pass it to manage_auto_turbo + let on_ac_power = if report.batteries.is_empty() { + // No batteries means desktop/server, always on AC + true + } else { + // Check if any battery reports AC connected + report.batteries.iter().any(|b| b.ac_connected) + }; + manage_auto_turbo(report, selected_profile_config, on_ac_power)?; } else { debug!( "Superfreq's dynamic turbo management is disabled by configuration. Ensuring system uses its default behavior for automatic turbo control." @@ -275,7 +283,11 @@ pub fn determine_and_apply_settings( Ok(()) } -fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<(), EngineError> { +fn manage_auto_turbo( + report: &SystemReport, + config: &ProfileConfig, + on_ac_power: bool, +) -> Result<(), EngineError> { // Get the auto turbo settings from the config, or use defaults let turbo_settings = config.turbo_auto_settings.clone().unwrap_or_default(); @@ -307,20 +319,10 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() } }; - // Use the appropriate hysteresis object based on whether we're on battery or AC power - // For systems without batteries (desktops), we should always use the AC power hysteresis - let is_on_ac = if report.batteries.is_empty() { - // No batteries means desktop/server, always on AC - true - } else { - // Check if any battery reports AC connected - report.batteries.iter().any(|b| b.ac_connected) - }; - // Get the previous state or initialize with the configured initial state let previous_turbo_enabled = { let turbo_states = get_turbo_states().lock().unwrap(); - let hysteresis = turbo_states.get_for_power_state(is_on_ac); + let hysteresis = turbo_states.get_for_power_state(on_ac_power); if let Some(state) = hysteresis.get_previous_state() { Some(state) } else { @@ -394,7 +396,7 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() // Save the current state for next time { let turbo_states = get_turbo_states().lock().unwrap(); - let hysteresis = turbo_states.get_for_power_state(is_on_ac); + let hysteresis = turbo_states.get_for_power_state(on_ac_power); hysteresis.update_state(enable_turbo); } From 044d6f2fa95117199e06a71a62dbc8566b9e1475 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 03:33:11 +0300 Subject: [PATCH 20/31] engine: get rid of unnecessary mutex wrapping AtomicBooll in TurboHystersis already provides thread safety --- src/engine.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index d4c7ab1..9dcd183 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -4,8 +4,8 @@ use crate::core::{OperationalMode, SystemReport, TurboSetting}; use crate::cpu::{self}; use crate::util::error::{ControlError, EngineError}; use log::{debug, info, warn}; +use std::sync::OnceLock; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Mutex, OnceLock}; /// A struct to track turbo boost state for AC and battery power modes struct TurboHysteresisStates { @@ -32,12 +32,13 @@ impl TurboHysteresisStates { } } -/// Create a global instance of `TurboHysteresisStates` protected by a mutex -static TURBO_STATES: OnceLock> = OnceLock::new(); +/// Create a global instance of `TurboHysteresisStates` without a mutex +/// since `AtomicBool` already provides thread safety +static TURBO_STATES: OnceLock = OnceLock::new(); /// Get or initialize the global turbo states -fn get_turbo_states() -> &'static Mutex { - TURBO_STATES.get_or_init(|| Mutex::new(TurboHysteresisStates::new())) +fn get_turbo_states() -> &'static TurboHysteresisStates { + TURBO_STATES.get_or_init(TurboHysteresisStates::new) } /// Manage turbo boost hysteresis state. @@ -321,7 +322,7 @@ fn manage_auto_turbo( // Get the previous state or initialize with the configured initial state let previous_turbo_enabled = { - let turbo_states = get_turbo_states().lock().unwrap(); + let turbo_states = get_turbo_states(); let hysteresis = turbo_states.get_for_power_state(on_ac_power); if let Some(state) = hysteresis.get_previous_state() { Some(state) @@ -395,7 +396,7 @@ fn manage_auto_turbo( // Save the current state for next time { - let turbo_states = get_turbo_states().lock().unwrap(); + let turbo_states = get_turbo_states(); let hysteresis = turbo_states.get_for_power_state(on_ac_power); hysteresis.update_state(enable_turbo); } From 1655886af0635a33060de2591975a9c65ed576e3 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 03:44:26 +0300 Subject: [PATCH 21/31] engine: document data race in `initialize_with` --- src/engine.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine.rs b/src/engine.rs index 9dcd183..32bdd0c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -81,6 +81,7 @@ impl TurboHysteresis { ) { Ok(_) => { // We won the race to initialize - set the initial state + // Store the initial_state first, before marking as initialized self.previous_state.store(initial_state, Ordering::Release); initial_state } From 8a92c124b115a5432f99ec0703dd41a75d8b8eff Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 03:52:21 +0300 Subject: [PATCH 22/31] engine: simplify default handling --- src/config/types.rs | 6 +++--- src/engine.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config/types.rs b/src/config/types.rs index 5d7e59e..fa20c36 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -50,7 +50,7 @@ pub struct ProfileConfig { pub min_freq_mhz: Option, pub max_freq_mhz: Option, pub platform_profile: Option, - pub turbo_auto_settings: Option, + pub turbo_auto_settings: TurboAutoSettings, pub enable_auto_turbo: bool, #[serde(skip_serializing_if = "Option::is_none")] pub battery_charge_thresholds: Option, @@ -66,7 +66,7 @@ impl Default for ProfileConfig { min_freq_mhz: None, // no override max_freq_mhz: None, // no override platform_profile: None, // no override - turbo_auto_settings: Some(TurboAutoSettings::default()), + turbo_auto_settings: TurboAutoSettings::default(), enable_auto_turbo: default_enable_auto_turbo(), battery_charge_thresholds: None, } @@ -224,7 +224,7 @@ impl From for ProfileConfig { platform_profile: toml_config.platform_profile, turbo_auto_settings: toml_config .turbo_auto_settings - .or_else(|| Some(TurboAutoSettings::default())), + .unwrap_or_default(), enable_auto_turbo: toml_config.enable_auto_turbo, battery_charge_thresholds: toml_config.battery_charge_thresholds, } diff --git a/src/engine.rs b/src/engine.rs index 32bdd0c..f5ba279 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -290,8 +290,8 @@ fn manage_auto_turbo( config: &ProfileConfig, on_ac_power: bool, ) -> 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 the auto turbo settings from the config + let turbo_settings = config.turbo_auto_settings.clone(); // Validate the complete configuration to ensure it's usable validate_turbo_auto_settings(&turbo_settings)?; From 5f1d2c9748daeb9149987595402b44a23f7f83bf Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 03:58:19 +0300 Subject: [PATCH 23/31] engine: simplify AC power status determination --- src/config/types.rs | 4 +--- src/engine.rs | 17 +++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/config/types.rs b/src/config/types.rs index fa20c36..d0d3579 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -222,9 +222,7 @@ impl From for ProfileConfig { 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: toml_config - .turbo_auto_settings - .unwrap_or_default(), + turbo_auto_settings: toml_config.turbo_auto_settings.unwrap_or_default(), enable_auto_turbo: toml_config.enable_auto_turbo, battery_charge_thresholds: toml_config.battery_charge_thresholds, } diff --git a/src/engine.rs b/src/engine.rs index f5ba279..8a4b592 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -202,20 +202,21 @@ pub fn determine_and_apply_settings( } } + // Determine AC/Battery status once for the entire function + let on_ac_power = if report.batteries.is_empty() { + // No batteries means desktop/server, always on AC + true + } else { + // Check if any battery reports AC connected + report.batteries.iter().any(|b| b.ac_connected) + }; + if let Some(turbo_setting) = selected_profile_config.turbo { info!("Setting turbo to '{turbo_setting:?}'"); match turbo_setting { TurboSetting::Auto => { if selected_profile_config.enable_auto_turbo { debug!("Managing turbo in auto mode based on system conditions"); - // Determine AC status and pass it to manage_auto_turbo - let on_ac_power = if report.batteries.is_empty() { - // No batteries means desktop/server, always on AC - true - } else { - // Check if any battery reports AC connected - report.batteries.iter().any(|b| b.ac_connected) - }; manage_auto_turbo(report, selected_profile_config, on_ac_power)?; } else { debug!( From 535a045e8b4e3a3adb8ffdbc36309bb5fbf5477c Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 04:09:05 +0300 Subject: [PATCH 24/31] engine: streamline turbo state management --- src/engine.rs | 83 ++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 8a4b592..56ee1da 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -150,6 +150,17 @@ pub fn determine_and_apply_settings( })?; } + // Determine AC/Battery status once, early in the function + // For desktops (no batteries), we should always use the AC power profile + // For laptops, we check if any battery is present and not connected to AC + let on_ac_power = if report.batteries.is_empty() { + // No batteries means desktop/server, always on AC + true + } else { + // Check if any battery reports AC connected + report.batteries.iter().any(|b| b.ac_connected) + }; + let selected_profile_config: &ProfileConfig; if let Some(mode) = force_mode { @@ -164,17 +175,7 @@ pub fn determine_and_apply_settings( } } } else { - // Determine AC/Battery status - // For desktops (no batteries), we should always use the AC power profile - // For laptops, we check if any battery is present and not connected to AC - let on_ac_power = if report.batteries.is_empty() { - // No batteries means desktop/server, always on AC - true - } else { - // Check if any battery reports AC connected - report.batteries.iter().any(|b| b.ac_connected) - }; - + // Use the previously computed on_ac_power value if on_ac_power { info!("On AC power, selecting Charger profile."); selected_profile_config = &config.charger; @@ -202,15 +203,6 @@ pub fn determine_and_apply_settings( } } - // Determine AC/Battery status once for the entire function - let on_ac_power = if report.batteries.is_empty() { - // No batteries means desktop/server, always on AC - true - } else { - // Check if any battery reports AC connected - report.batteries.iter().any(|b| b.ac_connected) - }; - if let Some(turbo_setting) = selected_profile_config.turbo { info!("Setting turbo to '{turbo_setting:?}'"); match turbo_setting { @@ -327,10 +319,10 @@ fn manage_auto_turbo( let turbo_states = get_turbo_states(); let hysteresis = turbo_states.get_for_power_state(on_ac_power); if let Some(state) = hysteresis.get_previous_state() { - Some(state) + state } else { // Initialize with the configured initial state and return it - Some(hysteresis.initialize_with(turbo_settings.initial_turbo_state)) + hysteresis.initialize_with(turbo_settings.initial_turbo_state) } }; @@ -361,7 +353,7 @@ fn manage_auto_turbo( false } // In intermediate load range, maintain previous state (hysteresis) - (_, Some(usage), Some(prev_state)) + (_, Some(usage), prev_state) if usage > turbo_settings.load_threshold_low && usage < turbo_settings.load_threshold_high => { @@ -372,27 +364,30 @@ fn manage_auto_turbo( ); prev_state } - // In indeterminate states or unknown previous state, use the configured initial state - _ => { - // If we have a previous state, maintain it for hysteresis even if load data is missing - if let Some(prev_state) = previous_turbo_enabled { - info!( - "Auto Turbo: Maintaining previous state ({}) due to missing CPU data", - if prev_state { "enabled" } else { "disabled" } - ); - prev_state - } else { - // No previous state exists, fall back to configured initial state - info!( - "Auto Turbo: Using configured initial state ({})", - if turbo_settings.initial_turbo_state { - "enabled" - } else { - "disabled" - } - ); - turbo_settings.initial_turbo_state - } + // When CPU load data is present but temperature is missing, use the same hysteresis logic + (None, Some(usage), prev_state) => { + info!( + "Auto Turbo: Maintaining previous state ({}) due to missing temperature data (load: {:.1}%)", + if prev_state { "enabled" } else { "disabled" }, + usage + ); + prev_state + } + // When all metrics are missing, maintain the previous state + (None, None, prev_state) => { + info!( + "Auto Turbo: Maintaining previous state ({}) due to missing all CPU metrics", + if prev_state { "enabled" } else { "disabled" } + ); + prev_state + } + // Any other cases with partial metrics, maintain previous state for stability + (_, _, prev_state) => { + info!( + "Auto Turbo: Maintaining previous state ({}) due to incomplete CPU metrics", + if prev_state { "enabled" } else { "disabled" } + ); + prev_state } }; From 899d24d5c61777448118041408238d0ad8d317b1 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 04:24:14 +0300 Subject: [PATCH 25/31] engine: optimize turbo state update logic --- src/engine.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/engine.rs b/src/engine.rs index 56ee1da..ce644d2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -94,8 +94,24 @@ impl TurboHysteresis { /// Update the turbo state for hysteresis fn update_state(&self, new_state: bool) { +q // First store the new state, then mark as initialized + // With this, any thread seeing initialized=true will also see the correct state self.previous_state.store(new_state, Ordering::Release); - self.initialized.store(true, Ordering::Release); + + // Already initialized, no need for compare_exchange + if self.initialized.load(Ordering::Relaxed) { + return; + } + + // Otherwise, try to set initialized=true (but only if it was false) + self.initialized + .compare_exchange( + false, // expected: not initialized + true, // desired: mark as initialized + Ordering::Release, // success ordering: release for memory visibility + Ordering::Relaxed, // failure ordering: we don't care about the current value on failure + ) + .ok(); // Ignore the result - if it fails, it means another thread already initialized it } } From 4e7b2d405baeb2f5d3bdbcf4c97d36c21a1926ba Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 04:28:49 +0300 Subject: [PATCH 26/31] config: document `default_initial_turbo_state` semantics --- src/config/types.rs | 3 +++ src/engine.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/types.rs b/src/config/types.rs index d0d3579..c5995df 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -171,6 +171,9 @@ pub struct TurboAutoSettings { pub load_threshold_low: f32, #[serde(default = "default_temp_threshold_high")] pub temp_threshold_high: f32, + /// Initial turbo boost state when no previous state exists. + /// Set to `true` to start with turbo enabled, `false` to start with turbo disabled. + /// This is only used at first launch or after a reset. #[serde(default = "default_initial_turbo_state")] pub initial_turbo_state: bool, } diff --git a/src/engine.rs b/src/engine.rs index ce644d2..5aa7bc6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -94,7 +94,7 @@ impl TurboHysteresis { /// Update the turbo state for hysteresis fn update_state(&self, new_state: bool) { -q // First store the new state, then mark as initialized + // First store the new state, then mark as initialized // With this, any thread seeing initialized=true will also see the correct state self.previous_state.store(new_state, Ordering::Release); From 15cdf225571a43d820f87ce6d33f21f71d3a5a38 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 04:35:12 +0300 Subject: [PATCH 27/31] engine: stronger validation for turbo auto settings thresholds --- src/cpu.rs | 40 ++++++++++------------------------------ src/engine.rs | 10 +++++++--- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index b5b3704..cbd37f8 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,6 +1,7 @@ use crate::core::{GovernorOverrideMode, TurboSetting}; use crate::util::error::ControlError; use core::str; +use log::debug; use std::{fs, io, path::Path, string::ToString}; pub type Result = std::result::Result; @@ -212,44 +213,23 @@ fn get_available_governors() -> Result> { )) } -/// Controls CPU turbo boost behavior by writing to appropriate sysfs files. -/// -/// # Parameters -/// -/// * `setting` - The desired turbo boost setting: -/// - `TurboSetting::Always`: Forces turbo boost to be always enabled -/// - `TurboSetting::Never`: Forces turbo boost to be always disabled -/// - `TurboSetting::Auto`: Has two distinct behaviors depending on the context: -/// 1. When called directly from user commands: Resets turbo to system default (enabled) -/// 2. When used with `enable_auto_turbo=true` in config: Managed dynamically by the engine -/// -/// # Turbo Auto Mode Explained -/// -/// When `TurboSetting::Auto` is used: -/// - This function writes the same values as `TurboSetting::Always` to reset the hardware -/// to its default state (turbo enabled) -/// - The actual dynamic management happens at a higher level in the engine module -/// when `enable_auto_turbo=true` -/// - With `enable_auto_turbo=false`, the system's native turbo management takes over -/// -/// -/// # Returns -/// -/// * `Result<()>` - Success or an error if the operation failed pub fn set_turbo(setting: TurboSetting) -> Result<()> { let value_pstate = match setting { TurboSetting::Always => "0", // no_turbo = 0 means turbo is enabled TurboSetting::Never => "1", // no_turbo = 1 means turbo is disabled - // For Auto, we need to enable the hardware default (which is turbo enabled) - // and we reset to the system default when explicitly set to Auto - TurboSetting::Auto => "0", // set to enabled (default hardware state) when Auto is requested + // Auto mode is handled at the engine level, not directly at the sysfs level + TurboSetting::Auto => { + debug!("Turbo Auto mode is managed by engine logic based on system conditions"); + return Ok(()); + } }; let value_boost = match setting { TurboSetting::Always => "1", // boost = 1 means turbo is enabled TurboSetting::Never => "0", // boost = 0 means turbo is disabled - // For Auto, we need to enable the hardware default (which is turbo enabled) - // and we reset to the system default when explicitly set to Auto - TurboSetting::Auto => "1", // set to enabled (default hardware state) when Auto is requested + TurboSetting::Auto => { + debug!("Turbo Auto mode is managed by engine logic based on system conditions"); + return Ok(()); + } }; // AMD specific paths diff --git a/src/engine.rs b/src/engine.rs index 5aa7bc6..988e406 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -434,10 +434,14 @@ fn manage_auto_turbo( } fn validate_turbo_auto_settings(settings: &TurboAutoSettings) -> Result<(), EngineError> { - // Validate load thresholds - if settings.load_threshold_high <= settings.load_threshold_low { + // Validate load thresholds (0-100 % and high > low) + if settings.load_threshold_high <= settings.load_threshold_low + || settings.load_threshold_high > 100.0 + || settings.load_threshold_low < 0.0 + || settings.load_threshold_low > 100.0 + { return Err(EngineError::ConfigurationError( - "Invalid turbo auto settings: high threshold must be greater than low threshold" + "Invalid turbo auto settings: load thresholds must be in 0-100% and high > low" .to_string(), )); } From eb9fbdc681f2d4687bb53dd8f9686c257e6afbce Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 04:48:10 +0300 Subject: [PATCH 28/31] engine: fix potential data race when previous state is outdated --- src/engine.rs | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 988e406..2f0a5b3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -7,7 +7,7 @@ use log::{debug, info, warn}; use std::sync::OnceLock; use std::sync::atomic::{AtomicBool, Ordering}; -/// A struct to track turbo boost state for AC and battery power modes +/// Track turbo boost state for AC and battery power modes struct TurboHysteresisStates { /// State for when on AC power charger: TurboHysteresis, @@ -32,8 +32,6 @@ impl TurboHysteresisStates { } } -/// Create a global instance of `TurboHysteresisStates` without a mutex -/// since `AtomicBool` already provides thread safety static TURBO_STATES: OnceLock = OnceLock::new(); /// Get or initialize the global turbo states @@ -69,24 +67,26 @@ impl TurboHysteresis { } /// Initialize the state with a specific value if not already initialized - /// Uses atomic `compare_exchange` to ensure only one thread can initialize the state + /// Only one thread should be able to initialize the state fn initialize_with(&self, initial_state: bool) -> bool { + // First store the initial state so that it's visible before initialized=true + self.previous_state.store(initial_state, Ordering::Release); + // Try to atomically change initialized from false to true - // This ensures only one thread can win the initialization race + // Now, only one thread can win the initialization race match self.initialized.compare_exchange( false, // expected: not initialized true, // desired: mark as initialized - Ordering::AcqRel, // success ordering: acquire+release for memory visibility - Ordering::Acquire, // failure ordering: just need to acquire the current value + Ordering::Release, // success: release for memory visibility + Ordering::Acquire, // failure: just need to acquire the current value ) { Ok(_) => { - // We won the race to initialize - set the initial state - // Store the initial_state first, before marking as initialized - self.previous_state.store(initial_state, Ordering::Release); + // We won the race to initialize initial_state } Err(_) => { - // Another thread already initialized it - read the current state + // Another thread already initialized it. + // Read the current state in bitter defeat self.previous_state.load(Ordering::Acquire) } } @@ -108,10 +108,10 @@ impl TurboHysteresis { .compare_exchange( false, // expected: not initialized true, // desired: mark as initialized - Ordering::Release, // success ordering: release for memory visibility - Ordering::Relaxed, // failure ordering: we don't care about the current value on failure + Ordering::Release, // success: release for memory visibility + Ordering::Relaxed, // failure: we don't care about the current value on failure ) - .ok(); // Ignore the result - if it fails, it means another thread already initialized it + .ok(); // Ignore the result. If it fails, it means another thread already initialized it } } @@ -147,7 +147,7 @@ where } /// Determines the appropriate CPU profile based on power status or forced mode, -/// and applies the settings using functions from the `cpu` module. +/// and applies the settings (via helpers defined in the `cpu` module) pub fn determine_and_apply_settings( report: &SystemReport, config: &AppConfig, @@ -352,6 +352,7 @@ fn manage_auto_turbo( ); false } + // If load is high enough, enable turbo (unless temp already caused it to disable) (_, Some(usage), _) if usage >= turbo_settings.load_threshold_high => { info!( @@ -360,6 +361,7 @@ fn manage_auto_turbo( ); true } + // If load is low, disable turbo (_, Some(usage), _) if usage <= turbo_settings.load_threshold_low => { info!( @@ -368,6 +370,7 @@ fn manage_auto_turbo( ); false } + // In intermediate load range, maintain previous state (hysteresis) (_, Some(usage), prev_state) if usage > turbo_settings.load_threshold_low @@ -380,6 +383,7 @@ fn manage_auto_turbo( ); prev_state } + // When CPU load data is present but temperature is missing, use the same hysteresis logic (None, Some(usage), prev_state) => { info!( @@ -389,6 +393,7 @@ fn manage_auto_turbo( ); prev_state } + // When all metrics are missing, maintain the previous state (None, None, prev_state) => { info!( @@ -397,6 +402,7 @@ fn manage_auto_turbo( ); prev_state } + // Any other cases with partial metrics, maintain previous state for stability (_, _, prev_state) => { info!( @@ -454,6 +460,9 @@ fn validate_turbo_auto_settings(settings: &TurboAutoSettings) -> Result<(), Engi } // Validate temperature threshold (realistic range for CPU temps in Celsius) + // TODO: different CPUs have different temperature thresholds. While 110 is a good example + // "extreme" case, the upper barrier might be *lower* for some devices. We'll want to fix + // this eventually, or make it configurable. if settings.temp_threshold_high <= 0.0 || settings.temp_threshold_high > 110.0 { return Err(EngineError::ConfigurationError( "Invalid turbo auto settings: temperature threshold must be between 0°C and 110°C" From f1a5ad0b6c6c84a46f868764610a3cd35980d148 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 04:54:48 +0300 Subject: [PATCH 29/31] engine: improve AC state detection I hate the number of possible hardware combinations... --- src/engine.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 2f0a5b3..7e3aabc 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -168,13 +168,13 @@ pub fn determine_and_apply_settings( // Determine AC/Battery status once, early in the function // For desktops (no batteries), we should always use the AC power profile - // For laptops, we check if any battery is present and not connected to AC + // For laptops, we check if all batteries report connected to AC let on_ac_power = if report.batteries.is_empty() { // No batteries means desktop/server, always on AC true } else { - // Check if any battery reports AC connected - report.batteries.iter().any(|b| b.ac_connected) + // Check if all batteries report AC connected + report.batteries.iter().all(|b| b.ac_connected) }; let selected_profile_config: &ProfileConfig; From d7d6398041e99e0c2789b26dfbcff07bfff97ce9 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 05:03:34 +0300 Subject: [PATCH 30/31] engine: optimize turbo auto settings retrieval; improve validation errors --- src/engine.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 7e3aabc..7438dbb 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -300,10 +300,10 @@ fn manage_auto_turbo( on_ac_power: bool, ) -> Result<(), EngineError> { // Get the auto turbo settings from the config - let turbo_settings = config.turbo_auto_settings.clone(); + let turbo_settings = &config.turbo_auto_settings; // Validate the complete configuration to ensure it's usable - validate_turbo_auto_settings(&turbo_settings)?; + validate_turbo_auto_settings(turbo_settings)?; // Get average CPU temperature and CPU load let cpu_temp = report.cpu_global.average_temperature_celsius; @@ -440,25 +440,17 @@ fn manage_auto_turbo( } fn validate_turbo_auto_settings(settings: &TurboAutoSettings) -> Result<(), EngineError> { - // Validate load thresholds (0-100 % and high > low) if settings.load_threshold_high <= settings.load_threshold_low || settings.load_threshold_high > 100.0 || settings.load_threshold_low < 0.0 || settings.load_threshold_low > 100.0 { return Err(EngineError::ConfigurationError( - "Invalid turbo auto settings: load thresholds must be in 0-100% and high > low" + "Invalid turbo auto settings: load thresholds must be between 0 % and 100 % with high > low" .to_string(), )); } - // Validate range of load thresholds (should be 0-100%) - if settings.load_threshold_high > 100.0 || settings.load_threshold_low < 0.0 { - return Err(EngineError::ConfigurationError( - "Invalid turbo auto settings: load thresholds must be between 0% and 100%".to_string(), - )); - } - // Validate temperature threshold (realistic range for CPU temps in Celsius) // TODO: different CPUs have different temperature thresholds. While 110 is a good example // "extreme" case, the upper barrier might be *lower* for some devices. We'll want to fix From d687c037cb7e0727cf9a432829e6d18fa4196d21 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 18 May 2025 05:07:10 +0300 Subject: [PATCH 31/31] config: add serde(default) to turbo settings for backward compat --- src/config/types.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config/types.rs b/src/config/types.rs index c5995df..63dff9a 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -50,7 +50,9 @@ pub struct ProfileConfig { pub min_freq_mhz: Option, pub max_freq_mhz: Option, pub platform_profile: Option, + #[serde(default)] pub turbo_auto_settings: TurboAutoSettings, + #[serde(default)] pub enable_auto_turbo: bool, #[serde(skip_serializing_if = "Option::is_none")] pub battery_charge_thresholds: Option,