1
Fork 0
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:
NotAShelf 2025-05-13 22:28:16 +03:00
parent e6e46e18a8
commit 48bf7508aa
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
6 changed files with 259 additions and 24 deletions

View file

@ -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
View 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(())
}

View file

@ -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)),
}
}

View file

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