mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
core: init daemon mode for persistent operations
This commit is contained in:
parent
e6e46e18a8
commit
48bf7508aa
6 changed files with 259 additions and 24 deletions
|
@ -1,18 +1,19 @@
|
|||
use serde::Deserialize;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use crate::core::{OperationalMode, TurboSetting};
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// Structs for configuration using serde::Deserialize
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct ProfileConfig {
|
||||
pub governor: Option<String>,
|
||||
pub turbo: Option<TurboSetting>,
|
||||
pub epp: Option<String>, // Energy Performance Preference (EPP)
|
||||
pub epb: Option<String>, // Energy Performance Bias (EPB) - usually an integer, but string for flexibility from sysfs
|
||||
pub epp: Option<String>, // Energy Performance Preference (EPP)
|
||||
pub epb: Option<String>, // Energy Performance Bias (EPB) - usually an integer, but string for flexibility from sysfs
|
||||
pub min_freq_mhz: Option<u32>,
|
||||
pub max_freq_mhz: Option<u32>,
|
||||
pub platform_profile: Option<String>,
|
||||
pub turbo_auto_settings: Option<TurboAutoSettings>,
|
||||
}
|
||||
|
||||
impl Default for ProfileConfig {
|
||||
|
@ -20,11 +21,12 @@ impl Default for ProfileConfig {
|
|||
ProfileConfig {
|
||||
governor: Some("schedutil".to_string()), // common sensible default (?)
|
||||
turbo: Some(TurboSetting::Auto),
|
||||
epp: None, // defaults depend on governor and system
|
||||
epb: None, // defaults depend on governor and system
|
||||
min_freq_mhz: None, // no override
|
||||
max_freq_mhz: None, // no override
|
||||
epp: None, // defaults depend on governor and system
|
||||
epb: None, // defaults depend on governor and system
|
||||
min_freq_mhz: None, // no override
|
||||
max_freq_mhz: None, // no override
|
||||
platform_profile: None, // no override
|
||||
turbo_auto_settings: Some(TurboAutoSettings::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +92,9 @@ pub fn load_config() -> Result<AppConfig, ConfigError> {
|
|||
let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml");
|
||||
config_paths.push(user_config_path);
|
||||
} else {
|
||||
eprintln!("Warning: Could not determine home directory. User-specific config will not be loaded.");
|
||||
eprintln!(
|
||||
"Warning: Could not determine home directory. User-specific config will not be loaded."
|
||||
);
|
||||
}
|
||||
|
||||
// System-wide path
|
||||
|
@ -108,7 +112,8 @@ pub fn load_config() -> Result<AppConfig, ConfigError> {
|
|||
let app_config = AppConfig {
|
||||
charger: ProfileConfig::from(toml_app_config.charger),
|
||||
battery: ProfileConfig::from(toml_app_config.battery),
|
||||
battery_charge_thresholds: toml_app_config.battery_charge_thresholds,
|
||||
battery_charge_thresholds: toml_app_config
|
||||
.battery_charge_thresholds,
|
||||
ignored_power_supplies: toml_app_config.ignored_power_supplies,
|
||||
poll_interval_sec: toml_app_config.poll_interval_sec,
|
||||
};
|
||||
|
@ -176,22 +181,59 @@ impl Default for ProfileConfigToml {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct TurboAutoSettings {
|
||||
#[serde(default = "default_load_threshold_high")]
|
||||
pub load_threshold_high: f32,
|
||||
#[serde(default = "default_load_threshold_low")]
|
||||
pub load_threshold_low: f32,
|
||||
#[serde(default = "default_temp_threshold_high")]
|
||||
pub temp_threshold_high: f32,
|
||||
}
|
||||
|
||||
// Default thresholds for Auto turbo mode
|
||||
pub const DEFAULT_LOAD_THRESHOLD_HIGH: f32 = 70.0; // Enable turbo if load is above this
|
||||
pub const DEFAULT_LOAD_THRESHOLD_LOW: f32 = 30.0; // Disable turbo if load is below this
|
||||
pub const DEFAULT_TEMP_THRESHOLD_HIGH: f32 = 75.0; // Disable turbo if temperature is above this
|
||||
|
||||
fn default_load_threshold_high() -> f32 {
|
||||
DEFAULT_LOAD_THRESHOLD_HIGH
|
||||
}
|
||||
fn default_load_threshold_low() -> f32 {
|
||||
DEFAULT_LOAD_THRESHOLD_LOW
|
||||
}
|
||||
fn default_temp_threshold_high() -> f32 {
|
||||
DEFAULT_TEMP_THRESHOLD_HIGH
|
||||
}
|
||||
|
||||
impl Default for TurboAutoSettings {
|
||||
fn default() -> Self {
|
||||
TurboAutoSettings {
|
||||
load_threshold_high: DEFAULT_LOAD_THRESHOLD_HIGH,
|
||||
load_threshold_low: DEFAULT_LOAD_THRESHOLD_LOW,
|
||||
temp_threshold_high: DEFAULT_TEMP_THRESHOLD_HIGH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProfileConfigToml> for ProfileConfig {
|
||||
fn from(toml_config: ProfileConfigToml) -> Self {
|
||||
ProfileConfig {
|
||||
governor: toml_config.governor,
|
||||
turbo: toml_config.turbo.and_then(|s| match s.to_lowercase().as_str() {
|
||||
"always" => Some(TurboSetting::Always),
|
||||
"auto" => Some(TurboSetting::Auto),
|
||||
"never" => Some(TurboSetting::Never),
|
||||
_ => None,
|
||||
}),
|
||||
turbo: toml_config
|
||||
.turbo
|
||||
.and_then(|s| match s.to_lowercase().as_str() {
|
||||
"always" => Some(TurboSetting::Always),
|
||||
"auto" => Some(TurboSetting::Auto),
|
||||
"never" => Some(TurboSetting::Never),
|
||||
_ => None,
|
||||
}),
|
||||
epp: toml_config.epp,
|
||||
epb: toml_config.epb,
|
||||
min_freq_mhz: toml_config.min_freq_mhz,
|
||||
max_freq_mhz: toml_config.max_freq_mhz,
|
||||
platform_profile: toml_config.platform_profile,
|
||||
turbo_auto_settings: Some(TurboAutoSettings::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
61
src/daemon.rs
Normal file
61
src/daemon.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crate::config::AppConfig;
|
||||
use crate::engine;
|
||||
use crate::monitor;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Run the daemon in foreground mode
|
||||
pub fn run_background(config: AppConfig) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Starting superfreq daemon in foreground mode...");
|
||||
|
||||
// Create a flag that will be set to true when a signal is received
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
|
||||
// Set up signal handlers
|
||||
ctrlc::set_handler(move || {
|
||||
println!("Received shutdown signal, exiting...");
|
||||
r.store(false, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
println!(
|
||||
"Daemon initialized with poll interval: {}s",
|
||||
config.poll_interval_sec
|
||||
);
|
||||
|
||||
// Main loop
|
||||
while running.load(Ordering::SeqCst) {
|
||||
let start_time = Instant::now();
|
||||
|
||||
match monitor::collect_system_report(&config) {
|
||||
Ok(report) => {
|
||||
println!("Collected system report, applying settings...");
|
||||
match engine::determine_and_apply_settings(&report, &config, None) {
|
||||
Ok(()) => {
|
||||
println!("Successfully applied system settings");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error applying system settings: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error collecting system report: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep for the remaining time in the poll interval
|
||||
let elapsed = start_time.elapsed();
|
||||
let poll_duration = Duration::from_secs(config.poll_interval_sec);
|
||||
if elapsed < poll_duration {
|
||||
let sleep_time = poll_duration - elapsed;
|
||||
println!("Sleeping for {}s until next cycle", sleep_time.as_secs());
|
||||
std::thread::sleep(sleep_time);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Daemon stopped");
|
||||
Ok(())
|
||||
}
|
102
src/engine.rs
102
src/engine.rs
|
@ -1,5 +1,5 @@
|
|||
use crate::core::{SystemReport, OperationalMode, TurboSetting};
|
||||
use crate::config::{AppConfig, ProfileConfig};
|
||||
use crate::config::{AppConfig, ProfileConfig, TurboAutoSettings};
|
||||
use crate::core::{OperationalMode, SystemReport, TurboSetting};
|
||||
use crate::cpu::{self, ControlError};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -57,8 +57,8 @@ pub fn determine_and_apply_settings(
|
|||
// If no batteries, assume AC power (desktop).
|
||||
// Otherwise, check the ac_connected status from the (first) battery.
|
||||
// XXX: This relies on the setting ac_connected in BatteryInfo being set correctly.
|
||||
let on_ac_power = report.batteries.is_empty() ||
|
||||
report.batteries.first().map_or(false, |b| b.ac_connected);
|
||||
let on_ac_power = report.batteries.is_empty()
|
||||
|| report.batteries.first().map_or(false, |b| b.ac_connected);
|
||||
|
||||
if on_ac_power {
|
||||
println!("Engine: On AC power, selecting Charger profile.");
|
||||
|
@ -80,7 +80,13 @@ pub fn determine_and_apply_settings(
|
|||
|
||||
if let Some(turbo_setting) = selected_profile_config.turbo {
|
||||
println!("Engine: Setting turbo to '{:?}'", turbo_setting);
|
||||
cpu::set_turbo(turbo_setting)?;
|
||||
match turbo_setting {
|
||||
TurboSetting::Auto => {
|
||||
println!("Engine: Managing turbo in auto mode based on system conditions");
|
||||
manage_auto_turbo(report, selected_profile_config)?;
|
||||
}
|
||||
_ => cpu::set_turbo(turbo_setting)?,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(epp) = &selected_profile_config.epp {
|
||||
|
@ -89,7 +95,7 @@ pub fn determine_and_apply_settings(
|
|||
}
|
||||
|
||||
if let Some(epb) = &selected_profile_config.epb {
|
||||
println!("Engine: Setting EPB to '{}'", epb);
|
||||
println!("Engine: Setting EPB to '{}'", epb);
|
||||
cpu::set_epb(epb, None)?;
|
||||
}
|
||||
|
||||
|
@ -111,4 +117,86 @@ pub fn determine_and_apply_settings(
|
|||
println!("Engine: Profile settings applied successfully.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<(), EngineError> {
|
||||
// Get the auto turbo settings from the config, or use defaults
|
||||
let turbo_settings = config.turbo_auto_settings.clone().unwrap_or_default();
|
||||
|
||||
// Get average CPU temperature and CPU load
|
||||
let cpu_temp = report.cpu_global.average_temperature_celsius;
|
||||
|
||||
// Check if we have CPU usage data available
|
||||
let avg_cpu_usage = if !report.cpu_cores.is_empty() {
|
||||
let sum: f32 = report
|
||||
.cpu_cores
|
||||
.iter()
|
||||
.filter_map(|core| core.usage_percent)
|
||||
.sum();
|
||||
let count = report
|
||||
.cpu_cores
|
||||
.iter()
|
||||
.filter(|core| core.usage_percent.is_some())
|
||||
.count();
|
||||
|
||||
if count > 0 {
|
||||
Some(sum / count as f32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Decision logic for enabling/disabling turbo
|
||||
let enable_turbo = match (cpu_temp, avg_cpu_usage) {
|
||||
// If temperature is too high, disable turbo regardless of load
|
||||
(Some(temp), _) if temp >= turbo_settings.temp_threshold_high => {
|
||||
println!(
|
||||
"Engine: Auto Turbo: Disabled due to high temperature ({:.1}°C >= {:.1}°C)",
|
||||
temp, turbo_settings.temp_threshold_high
|
||||
);
|
||||
false
|
||||
}
|
||||
// If load is high enough, enable turbo (unless temp already caused it to disable)
|
||||
(_, Some(usage)) if usage >= turbo_settings.load_threshold_high => {
|
||||
println!(
|
||||
"Engine: Auto Turbo: Enabled due to high CPU load ({:.1}% >= {:.1}%)",
|
||||
usage, turbo_settings.load_threshold_high
|
||||
);
|
||||
true
|
||||
}
|
||||
// If load is low, disable turbo
|
||||
(_, Some(usage)) if usage <= turbo_settings.load_threshold_low => {
|
||||
println!(
|
||||
"Engine: Auto Turbo: Disabled due to low CPU load ({:.1}% <= {:.1}%)",
|
||||
usage, turbo_settings.load_threshold_low
|
||||
);
|
||||
false
|
||||
}
|
||||
// In intermediate load scenarios or if we can't determine, leave turbo in current state
|
||||
// For now, we'll disable it as a safe default
|
||||
_ => {
|
||||
println!("Engine: Auto Turbo: Disabled (default for indeterminate state)");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// Apply the turbo setting
|
||||
let turbo_setting = if enable_turbo {
|
||||
TurboSetting::Always
|
||||
} else {
|
||||
TurboSetting::Never
|
||||
};
|
||||
|
||||
match cpu::set_turbo(turbo_setting) {
|
||||
Ok(_) => {
|
||||
println!(
|
||||
"Engine: Auto Turbo: Successfully set turbo to {}",
|
||||
if enable_turbo { "enabled" } else { "disabled" }
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(EngineError::ControlError(e)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mod config;
|
||||
mod core;
|
||||
mod cpu;
|
||||
mod daemon;
|
||||
mod engine;
|
||||
mod monitor;
|
||||
|
||||
|
@ -19,6 +20,8 @@ struct Cli {
|
|||
enum Commands {
|
||||
/// Display current system information
|
||||
Info,
|
||||
/// Run as a daemon in the background
|
||||
Daemon,
|
||||
/// Set CPU governor
|
||||
SetGovernor {
|
||||
governor: String,
|
||||
|
@ -183,6 +186,7 @@ fn main() {
|
|||
}
|
||||
Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>),
|
||||
Some(Commands::Daemon) => daemon::run_background(config),
|
||||
None => {
|
||||
println!("Welcome to superfreq! Use --help for commands.");
|
||||
println!("Current effective configuration: {:?}", config);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue