mirror of
https://github.com/RGBCube/watt
synced 2025-07-30 09:27:45 +00:00
Merge pull request #26 from NotAShelf/auto-turbo
feature: dynamic CPU turbo management
This commit is contained in:
commit
0b8551d34f
6 changed files with 335 additions and 59 deletions
|
@ -1,7 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "superfreq"
|
name = "superfreq"
|
||||||
|
description = "Modern CPU frequency and power management utility for Linux"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
authors = ["NotAShelf <raf@notashelf.dev>"]
|
||||||
|
rust-version = "1.85"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
78
README.md
78
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
|
systems. It provides intelligent control of CPU governors, frequencies, and
|
||||||
power-saving features, helping optimize both performance and battery life.
|
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
|
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.
|
but most common usecases are already implemented.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
@ -31,6 +31,8 @@ but most common usecases are already implemented.
|
||||||
and turbo boost
|
and turbo boost
|
||||||
- **Intelligent Power Management**: Different profiles for AC and battery
|
- **Intelligent Power Management**: Different profiles for AC and battery
|
||||||
operation
|
operation
|
||||||
|
- **Dynamic Turbo Boost Control**: Automatically enables/disables turbo based on
|
||||||
|
CPU load and temperature
|
||||||
- **Fine-tuned Controls**: Adjust energy performance preferences, biases, and
|
- **Fine-tuned Controls**: Adjust energy performance preferences, biases, and
|
||||||
frequency limits
|
frequency limits
|
||||||
- **Per-core Control**: Apply settings globally or to specific CPU cores
|
- **Per-core Control**: Apply settings globally or to specific CPU cores
|
||||||
|
@ -150,6 +152,15 @@ variable.
|
||||||
governor = "performance"
|
governor = "performance"
|
||||||
# Turbo boost setting: "always", "auto", or "never"
|
# Turbo boost setting: "always", "auto", or "never"
|
||||||
turbo = "auto"
|
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,
|
||||||
|
initial_turbo_state = false, # whether turbo should be initially enabled (false = disabled)
|
||||||
|
}
|
||||||
# Energy Performance Preference
|
# Energy Performance Preference
|
||||||
epp = "performance"
|
epp = "performance"
|
||||||
# Energy Performance Bias (0-15 scale or named value)
|
# Energy Performance Bias (0-15 scale or named value)
|
||||||
|
@ -166,6 +177,14 @@ max_freq_mhz = 3500
|
||||||
[battery]
|
[battery]
|
||||||
governor = "powersave"
|
governor = "powersave"
|
||||||
turbo = "auto"
|
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,
|
||||||
|
initial_turbo_state = false, # start with turbo disabled on battery for power savings
|
||||||
|
}
|
||||||
epp = "power"
|
epp = "power"
|
||||||
epb = "balance_power"
|
epb = "balance_power"
|
||||||
platform_profile = "low-power"
|
platform_profile = "low-power"
|
||||||
|
@ -209,6 +228,45 @@ 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
|
inclined to use than others. If you have a use-case that is not covered, please
|
||||||
create an issue.
|
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
|
||||||
|
- **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
|
||||||
|
|
||||||
|
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"`.
|
||||||
|
|
||||||
|
#### 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**<br>Turbo is always active regardless of CPU load or temperature | **Always enabled**<br>Turbo is always active regardless of CPU load or temperature |
|
||||||
|
| `turbo = "never"` | **Always disabled**<br>Turbo is always disabled regardless of CPU load or temperature | **Always disabled**<br>Turbo is always disabled regardless of CPU load or temperature |
|
||||||
|
| `turbo = "auto"` | **Dynamically managed**<br>Superfreq enables/disables turbo based on CPU load and temperature thresholds | **System default**<br>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
|
### Adaptive Polling
|
||||||
|
|
||||||
Superfreq includes a "sophisticated" (euphemism for complicated) adaptive
|
Superfreq includes a "sophisticated" (euphemism for complicated) adaptive
|
||||||
|
@ -268,14 +326,16 @@ While reporting issues, please attach the results from `superfreq debug`.
|
||||||
Contributions to Superfreq are always welcome! Whether it's bug reports, feature
|
Contributions to Superfreq are always welcome! Whether it's bug reports, feature
|
||||||
requests, or code contributions, please feel free to contribute.
|
requests, or code contributions, please feel free to contribute.
|
||||||
|
|
||||||
If you are looking to reimplement features from auto_cpufreq, please consider
|
> [!NOTE]
|
||||||
opening an issue first and let us know what you have in mind. Certain features
|
> If you are looking to reimplement features from auto-cpufreq, please consider
|
||||||
(such as the system tray) are deliberately ignored, and might not be desired in
|
> opening an issue first and let us know what you have in mind. Certain features
|
||||||
the codebase as they stand.
|
> (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
|
### 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.85 or later is
|
||||||
|
required.
|
||||||
|
|
||||||
A `.envrc` is provided, and it's usage is encouraged for Nix users.
|
A `.envrc` is provided, and it's usage is encouraged for Nix users.
|
||||||
Alternatively, you may use Nix for a reproducible developer environment
|
Alternatively, you may use Nix for a reproducible developer environment
|
||||||
|
@ -285,9 +345,9 @@ nix develop
|
||||||
```
|
```
|
||||||
|
|
||||||
Non-Nix users may get the appropriate Cargo and Rust versions from their package
|
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
|
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.
|
sure all of your code is properly formatted. For Nix code, please use Alejandra.
|
||||||
|
|
|
@ -50,7 +50,10 @@ pub struct ProfileConfig {
|
||||||
pub min_freq_mhz: Option<u32>,
|
pub min_freq_mhz: Option<u32>,
|
||||||
pub max_freq_mhz: Option<u32>,
|
pub max_freq_mhz: Option<u32>,
|
||||||
pub platform_profile: Option<String>,
|
pub platform_profile: Option<String>,
|
||||||
pub turbo_auto_settings: Option<TurboAutoSettings>,
|
#[serde(default)]
|
||||||
|
pub turbo_auto_settings: TurboAutoSettings,
|
||||||
|
#[serde(default)]
|
||||||
|
pub enable_auto_turbo: bool,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
|
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
|
||||||
}
|
}
|
||||||
|
@ -65,7 +68,8 @@ impl Default for ProfileConfig {
|
||||||
min_freq_mhz: None, // no override
|
min_freq_mhz: None, // no override
|
||||||
max_freq_mhz: None, // no override
|
max_freq_mhz: None, // no override
|
||||||
platform_profile: 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,
|
battery_charge_thresholds: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +128,9 @@ pub struct ProfileConfigToml {
|
||||||
pub min_freq_mhz: Option<u32>,
|
pub min_freq_mhz: Option<u32>,
|
||||||
pub max_freq_mhz: Option<u32>,
|
pub max_freq_mhz: Option<u32>,
|
||||||
pub platform_profile: Option<String>,
|
pub platform_profile: Option<String>,
|
||||||
|
pub turbo_auto_settings: Option<TurboAutoSettings>,
|
||||||
|
#[serde(default = "default_enable_auto_turbo")]
|
||||||
|
pub enable_auto_turbo: bool,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
|
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
|
||||||
}
|
}
|
||||||
|
@ -151,6 +158,8 @@ impl Default for ProfileConfigToml {
|
||||||
min_freq_mhz: None,
|
min_freq_mhz: None,
|
||||||
max_freq_mhz: None,
|
max_freq_mhz: None,
|
||||||
platform_profile: None,
|
platform_profile: None,
|
||||||
|
turbo_auto_settings: None,
|
||||||
|
enable_auto_turbo: default_enable_auto_turbo(),
|
||||||
battery_charge_thresholds: None,
|
battery_charge_thresholds: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,12 +173,18 @@ pub struct TurboAutoSettings {
|
||||||
pub load_threshold_low: f32,
|
pub load_threshold_low: f32,
|
||||||
#[serde(default = "default_temp_threshold_high")]
|
#[serde(default = "default_temp_threshold_high")]
|
||||||
pub temp_threshold_high: f32,
|
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default thresholds for Auto turbo mode
|
// 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_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_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_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 {
|
const fn default_load_threshold_high() -> f32 {
|
||||||
DEFAULT_LOAD_THRESHOLD_HIGH
|
DEFAULT_LOAD_THRESHOLD_HIGH
|
||||||
|
@ -180,6 +195,9 @@ const fn default_load_threshold_low() -> f32 {
|
||||||
const fn default_temp_threshold_high() -> f32 {
|
const fn default_temp_threshold_high() -> f32 {
|
||||||
DEFAULT_TEMP_THRESHOLD_HIGH
|
DEFAULT_TEMP_THRESHOLD_HIGH
|
||||||
}
|
}
|
||||||
|
const fn default_initial_turbo_state() -> bool {
|
||||||
|
DEFAULT_INITIAL_TURBO_STATE
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for TurboAutoSettings {
|
impl Default for TurboAutoSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -187,6 +205,7 @@ impl Default for TurboAutoSettings {
|
||||||
load_threshold_high: DEFAULT_LOAD_THRESHOLD_HIGH,
|
load_threshold_high: DEFAULT_LOAD_THRESHOLD_HIGH,
|
||||||
load_threshold_low: DEFAULT_LOAD_THRESHOLD_LOW,
|
load_threshold_low: DEFAULT_LOAD_THRESHOLD_LOW,
|
||||||
temp_threshold_high: DEFAULT_TEMP_THRESHOLD_HIGH,
|
temp_threshold_high: DEFAULT_TEMP_THRESHOLD_HIGH,
|
||||||
|
initial_turbo_state: DEFAULT_INITIAL_TURBO_STATE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,7 +227,8 @@ impl From<ProfileConfigToml> for ProfileConfig {
|
||||||
min_freq_mhz: toml_config.min_freq_mhz,
|
min_freq_mhz: toml_config.min_freq_mhz,
|
||||||
max_freq_mhz: toml_config.max_freq_mhz,
|
max_freq_mhz: toml_config.max_freq_mhz,
|
||||||
platform_profile: toml_config.platform_profile,
|
platform_profile: toml_config.platform_profile,
|
||||||
turbo_auto_settings: Some(TurboAutoSettings::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,
|
battery_charge_thresholds: toml_config.battery_charge_thresholds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,6 +302,10 @@ const fn default_stats_file_path() -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn default_enable_auto_turbo() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
pub struct DaemonConfigToml {
|
pub struct DaemonConfigToml {
|
||||||
#[serde(default = "default_poll_interval_sec")]
|
#[serde(default = "default_poll_interval_sec")]
|
||||||
|
|
12
src/cpu.rs
12
src/cpu.rs
|
@ -1,6 +1,7 @@
|
||||||
use crate::core::{GovernorOverrideMode, TurboSetting};
|
use crate::core::{GovernorOverrideMode, TurboSetting};
|
||||||
use crate::util::error::ControlError;
|
use crate::util::error::ControlError;
|
||||||
use core::str;
|
use core::str;
|
||||||
|
use log::debug;
|
||||||
use std::{fs, io, path::Path, string::ToString};
|
use std::{fs, io, path::Path, string::ToString};
|
||||||
|
|
||||||
pub type Result<T, E = ControlError> = std::result::Result<T, E>;
|
pub type Result<T, E = ControlError> = std::result::Result<T, E>;
|
||||||
|
@ -216,12 +217,19 @@ pub fn set_turbo(setting: TurboSetting) -> Result<()> {
|
||||||
let value_pstate = match setting {
|
let value_pstate = match setting {
|
||||||
TurboSetting::Always => "0", // no_turbo = 0 means turbo is enabled
|
TurboSetting::Always => "0", // no_turbo = 0 means turbo is enabled
|
||||||
TurboSetting::Never => "1", // no_turbo = 1 means turbo is disabled
|
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 {
|
let value_boost = match setting {
|
||||||
TurboSetting::Always => "1", // boost = 1 means turbo is enabled
|
TurboSetting::Always => "1", // boost = 1 means turbo is enabled
|
||||||
TurboSetting::Never => "0", // boost = 0 means turbo is disabled
|
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
|
// AMD specific paths
|
||||||
|
|
257
src/engine.rs
257
src/engine.rs
|
@ -4,6 +4,116 @@ use crate::core::{OperationalMode, SystemReport, TurboSetting};
|
||||||
use crate::cpu::{self};
|
use crate::cpu::{self};
|
||||||
use crate::util::error::{ControlError, EngineError};
|
use crate::util::error::{ControlError, EngineError};
|
||||||
use log::{debug, info, warn};
|
use log::{debug, info, warn};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TURBO_STATES: OnceLock<TurboHysteresisStates> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Get or initialize the global turbo states
|
||||||
|
fn get_turbo_states() -> &'static TurboHysteresisStates {
|
||||||
|
TURBO_STATES.get_or_init(TurboHysteresisStates::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<bool> {
|
||||||
|
if self.initialized.load(Ordering::Acquire) {
|
||||||
|
Some(self.previous_state.load(Ordering::Acquire))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the state with a specific value if not already initialized
|
||||||
|
/// 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
|
||||||
|
// Now, only one thread can win the initialization race
|
||||||
|
match self.initialized.compare_exchange(
|
||||||
|
false, // expected: not initialized
|
||||||
|
true, // desired: mark as initialized
|
||||||
|
Ordering::Release, // success: release for memory visibility
|
||||||
|
Ordering::Acquire, // failure: just need to acquire the current value
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
// We won the race to initialize
|
||||||
|
initial_state
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
// Another thread already initialized it.
|
||||||
|
// Read the current state in bitter defeat
|
||||||
|
self.previous_state.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the turbo state for hysteresis
|
||||||
|
fn update_state(&self, new_state: bool) {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try applying a CPU feature and handle common error cases. Centralizes the where we
|
/// Try applying a CPU feature and handle common error cases. Centralizes the where we
|
||||||
/// previously did:
|
/// previously did:
|
||||||
|
@ -37,7 +147,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the appropriate CPU profile based on power status or forced mode,
|
/// 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(
|
pub fn determine_and_apply_settings(
|
||||||
report: &SystemReport,
|
report: &SystemReport,
|
||||||
config: &AppConfig,
|
config: &AppConfig,
|
||||||
|
@ -56,6 +166,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 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 all batteries report AC connected
|
||||||
|
report.batteries.iter().all(|b| b.ac_connected)
|
||||||
|
};
|
||||||
|
|
||||||
let selected_profile_config: &ProfileConfig;
|
let selected_profile_config: &ProfileConfig;
|
||||||
|
|
||||||
if let Some(mode) = force_mode {
|
if let Some(mode) = force_mode {
|
||||||
|
@ -70,17 +191,7 @@ pub fn determine_and_apply_settings(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Determine AC/Battery status
|
// Use the previously computed on_ac_power value
|
||||||
// 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)
|
|
||||||
};
|
|
||||||
|
|
||||||
if on_ac_power {
|
if on_ac_power {
|
||||||
info!("On AC power, selecting Charger profile.");
|
info!("On AC power, selecting Charger profile.");
|
||||||
selected_profile_config = &config.charger;
|
selected_profile_config = &config.charger;
|
||||||
|
@ -112,8 +223,19 @@ pub fn determine_and_apply_settings(
|
||||||
info!("Setting turbo to '{turbo_setting:?}'");
|
info!("Setting turbo to '{turbo_setting:?}'");
|
||||||
match turbo_setting {
|
match turbo_setting {
|
||||||
TurboSetting::Auto => {
|
TurboSetting::Auto => {
|
||||||
debug!("Managing turbo in auto mode based on system conditions");
|
if selected_profile_config.enable_auto_turbo {
|
||||||
manage_auto_turbo(report, selected_profile_config)?;
|
debug!("Managing turbo in auto mode based on system conditions");
|
||||||
|
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."
|
||||||
|
);
|
||||||
|
// 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(TurboSetting::Auto)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
try_apply_feature("Turbo boost", &format!("{turbo_setting:?}"), || {
|
try_apply_feature("Turbo boost", &format!("{turbo_setting:?}"), || {
|
||||||
|
@ -172,12 +294,16 @@ pub fn determine_and_apply_settings(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<(), EngineError> {
|
fn manage_auto_turbo(
|
||||||
// Get the auto turbo settings from the config, or use defaults
|
report: &SystemReport,
|
||||||
let turbo_settings = config.turbo_auto_settings.clone().unwrap_or_default();
|
config: &ProfileConfig,
|
||||||
|
on_ac_power: bool,
|
||||||
|
) -> Result<(), EngineError> {
|
||||||
|
// Get the auto turbo settings from the config
|
||||||
|
let turbo_settings = &config.turbo_auto_settings;
|
||||||
|
|
||||||
// Validate the complete configuration to ensure it's usable
|
// 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
|
// Get average CPU temperature and CPU load
|
||||||
let cpu_temp = report.cpu_global.average_temperature_celsius;
|
let cpu_temp = report.cpu_global.average_temperature_celsius;
|
||||||
|
@ -204,40 +330,96 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decision logic for enabling/disabling turbo
|
// Get the previous state or initialize with the configured initial state
|
||||||
let enable_turbo = match (cpu_temp, avg_cpu_usage) {
|
let previous_turbo_enabled = {
|
||||||
|
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() {
|
||||||
|
state
|
||||||
|
} else {
|
||||||
|
// Initialize with the configured initial state and return it
|
||||||
|
hysteresis.initialize_with(turbo_settings.initial_turbo_state)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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
|
// 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!(
|
info!(
|
||||||
"Auto Turbo: Disabled due to high temperature ({:.1}°C >= {:.1}°C)",
|
"Auto Turbo: Disabled due to high temperature ({:.1}°C >= {:.1}°C)",
|
||||||
temp, turbo_settings.temp_threshold_high
|
temp, turbo_settings.temp_threshold_high
|
||||||
);
|
);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If load is high enough, enable turbo (unless temp already caused it to disable)
|
// 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!(
|
info!(
|
||||||
"Auto Turbo: Enabled due to high CPU load ({:.1}% >= {:.1}%)",
|
"Auto Turbo: Enabled due to high CPU load ({:.1}% >= {:.1}%)",
|
||||||
usage, turbo_settings.load_threshold_high
|
usage, turbo_settings.load_threshold_high
|
||||||
);
|
);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// If load is low, disable turbo
|
// 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!(
|
info!(
|
||||||
"Auto Turbo: Disabled due to low CPU load ({:.1}% <= {:.1}%)",
|
"Auto Turbo: Disabled due to low CPU load ({:.1}% <= {:.1}%)",
|
||||||
usage, turbo_settings.load_threshold_low
|
usage, turbo_settings.load_threshold_low
|
||||||
);
|
);
|
||||||
false
|
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), prev_state)
|
||||||
info!("Auto Turbo: Disabled (default for indeterminate state)");
|
if usage > turbo_settings.load_threshold_low
|
||||||
false
|
&& 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Save the current state for next time
|
||||||
|
{
|
||||||
|
let turbo_states = get_turbo_states();
|
||||||
|
let hysteresis = turbo_states.get_for_power_state(on_ac_power);
|
||||||
|
hysteresis.update_state(enable_turbo);
|
||||||
|
}
|
||||||
|
|
||||||
// Apply the turbo setting
|
// Apply the turbo setting
|
||||||
let turbo_setting = if enable_turbo {
|
let turbo_setting = if enable_turbo {
|
||||||
TurboSetting::Always
|
TurboSetting::Always
|
||||||
|
@ -258,22 +440,21 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_turbo_auto_settings(settings: &TurboAutoSettings) -> Result<(), EngineError> {
|
fn validate_turbo_auto_settings(settings: &TurboAutoSettings) -> Result<(), EngineError> {
|
||||||
// Validate load thresholds
|
if settings.load_threshold_high <= settings.load_threshold_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(
|
return Err(EngineError::ConfigurationError(
|
||||||
"Invalid turbo auto settings: high threshold must be greater than low threshold"
|
"Invalid turbo auto settings: load thresholds must be between 0 % and 100 % with high > low"
|
||||||
.to_string(),
|
.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)
|
// 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 {
|
if settings.temp_threshold_high <= 0.0 || settings.temp_threshold_high > 110.0 {
|
||||||
return Err(EngineError::ConfigurationError(
|
return Err(EngineError::ConfigurationError(
|
||||||
"Invalid turbo auto settings: temperature threshold must be between 0°C and 110°C"
|
"Invalid turbo auto settings: temperature threshold must be between 0°C and 110°C"
|
||||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -140,7 +140,7 @@ fn main() -> Result<(), AppError> {
|
||||||
|
|
||||||
format_section("CPU Global Info");
|
format_section("CPU Global Info");
|
||||||
println!(
|
println!(
|
||||||
"Current Governor: {}",
|
"Current Governor: {}",
|
||||||
report
|
report
|
||||||
.cpu_global
|
.cpu_global
|
||||||
.current_governor
|
.current_governor
|
||||||
|
@ -148,11 +148,11 @@ fn main() -> Result<(), AppError> {
|
||||||
.unwrap_or("N/A")
|
.unwrap_or("N/A")
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"Available Governors: {}",
|
"Available Governors: {}", // 21 length baseline
|
||||||
report.cpu_global.available_governors.join(", ")
|
report.cpu_global.available_governors.join(", ")
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"Turbo Status: {}",
|
"Turbo Status: {}",
|
||||||
match report.cpu_global.turbo_status {
|
match report.cpu_global.turbo_status {
|
||||||
Some(true) => "Enabled",
|
Some(true) => "Enabled",
|
||||||
Some(false) => "Disabled",
|
Some(false) => "Disabled",
|
||||||
|
@ -161,15 +161,15 @@ fn main() -> Result<(), AppError> {
|
||||||
);
|
);
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"EPP: {}",
|
"EPP: {}",
|
||||||
report.cpu_global.epp.as_deref().unwrap_or("N/A")
|
report.cpu_global.epp.as_deref().unwrap_or("N/A")
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"EPB: {}",
|
"EPB: {}",
|
||||||
report.cpu_global.epb.as_deref().unwrap_or("N/A")
|
report.cpu_global.epb.as_deref().unwrap_or("N/A")
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"Platform Profile: {}",
|
"Platform Profile: {}",
|
||||||
report
|
report
|
||||||
.cpu_global
|
.cpu_global
|
||||||
.platform_profile
|
.platform_profile
|
||||||
|
@ -177,7 +177,7 @@ fn main() -> Result<(), AppError> {
|
||||||
.unwrap_or("N/A")
|
.unwrap_or("N/A")
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"CPU Temperature: {}",
|
"CPU Temperature: {}",
|
||||||
report.cpu_global.average_temperature_celsius.map_or_else(
|
report.cpu_global.average_temperature_celsius.map_or_else(
|
||||||
|| "N/A (No sensor detected)".to_string(),
|
|| "N/A (No sensor detected)".to_string(),
|
||||||
|t| format!("{t:.1}°C")
|
|t| format!("{t:.1}°C")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue