mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
config: nuke old config and implement a new system
This commit is contained in:
parent
2995909544
commit
0d3a88be03
7 changed files with 158 additions and 474 deletions
|
@ -10,7 +10,7 @@ rust-version = "1.85"
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
dirs = "6.0"
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
clap = { version = "4.0", features = ["derive", "env"] }
|
||||
num_cpus = "1.16"
|
||||
ctrlc = "3.4"
|
||||
log = "0.4"
|
||||
|
|
138
src/config.rs
Normal file
138
src/config.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{Context, bail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
|
||||
*value == T::default()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, clap::Parser, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields, default, rename_all = "kebab-case")]
|
||||
pub struct CpuDelta {
|
||||
/// The CPUs to apply the changes to. When unspecified, will be applied to all CPUs.
|
||||
#[arg(short = 'c', long = "for")]
|
||||
#[serde(rename = "for", skip_serializing_if = "is_default")]
|
||||
pub for_: Option<Vec<u32>>,
|
||||
|
||||
/// Set the CPU governor.
|
||||
#[arg(short = 'g', long)]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub governor: Option<String>, // TODO: Validate with clap for available governors.
|
||||
|
||||
/// Set CPU Energy Performance Preference (EPP). Short form: --epp.
|
||||
#[arg(short = 'p', long, alias = "epp")]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub energy_performance_preference: Option<String>, // TODO: Validate with clap for available governors.
|
||||
|
||||
/// Set CPU Energy Performance Bias (EPB). Short form: --epb.
|
||||
#[arg(short = 'b', long, alias = "epb")]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub energy_performance_bias: Option<String>, // TODO: Validate with clap for available governors.
|
||||
|
||||
/// Set minimum CPU frequency in MHz. Short form: --freq-min.
|
||||
#[arg(short = 'f', long, alias = "freq-min", value_parser = clap::value_parser!(u64).range(1..=10_000))]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub frequency_mhz_minimum: Option<u64>,
|
||||
|
||||
/// Set maximum CPU frequency in MHz. Short form: --freq-max.
|
||||
#[arg(short = 'F', long, alias = "freq-max", value_parser = clap::value_parser!(u64).range(1..=10_000))]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub frequency_mhz_maximum: Option<u64>,
|
||||
|
||||
/// Set turbo boost behaviour. Has to be for all CPUs.
|
||||
#[arg(short = 't', long, conflicts_with = "for_")]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub turbo: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, clap::Parser, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields, default, rename_all = "kebab-case")]
|
||||
pub struct PowerDelta {
|
||||
/// The power supplies to apply the changes to. When unspecified, will be applied to all power supplies.
|
||||
#[arg(short = 'p', long = "for")]
|
||||
#[serde(rename = "for", skip_serializing_if = "is_default")]
|
||||
pub for_: Option<Vec<String>>,
|
||||
|
||||
/// Set the percentage that the power supply has to drop under for charging to start. Short form: --charge-start.
|
||||
#[arg(short = 'c', long, alias = "charge-start", value_parser = clap::value_parser!(u8).range(0..=100))]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub charge_threshold_start: Option<u8>,
|
||||
|
||||
/// Set the percentage where charging will stop. Short form: --charge-end.
|
||||
#[arg(short = 'C', long, alias = "charge-end", value_parser = clap::value_parser!(u8).range(0..=100))]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub charge_threshold_end: Option<u8>,
|
||||
|
||||
/// Set ACPI platform profile. Has to be for all power supplies.
|
||||
#[arg(short = 'f', long, alias = "profile", conflicts_with = "for_")]
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub platform_profile: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[serde(untagged, rename_all = "kebab-case")]
|
||||
pub enum Condition {
|
||||
ChargeLessThan(u8),
|
||||
ChargeMoreThan(u8),
|
||||
|
||||
TemperatureLessThan(u8),
|
||||
TemperatureMoreThan(u8),
|
||||
|
||||
UtilizationLessThan(u8),
|
||||
UtilizationMoreThan(u8),
|
||||
|
||||
Charging,
|
||||
OnBattery,
|
||||
|
||||
False,
|
||||
#[default]
|
||||
True,
|
||||
|
||||
All(Vec<Condition>),
|
||||
Any(Vec<Condition>),
|
||||
|
||||
Not(Box<Condition>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct DaemonConfigLayer {
|
||||
priority: u8,
|
||||
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
if_: Condition,
|
||||
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
cpu: CpuDelta,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
power: PowerDelta,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(transparent, default, rename_all = "kebab-case")]
|
||||
pub struct DaemonConfig(pub Vec<DaemonConfigLayer>);
|
||||
|
||||
impl DaemonConfig {
|
||||
pub fn load_from(path: &Path) -> anyhow::Result<Self> {
|
||||
let contents = fs::read_to_string(path).with_context(|| {
|
||||
format!("failed to read config from '{path}'", path = path.display())
|
||||
})?;
|
||||
|
||||
let config: Self = toml::from_str(&contents).context("failed to parse config file")?;
|
||||
|
||||
{
|
||||
let mut priorities = Vec::with_capacity(config.0.len());
|
||||
|
||||
for layer in &config.0 {
|
||||
if priorities.contains(&layer.priority) {
|
||||
bail!("each config layer must have a different priority")
|
||||
}
|
||||
|
||||
priorities.push(layer.priority);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
// Configuration loading functionality
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Context as _;
|
||||
|
||||
use crate::config::types::{AppConfig, AppConfigToml, DaemonConfig, ProfileConfig};
|
||||
|
||||
/// The primary function to load application configuration from a specific path or from default locations.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `specific_path` - If provided, only attempts to load from this path and errors if not found
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(AppConfig)` - Successfully loaded configuration
|
||||
/// * `Err(ConfigError)` - Error loading or parsing configuration
|
||||
pub fn load_config() -> anyhow::Result<AppConfig> {
|
||||
load_config_from_path(None)
|
||||
}
|
||||
|
||||
/// Load configuration from a specific path or try default paths
|
||||
pub fn load_config_from_path(specific_path: Option<&str>) -> anyhow::Result<AppConfig> {
|
||||
// If a specific path is provided, only try that one
|
||||
if let Some(path_str) = specific_path {
|
||||
let path = Path::new(path_str);
|
||||
if path.exists() {
|
||||
return load_and_parse_config(path);
|
||||
}
|
||||
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("Specified config file not found: {}", path.display()),
|
||||
))?;
|
||||
}
|
||||
|
||||
// Check for SUPERFREQ_CONFIG environment variable
|
||||
if let Ok(env_path) = std::env::var("SUPERFREQ_CONFIG") {
|
||||
let env_path = Path::new(&env_path);
|
||||
if env_path.exists() {
|
||||
println!(
|
||||
"Loading config from SUPERFREQ_CONFIG: {}",
|
||||
env_path.display()
|
||||
);
|
||||
return load_and_parse_config(env_path);
|
||||
}
|
||||
eprintln!(
|
||||
"Warning: Config file specified by SUPERFREQ_CONFIG not found: {}",
|
||||
env_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// System-wide paths
|
||||
let config_paths = vec![
|
||||
PathBuf::from("/etc/xdg/superfreq/config.toml"),
|
||||
PathBuf::from("/etc/superfreq.toml"),
|
||||
];
|
||||
|
||||
for path in config_paths {
|
||||
if path.exists() {
|
||||
println!("Loading config from: {}", path.display());
|
||||
match load_and_parse_config(&path) {
|
||||
Ok(config) => return Ok(config),
|
||||
Err(e) => {
|
||||
eprintln!("Error with config file {}: {}", path.display(), e);
|
||||
// Continue trying other files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("No configuration file found or all failed to parse. Using default configuration.");
|
||||
// Construct default AppConfig by converting default AppConfigToml
|
||||
let default_toml_config = AppConfigToml::default();
|
||||
Ok(AppConfig {
|
||||
charger: ProfileConfig::from(default_toml_config.charger),
|
||||
battery: ProfileConfig::from(default_toml_config.battery),
|
||||
ignored_power_supplies: default_toml_config.ignored_power_supplies,
|
||||
daemon: DaemonConfig::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Load and parse a configuration file
|
||||
fn load_and_parse_config(path: &Path) -> anyhow::Result<AppConfig> {
|
||||
let contents = fs::read_to_string(path).with_context(|| {
|
||||
format!(
|
||||
"failed to read config file from '{path}'",
|
||||
path = path.display(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let toml_app_config =
|
||||
toml::from_str::<AppConfigToml>(&contents).context("failed to parse config toml")?;
|
||||
|
||||
// Handle inheritance of values from global to profile configs
|
||||
let mut charger_profile = toml_app_config.charger.clone();
|
||||
let mut battery_profile = toml_app_config.battery.clone();
|
||||
|
||||
// Clone global battery_charge_thresholds once if it exists
|
||||
if let Some(global_thresholds) = toml_app_config.battery_charge_thresholds {
|
||||
// Apply to charger profile if not already set
|
||||
if charger_profile.battery_charge_thresholds.is_none() {
|
||||
charger_profile.battery_charge_thresholds = Some(global_thresholds.clone());
|
||||
}
|
||||
|
||||
// Apply to battery profile if not already set
|
||||
if battery_profile.battery_charge_thresholds.is_none() {
|
||||
battery_profile.battery_charge_thresholds = Some(global_thresholds);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert AppConfigToml to AppConfig
|
||||
Ok(AppConfig {
|
||||
charger: ProfileConfig::from(charger_profile),
|
||||
battery: ProfileConfig::from(battery_profile),
|
||||
ignored_power_supplies: toml_app_config.ignored_power_supplies,
|
||||
daemon: DaemonConfig {
|
||||
poll_interval_sec: toml_app_config.daemon.poll_interval_sec,
|
||||
adaptive_interval: toml_app_config.daemon.adaptive_interval,
|
||||
min_poll_interval_sec: toml_app_config.daemon.min_poll_interval_sec,
|
||||
max_poll_interval_sec: toml_app_config.daemon.max_poll_interval_sec,
|
||||
throttle_on_battery: toml_app_config.daemon.throttle_on_battery,
|
||||
log_level: toml_app_config.daemon.log_level,
|
||||
stats_file_path: toml_app_config.daemon.stats_file_path,
|
||||
},
|
||||
})
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
pub mod load;
|
||||
pub mod types;
|
||||
|
||||
pub use load::*;
|
||||
pub use types::*;
|
|
@ -1,282 +0,0 @@
|
|||
use anyhow::bail;
|
||||
// Configuration types and structures for superfreq
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// Defines constant-returning functions used for default values.
|
||||
/// This hopefully reduces repetition since we have way too many
|
||||
/// default functions that just return constants.
|
||||
macro_rules! default_const {
|
||||
($($name:ident -> $type:ty = $value:expr;)*) => {
|
||||
$(
|
||||
const fn $name() -> $type {
|
||||
$value
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PowerSupplyChargeThresholds {
|
||||
pub start: u8,
|
||||
pub stop: u8,
|
||||
}
|
||||
|
||||
impl TryFrom<(u8, u8)> for PowerSupplyChargeThresholds {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from((start, stop): (u8, u8)) -> anyhow::Result<Self> {
|
||||
if stop == 0 {
|
||||
bail!("stop threshold must be greater than 0%");
|
||||
}
|
||||
if start >= stop {
|
||||
bail!("start threshold ({start}) must be less than stop threshold ({stop})");
|
||||
}
|
||||
if stop > 100 {
|
||||
bail!("stop threshold ({stop}) cannot exceed 100%");
|
||||
}
|
||||
|
||||
Ok(PowerSupplyChargeThresholds { start, stop })
|
||||
}
|
||||
}
|
||||
|
||||
// Structs for configuration using serde::Deserialize
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ProfileConfig {
|
||||
pub governor: Option<String>,
|
||||
pub turbo: Option<bool>,
|
||||
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>,
|
||||
#[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<PowerSupplyChargeThresholds>,
|
||||
}
|
||||
|
||||
impl Default for ProfileConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
governor: Some("schedutil".to_string()), // common sensible default (?)
|
||||
turbo: None,
|
||||
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: TurboAutoSettings::default(),
|
||||
enable_auto_turbo: default_enable_auto_turbo(),
|
||||
battery_charge_thresholds: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default, Clone)]
|
||||
pub struct AppConfig {
|
||||
#[serde(default)]
|
||||
pub charger: ProfileConfig,
|
||||
#[serde(default)]
|
||||
pub battery: ProfileConfig,
|
||||
pub ignored_power_supplies: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub daemon: DaemonConfig,
|
||||
}
|
||||
|
||||
// Intermediate structs for TOML parsing
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ProfileConfigToml {
|
||||
pub governor: Option<String>,
|
||||
pub turbo: Option<String>, // "always", "auto", "never"
|
||||
pub epp: Option<String>,
|
||||
pub epb: Option<String>,
|
||||
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<PowerSupplyChargeThresholds>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
pub struct AppConfigToml {
|
||||
#[serde(default)]
|
||||
pub charger: ProfileConfigToml,
|
||||
#[serde(default)]
|
||||
pub battery: ProfileConfigToml,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub battery_charge_thresholds: Option<PowerSupplyChargeThresholds>,
|
||||
pub ignored_power_supplies: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub daemon: DaemonConfigToml,
|
||||
}
|
||||
|
||||
impl Default for ProfileConfigToml {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
governor: Some("schedutil".to_string()),
|
||||
turbo: Some("auto".to_string()),
|
||||
epp: None,
|
||||
epb: None,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, 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,
|
||||
/// 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
|
||||
|
||||
default_const! {
|
||||
default_load_threshold_high -> f32 = DEFAULT_LOAD_THRESHOLD_HIGH;
|
||||
default_load_threshold_low -> f32 = DEFAULT_LOAD_THRESHOLD_LOW;
|
||||
|
||||
default_temp_threshold_high -> f32 = DEFAULT_TEMP_THRESHOLD_HIGH;
|
||||
|
||||
default_initial_turbo_state -> bool = DEFAULT_INITIAL_TURBO_STATE;
|
||||
}
|
||||
|
||||
impl Default for TurboAutoSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProfileConfigToml> for ProfileConfig {
|
||||
fn from(toml_config: ProfileConfigToml) -> Self {
|
||||
Self {
|
||||
governor: toml_config.governor,
|
||||
turbo: toml_config
|
||||
.turbo
|
||||
.and_then(|s| match s.to_lowercase().as_str() {
|
||||
"always" => Some(true),
|
||||
"auto" => None,
|
||||
"never" => Some(false),
|
||||
_ => panic!("invalid turbo value: {s}, must be one of: always, auto, never"),
|
||||
}),
|
||||
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: toml_config.turbo_auto_settings.unwrap_or_default(),
|
||||
enable_auto_turbo: toml_config.enable_auto_turbo,
|
||||
battery_charge_thresholds: toml_config.battery_charge_thresholds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct DaemonConfig {
|
||||
#[serde(default = "default_poll_interval_sec")]
|
||||
pub poll_interval_sec: u64,
|
||||
#[serde(default = "default_adaptive_interval")]
|
||||
pub adaptive_interval: bool,
|
||||
#[serde(default = "default_min_poll_interval_sec")]
|
||||
pub min_poll_interval_sec: u64,
|
||||
#[serde(default = "default_max_poll_interval_sec")]
|
||||
pub max_poll_interval_sec: u64,
|
||||
#[serde(default = "default_throttle_on_battery")]
|
||||
pub throttle_on_battery: bool,
|
||||
#[serde(default = "default_log_level")]
|
||||
pub log_level: LogLevel,
|
||||
#[serde(default = "default_stats_file_path")]
|
||||
pub stats_file_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LogLevel {
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
Debug,
|
||||
}
|
||||
|
||||
impl Default for DaemonConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
poll_interval_sec: default_poll_interval_sec(),
|
||||
adaptive_interval: default_adaptive_interval(),
|
||||
min_poll_interval_sec: default_min_poll_interval_sec(),
|
||||
max_poll_interval_sec: default_max_poll_interval_sec(),
|
||||
throttle_on_battery: default_throttle_on_battery(),
|
||||
log_level: default_log_level(),
|
||||
stats_file_path: default_stats_file_path(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default_const! {
|
||||
default_poll_interval_sec -> u64 = 5;
|
||||
default_adaptive_interval -> bool = false;
|
||||
default_min_poll_interval_sec -> u64 = 1;
|
||||
default_max_poll_interval_sec -> u64 = 30;
|
||||
default_throttle_on_battery -> bool = true;
|
||||
default_log_level -> LogLevel = LogLevel::Info;
|
||||
default_stats_file_path -> Option<String> = None;
|
||||
default_enable_auto_turbo -> bool = true;
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct DaemonConfigToml {
|
||||
#[serde(default = "default_poll_interval_sec")]
|
||||
pub poll_interval_sec: u64,
|
||||
#[serde(default = "default_adaptive_interval")]
|
||||
pub adaptive_interval: bool,
|
||||
#[serde(default = "default_min_poll_interval_sec")]
|
||||
pub min_poll_interval_sec: u64,
|
||||
#[serde(default = "default_max_poll_interval_sec")]
|
||||
pub max_poll_interval_sec: u64,
|
||||
#[serde(default = "default_throttle_on_battery")]
|
||||
pub throttle_on_battery: bool,
|
||||
#[serde(default = "default_log_level")]
|
||||
pub log_level: LogLevel,
|
||||
#[serde(default = "default_stats_file_path")]
|
||||
pub stats_file_path: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for DaemonConfigToml {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
poll_interval_sec: default_poll_interval_sec(),
|
||||
adaptive_interval: default_adaptive_interval(),
|
||||
min_poll_interval_sec: default_min_poll_interval_sec(),
|
||||
max_poll_interval_sec: default_max_poll_interval_sec(),
|
||||
throttle_on_battery: default_throttle_on_battery(),
|
||||
log_level: default_log_level(),
|
||||
stats_file_path: default_stats_file_path(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ fn read_u64(path: impl AsRef<Path>) -> anyhow::Result<u64> {
|
|||
|
||||
let content = fs::read_to_string(path)?;
|
||||
|
||||
Ok(content.trim().parse::<u64>()?)
|
||||
Ok(content.trim().parse()?)
|
||||
}
|
||||
|
||||
fn write(path: impl AsRef<Path>, value: &str) -> anyhow::Result<()> {
|
||||
|
@ -73,7 +73,7 @@ impl Cpu {
|
|||
};
|
||||
|
||||
// Has to match "cpu{N}".
|
||||
let Ok(number) = cpu_prefix_removed.parse::<u32>() else {
|
||||
let Ok(number) = cpu_prefix_removed.parse() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
73
src/main.rs
73
src/main.rs
|
@ -10,6 +10,7 @@ use anyhow::Context;
|
|||
use clap::Parser as _;
|
||||
use std::fmt::Write as _;
|
||||
use std::io::Write as _;
|
||||
use std::path::PathBuf;
|
||||
use std::{io, process};
|
||||
use yansi::Paint as _;
|
||||
|
||||
|
@ -29,57 +30,17 @@ enum Command {
|
|||
Info,
|
||||
|
||||
/// Start the daemon.
|
||||
Start,
|
||||
Start {
|
||||
/// The daemon config path.
|
||||
#[arg(long, env = "SUPERFREQ_CONFIG")]
|
||||
config: PathBuf,
|
||||
},
|
||||
|
||||
/// Modify CPU attributes.
|
||||
CpuSet {
|
||||
/// The CPUs to apply the changes to. When unspecified, will be applied to all CPUs.
|
||||
#[arg(short = 'c', long = "for")]
|
||||
for_: Option<Vec<u32>>,
|
||||
|
||||
/// Set the CPU governor.
|
||||
#[arg(short = 'g', long)]
|
||||
governor: Option<String>, // TODO: Validate with clap for available governors.
|
||||
|
||||
/// Set CPU Energy Performance Preference (EPP). Short form: --epp.
|
||||
#[arg(short = 'p', long, alias = "epp")]
|
||||
energy_performance_preference: Option<String>,
|
||||
|
||||
/// Set CPU Energy Performance Bias (EPB). Short form: --epb.
|
||||
#[arg(short = 'b', long, alias = "epb")]
|
||||
energy_performance_bias: Option<String>,
|
||||
|
||||
/// Set minimum CPU frequency in MHz. Short form: --freq-min.
|
||||
#[arg(short = 'f', long, alias = "freq-min", value_parser = clap::value_parser!(u64).range(1..=10_000))]
|
||||
frequency_mhz_minimum: Option<u64>,
|
||||
|
||||
/// Set maximum CPU frequency in MHz. Short form: --freq-max.
|
||||
#[arg(short = 'F', long, alias = "freq-max", value_parser = clap::value_parser!(u64).range(1..=10_000))]
|
||||
frequency_mhz_maximum: Option<u64>,
|
||||
|
||||
/// Set turbo boost behaviour. Has to be for all CPUs.
|
||||
#[arg(short = 't', long, conflicts_with = "for_")]
|
||||
turbo: Option<bool>,
|
||||
},
|
||||
CpuSet(config::CpuDelta),
|
||||
|
||||
/// Modify power supply attributes.
|
||||
PowerSet {
|
||||
/// The power supplies to apply the changes to. When unspecified, will be applied to all power supplies.
|
||||
#[arg(short = 'p', long = "for")]
|
||||
for_: Option<Vec<String>>,
|
||||
|
||||
/// Set the percentage that the power supply has to drop under for charging to start. Short form: --charge-start.
|
||||
#[arg(short = 'c', long, alias = "charge-start", value_parser = clap::value_parser!(u8).range(0..=100))]
|
||||
charge_threshold_start: Option<u8>,
|
||||
|
||||
/// Set the percentage where charging will stop. Short form: --charge-end.
|
||||
#[arg(short = 'C', long, alias = "charge-end", value_parser = clap::value_parser!(u8).range(0..=100))]
|
||||
charge_threshold_end: Option<u8>,
|
||||
|
||||
/// Set ACPI platform profile. Has to be for all power supplies.
|
||||
#[arg(short = 'f', long, alias = "profile", conflicts_with = "for_")]
|
||||
platform_profile: Option<String>,
|
||||
},
|
||||
PowerSet(config::PowerDelta),
|
||||
}
|
||||
|
||||
fn real_main() -> anyhow::Result<()> {
|
||||
|
@ -91,17 +52,17 @@ fn real_main() -> anyhow::Result<()> {
|
|||
.format_module_path(false)
|
||||
.init();
|
||||
|
||||
let config = config::load_config().context("failed to load config")?;
|
||||
|
||||
match cli.command {
|
||||
Command::Info => todo!(),
|
||||
|
||||
Command::Start => {
|
||||
daemon::run_daemon(config)?;
|
||||
Ok(())
|
||||
Command::Start { config } => {
|
||||
let config = config::DaemonConfig::load_from(&config)
|
||||
.context("failed to load daemon config file")?;
|
||||
|
||||
daemon::run(config)
|
||||
}
|
||||
|
||||
Command::CpuSet {
|
||||
Command::CpuSet(config::CpuDelta {
|
||||
for_,
|
||||
governor,
|
||||
energy_performance_preference,
|
||||
|
@ -109,7 +70,7 @@ fn real_main() -> anyhow::Result<()> {
|
|||
frequency_mhz_minimum,
|
||||
frequency_mhz_maximum,
|
||||
turbo,
|
||||
} => {
|
||||
}) => {
|
||||
let cpus = match for_ {
|
||||
Some(numbers) => {
|
||||
let mut cpus = Vec::with_capacity(numbers.len());
|
||||
|
@ -152,12 +113,12 @@ fn real_main() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
Command::PowerSet {
|
||||
Command::PowerSet(config::PowerDelta {
|
||||
for_,
|
||||
charge_threshold_start,
|
||||
charge_threshold_end,
|
||||
platform_profile,
|
||||
} => {
|
||||
}) => {
|
||||
let power_supplies = match for_ {
|
||||
Some(names) => {
|
||||
let mut power_supplies = Vec::with_capacity(names.len());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue