1
Fork 0
mirror of https://github.com/RGBCube/watt synced 2025-07-29 17:07:46 +00:00

Merge pull request #26 from NotAShelf/auto-turbo

feature: dynamic CPU turbo management
This commit is contained in:
raf 2025-05-18 05:11:30 +03:00 committed by GitHub
commit 0b8551d34f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 335 additions and 59 deletions

View file

@ -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 <raf@notashelf.dev>"]
rust-version = "1.85"
[dependencies]
serde = { version = "1.0", features = ["derive"] }

View file

@ -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
@ -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,15 @@ 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,
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)
@ -166,6 +177,14 @@ 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,
initial_turbo_state = false, # start with turbo disabled on battery for power savings
}
epp = "power"
epb = "balance_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
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
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
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
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.
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
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.

View file

@ -50,7 +50,10 @@ pub struct ProfileConfig {
pub min_freq_mhz: Option<u32>,
pub max_freq_mhz: Option<u32>,
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")]
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
}
@ -65,7 +68,8 @@ 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,
}
}
@ -124,6 +128,9 @@ pub struct ProfileConfigToml {
pub min_freq_mhz: Option<u32>,
pub max_freq_mhz: Option<u32>,
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")]
pub battery_charge_thresholds: Option<BatteryChargeThresholds>,
}
@ -151,6 +158,8 @@ impl Default for ProfileConfigToml {
min_freq_mhz: None,
max_freq_mhz: None,
platform_profile: None,
turbo_auto_settings: None,
enable_auto_turbo: default_enable_auto_turbo(),
battery_charge_thresholds: None,
}
}
@ -164,12 +173,18 @@ 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,
}
// 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
@ -180,6 +195,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 {
@ -187,6 +205,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,
}
}
}
@ -208,7 +227,8 @@ impl From<ProfileConfigToml> 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.unwrap_or_default(),
enable_auto_turbo: toml_config.enable_auto_turbo,
battery_charge_thresholds: toml_config.battery_charge_thresholds,
}
}
@ -282,6 +302,10 @@ const fn default_stats_file_path() -> Option<String> {
None
}
const fn default_enable_auto_turbo() -> bool {
true
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct DaemonConfigToml {
#[serde(default = "default_poll_interval_sec")]

View file

@ -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<T, E = ControlError> = std::result::Result<T, E>;
@ -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

View file

@ -4,6 +4,116 @@ 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};
/// 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
/// previously did:
@ -37,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,
@ -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;
if let Some(mode) = force_mode {
@ -70,17 +191,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;
@ -112,8 +223,19 @@ 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, 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:?}"), || {
@ -172,12 +294,16 @@ pub fn determine_and_apply_settings(
Ok(())
}
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();
fn manage_auto_turbo(
report: &SystemReport,
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_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;
@ -204,40 +330,96 @@ 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 the previous state or initialize with the configured initial state
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
(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
);
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
);
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
_ => {
info!("Auto Turbo: Disabled (default for indeterminate state)");
false
// In intermediate load range, maintain previous state (hysteresis)
(_, Some(usage), 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
}
// 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
let turbo_setting = if enable_turbo {
TurboSetting::Always
@ -258,22 +440,21 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<()
}
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(
"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(),
));
}
// 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
// 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"

View file

@ -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")