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

39
Cargo.lock generated
View file

@ -64,6 +64,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.38" version = "4.5.38"
@ -110,6 +116,25 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "ctrlc"
version = "3.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
dependencies = [
"nix",
"windows-sys",
]
[[package]]
name = "daemonize"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "6.0.0" version = "6.0.0"
@ -204,6 +229,18 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.16.0" version = "1.16.0"
@ -295,6 +332,8 @@ name = "superfreq"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"ctrlc",
"daemonize",
"dirs", "dirs",
"num_cpus", "num_cpus",
"serde", "serde",

View file

@ -9,3 +9,4 @@ toml = "0.8"
dirs = "6.0" dirs = "6.0"
clap = { version = "4.0", features = ["derive"] } clap = { version = "4.0", features = ["derive"] }
num_cpus = "1.16" num_cpus = "1.16"
ctrlc = "3.4"

View file

@ -1,18 +1,19 @@
use serde::Deserialize;
use std::path::{Path, PathBuf};
use std::fs;
use crate::core::{OperationalMode, TurboSetting}; use crate::core::{OperationalMode, TurboSetting};
use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
// Structs for configuration using serde::Deserialize // Structs for configuration using serde::Deserialize
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct ProfileConfig { pub struct ProfileConfig {
pub governor: Option<String>, pub governor: Option<String>,
pub turbo: Option<TurboSetting>, pub turbo: Option<TurboSetting>,
pub epp: Option<String>, // Energy Performance Preference (EPP) 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 epb: Option<String>, // Energy Performance Bias (EPB) - usually an integer, but string for flexibility from sysfs
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>,
} }
impl Default for ProfileConfig { impl Default for ProfileConfig {
@ -20,11 +21,12 @@ impl Default for ProfileConfig {
ProfileConfig { ProfileConfig {
governor: Some("schedutil".to_string()), // common sensible default (?) governor: Some("schedutil".to_string()), // common sensible default (?)
turbo: Some(TurboSetting::Auto), turbo: Some(TurboSetting::Auto),
epp: None, // defaults depend on governor and system epp: None, // defaults depend on governor and system
epb: None, // defaults depend on governor and system epb: None, // defaults depend on governor and system
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()),
} }
} }
} }
@ -90,7 +92,9 @@ pub fn load_config() -> Result<AppConfig, ConfigError> {
let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml"); let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml");
config_paths.push(user_config_path); config_paths.push(user_config_path);
} else { } 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 // System-wide path
@ -108,7 +112,8 @@ pub fn load_config() -> Result<AppConfig, ConfigError> {
let app_config = AppConfig { let app_config = AppConfig {
charger: ProfileConfig::from(toml_app_config.charger), charger: ProfileConfig::from(toml_app_config.charger),
battery: ProfileConfig::from(toml_app_config.battery), 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, ignored_power_supplies: toml_app_config.ignored_power_supplies,
poll_interval_sec: toml_app_config.poll_interval_sec, 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 { impl From<ProfileConfigToml> for ProfileConfig {
fn from(toml_config: ProfileConfigToml) -> Self { fn from(toml_config: ProfileConfigToml) -> Self {
ProfileConfig { ProfileConfig {
governor: toml_config.governor, governor: toml_config.governor,
turbo: toml_config.turbo.and_then(|s| match s.to_lowercase().as_str() { turbo: toml_config
"always" => Some(TurboSetting::Always), .turbo
"auto" => Some(TurboSetting::Auto), .and_then(|s| match s.to_lowercase().as_str() {
"never" => Some(TurboSetting::Never), "always" => Some(TurboSetting::Always),
_ => None, "auto" => Some(TurboSetting::Auto),
}), "never" => Some(TurboSetting::Never),
_ => None,
}),
epp: toml_config.epp, epp: toml_config.epp,
epb: toml_config.epb, epb: toml_config.epb,
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()),
} }
} }
} }

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, TurboAutoSettings};
use crate::config::{AppConfig, ProfileConfig}; use crate::core::{OperationalMode, SystemReport, TurboSetting};
use crate::cpu::{self, ControlError}; use crate::cpu::{self, ControlError};
#[derive(Debug)] #[derive(Debug)]
@ -57,8 +57,8 @@ pub fn determine_and_apply_settings(
// If no batteries, assume AC power (desktop). // If no batteries, assume AC power (desktop).
// Otherwise, check the ac_connected status from the (first) battery. // Otherwise, check the ac_connected status from the (first) battery.
// XXX: This relies on the setting ac_connected in BatteryInfo being set correctly. // XXX: This relies on the setting ac_connected in BatteryInfo being set correctly.
let on_ac_power = report.batteries.is_empty() || let on_ac_power = report.batteries.is_empty()
report.batteries.first().map_or(false, |b| b.ac_connected); || report.batteries.first().map_or(false, |b| b.ac_connected);
if on_ac_power { if on_ac_power {
println!("Engine: On AC power, selecting Charger profile."); 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 { if let Some(turbo_setting) = selected_profile_config.turbo {
println!("Engine: Setting turbo to '{:?}'", turbo_setting); 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 { 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 { 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)?; cpu::set_epb(epb, None)?;
} }
@ -111,4 +117,86 @@ pub fn determine_and_apply_settings(
println!("Engine: Profile settings applied successfully."); println!("Engine: Profile settings applied successfully.");
Ok(()) 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 config;
mod core; mod core;
mod cpu; mod cpu;
mod daemon;
mod engine; mod engine;
mod monitor; mod monitor;
@ -19,6 +20,8 @@ struct Cli {
enum Commands { enum Commands {
/// Display current system information /// Display current system information
Info, Info,
/// Run as a daemon in the background
Daemon,
/// Set CPU governor /// Set CPU governor
SetGovernor { SetGovernor {
governor: String, governor: String,
@ -183,6 +186,7 @@ fn main() {
} }
Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile) Some(Commands::SetPlatformProfile { profile }) => cpu::set_platform_profile(&profile)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>), .map_err(|e| Box::new(e) as Box<dyn std::error::Error>),
Some(Commands::Daemon) => daemon::run_background(config),
None => { None => {
println!("Welcome to superfreq! Use --help for commands."); println!("Welcome to superfreq! Use --help for commands.");
println!("Current effective configuration: {:?}", config); println!("Current effective configuration: {:?}", config);