diff --git a/README.md b/README.md index 574879f..881ed12 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,7 @@ if.is-governor-available = "powersave" if.is-energy-performance-preference-available = "balance_performance" if.is-energy-performance-bias-available = "5" if.is-platform-profile-available = "low-power" +if.is-driver-loaded = "intel_pstate" ``` Each will be `true` only if the named value is available on your system. If the diff --git a/watt/config.rs b/watt/config.rs index a1f6247..aa1148d 100644 --- a/watt/config.rs +++ b/watt/config.rs @@ -417,6 +417,10 @@ pub enum Expression { #[serde(rename = "is-platform-profile-available")] value: Box, }, + IsDriverLoaded { + #[serde(rename = "is-driver-loaded")] + value: Box, + }, #[serde(with = "expression::frequency_available")] FrequencyAvailable, @@ -729,6 +733,11 @@ impl Expression { Boolean(available) }, + IsDriverLoaded { value } => { + let value = eval!(value).try_into_string()?; + + Boolean(crate::fs::exists(format!("/sys/module/{value}"))) + }, FrequencyAvailable => Boolean(state.frequency_available), TurboAvailable => Boolean(state.turbo_available), @@ -896,6 +905,7 @@ fn literal_is_true(expression: &Expression) -> bool { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct Rule { + pub name: String, pub priority: u16, #[serde( @@ -914,6 +924,7 @@ pub struct Rule { impl Default for Rule { fn default() -> Self { Self { + name: String::default(), priority: u16::default(), condition: literal_true(), cpu: CpusDelta::default(), @@ -979,7 +990,7 @@ impl DaemonConfig { config.rules.sort_by_key(|rule| rule.priority); - log::debug!("sorted {} rules by priority", config.rules.len()); + log::debug!("sorted {len} rules by priority", len = config.rules.len()); log::debug!("loaded config: {config:#?}"); diff --git a/watt/config.toml b/watt/config.toml index 42d460a..2aaa2be 100644 --- a/watt/config.toml +++ b/watt/config.toml @@ -3,8 +3,8 @@ # Rules are evaluated by priority (higher number => higher priority). # Each rule can specify conditions and actions for CPU and power management. -# Emergency thermal protection (highest priority). [[rule]] +name = "emergency-thermal-protection" if = { is-more-than = 85.0, value = "$cpu-temperature" } priority = 100 @@ -14,20 +14,19 @@ cpu.frequency-mhz-maximum = { if = "?frequency-available", then = 2000 } cpu.governor = { if.is-governor-available = "powersave", then = "powersave" } cpu.turbo = { if = "?turbo-available", then = false } -# Critical battery preservation. [[rule]] +name = "critical-battery-preservation" if.all = [ "?discharging", { is-less-than = 0.3, value = "%power-supply-charge" } ] priority = 90 cpu.energy-performance-bias = { if.is-energy-performance-bias-available = "power", then = "power" } cpu.energy-performance-preference = { if.is-energy-performance-preference-available = "power", then = "power" } -cpu.frequency-mhz-maximum = { if = "?frequency-available", then = 800 } # More aggressive below critical threshold. cpu.governor = { if.is-governor-available = "powersave", then = "powersave" } cpu.turbo = { if = "?turbo-available", then = false } power.platform-profile = { if.is-platform-profile-available = "low-power", then = "low-power" } -# High performance mode for sustained high load. [[rule]] +name = "high-load-performance-sustainance" if.all = [ { is-more-than = 0.8, value = "%cpu-usage" }, { is-less-than = 30.0, value = "$cpu-idle-seconds" }, @@ -35,13 +34,13 @@ if.all = [ ] priority = 80 -cpu.energy-performance-bias = { if.is-energy-performance-bias-available = "performance", then = "performance" } -cpu.energy-performance-preference = { if.is-energy-performance-preference-available = "performance", then = "performance" } +cpu.energy-performance-bias = { if.all = [{ not.is-driver-loaded = "intel_pstate" }, { is-energy-performance-bias-available = "performance" }], then = "performance" } +cpu.energy-performance-preference = { if.all = [{ not.is-driver-loaded = "intel_pstate" }, { is-energy-performance-preference-available = "performance" }], then = "performance" } cpu.governor = { if.is-governor-available = "performance", then = "performance" } cpu.turbo = { if = "?turbo-available", then = true } -# Performance mode when not discharging. [[rule]] +name = "plugged-in-performance" if.all = [ { not = "?discharging" }, { is-more-than = 0.1, value = "%cpu-usage" }, @@ -49,13 +48,13 @@ if.all = [ ] priority = 70 -cpu.energy-performance-bias = { if.is-energy-performance-bias-available = "balance_performance", then = "balance_performance" } -cpu.energy-performance-preference = { if.is-energy-performance-preference-available = "performance", then = "performance" } +cpu.energy-performance-bias = { if.all = [{ not.is-driver-loaded = "intel_pstate" }, { is-energy-performance-bias-available = "balance_performance" }], then = "balance_performance" } +cpu.energy-performance-preference = { if.all = [{ not.is-driver-loaded = "intel_pstate" }, { is-energy-performance-preference-available = "performance" }], then = "performance" } cpu.governor = { if.is-governor-available = "performance", then = "performance" } cpu.turbo = { if = "?turbo-available", then = true } -# Moderate performance for medium load. [[rule]] +name = "moderate-load-balanced-performance" if.all = [ { is-more-than = 0.4, value = "%cpu-usage" }, { is-less-than = 0.8, value = "%cpu-usage" }, @@ -66,8 +65,8 @@ cpu.energy-performance-bias = { if.is-energy-performance-bias-available = cpu.energy-performance-preference = { if.is-energy-performance-preference-available = "balance_performance", then = "balance_performance" } cpu.governor = { if.is-governor-available = "schedutil", then = "schedutil" } -# Power saving during low activity. [[rule]] +name = "low-activity-power-saving" if.all = [ { is-less-than = 0.2, value = "%cpu-usage" }, { is-more-than = 60.0, value = "$cpu-idle-seconds" }, @@ -79,8 +78,8 @@ cpu.energy-performance-preference = { if.is-energy-performance-preference-availa cpu.governor = { if.is-governor-available = "powersave", then = "powersave" } cpu.turbo = { if = "?turbo-available", then = false } -# Extended idle power optimization. [[rule]] +name = "extended-idle-power-saving" if = { is-more-than = 300.0, value = "$cpu-idle-seconds" } priority = 40 @@ -90,8 +89,8 @@ cpu.frequency-mhz-maximum = { if = "?frequency-available", then = 1600 } cpu.governor = { if.is-governor-available = "powersave", then = "powersave" } cpu.turbo = { if = "?turbo-available", then = false } -# Battery conservation when discharging. [[rule]] +name = "discharging-battery-conservation" if.all = [ "?discharging", { is-less-than = 0.5, value = "%power-supply-charge" } ] priority = 30 @@ -102,8 +101,8 @@ cpu.governor = { if.is-governor-available = "powersave", th cpu.turbo = { if = "?turbo-available", then = false } power.platform-profile = { if.is-platform-profile-available = "low-power", then = "low-power" } -# General battery mode. [[rule]] +name = "battery-balanced" if = "?discharging" priority = 20 @@ -114,8 +113,8 @@ cpu.frequency-mhz-minimum = { if = "?frequency-available", then = 200 } cpu.governor = { if.is-governor-available = "powersave", then = "powersave" } cpu.turbo = { if = "?turbo-available", then = false } -# Balanced performance for general use. Default fallback rule. [[rule]] +name = "default-balanced" cpu.energy-performance-bias = { if.is-energy-performance-bias-available = "balance_performance", then = "balance_performance" } cpu.energy-performance-preference = { if.is-energy-performance-preference-available = "balance_performance", then = "balance_performance" } cpu.governor = { if.is-governor-available = "schedutil", then = "schedutil" } diff --git a/watt/cpu.rs b/watt/cpu.rs index e921ec9..facd28c 100644 --- a/watt/cpu.rs +++ b/watt/cpu.rs @@ -156,14 +156,14 @@ impl Cpu { } } - log::info!("detected {} CPUs", cpus.len()); + log::info!("detected {len} CPUs", len = cpus.len()); Ok(cpus) } /// Scan CPU, tuning local copy of settings. fn scan(&mut self, cache: &CpuScanCache) -> anyhow::Result<()> { - log::debug!("scanning CPU {}", self.number); + log::debug!("scanning CPU {number}", number = self.number); let Self { number, .. } = self; @@ -174,7 +174,7 @@ impl Cpu { self.has_cpufreq = fs::exists(format!("/sys/devices/system/cpu/cpu{number}/cpufreq")); - log::trace!("CPU {} has cpufreq: {}", self.number, self.has_cpufreq); + log::trace!("CPU {number} has cpufreq: {has_cpufreq}", number = self.number, has_cpufreq = self.has_cpufreq); if self.has_cpufreq { self.scan_governor()?; @@ -190,7 +190,7 @@ impl Cpu { } fn scan_governor(&mut self) -> anyhow::Result<()> { - log::trace!("scanning governor for CPU {}", self.number); + log::trace!("scanning governor for CPU {number}", number = self.number); let Self { number, .. } = *self; @@ -223,7 +223,7 @@ impl Cpu { } fn scan_frequency(&mut self) -> anyhow::Result<()> { - log::trace!("scanning frequency for CPU {}", self.number); + log::trace!("scanning frequency for CPU {number}", number = self.number); let Self { number, .. } = *self; @@ -248,7 +248,7 @@ impl Cpu { } fn scan_epp(&mut self) -> anyhow::Result<()> { - log::trace!("scanning EPP for CPU {}", self.number); + log::trace!("scanning EPP for CPU {number}", number = self.number); let Self { number, .. } = *self; @@ -280,7 +280,7 @@ impl Cpu { } fn scan_epb(&mut self) -> anyhow::Result<()> { - log::trace!("scanning EPB for CPU {}", self.number); + log::trace!("scanning EPB for CPU {number}", number = self.number); let Self { number, .. } = self; @@ -319,7 +319,7 @@ impl Cpu { } fn scan_stat(&mut self, cache: &CpuScanCache) -> anyhow::Result<()> { - log::trace!("scanning stat for CPU {}", self.number); + log::trace!("scanning stat for CPU {number}", number = self.number); // OnceCell::get_or_try_init is unstable. Cope: let stat = match cache.stat.get() { @@ -370,7 +370,7 @@ impl Cpu { } fn scan_info(&mut self, cache: &CpuScanCache) -> anyhow::Result<()> { - log::trace!("scanning info for CPU {}", self.number); + log::trace!("scanning info for CPU {number}", number = self.number); // OnceCell::get_or_try_init is unstable. Cope: let info = match cache.info.get() { @@ -459,7 +459,7 @@ impl Cpu { self.governor = Some(governor.to_owned()); - log::info!("CPU {} governor set to {}", self.number, governor); + log::info!("CPU {number} governor set to {governor}", number = self.number); Ok(()) } @@ -495,7 +495,7 @@ impl Cpu { self.epp = Some(epp.to_owned()); - log::info!("CPU {} EPP set to {}", self.number, epp); + log::info!("CPU {number} EPP set to {epp}", number = self.number); Ok(()) } @@ -530,7 +530,7 @@ impl Cpu { self.epb = Some(epb.to_owned()); - log::info!("CPU {} EPB set to {}", self.number, epb); + log::info!("CPU {number} EPB set to {epb}", number = self.number); Ok(()) } @@ -559,9 +559,8 @@ impl Cpu { })?; log::info!( - "CPU {} min frequency set to {} MHz", - self.number, - frequency_mhz + "CPU {number} min frequency set to {frequency_mhz} MHz", + number = self.number, ); Ok(()) @@ -585,8 +584,8 @@ impl Cpu { if new_frequency_mhz * 1000 < minimum_frequency_khz { bail!( "new software minimum frequency ({new_frequency_mhz} MHz) cannot be \ - lower than the hardware minimum frequency ({} MHz) for {self}", - minimum_frequency_khz / 1000, + lower than the hardware minimum frequency ({mhz} MHz) for {self}", + mhz = minimum_frequency_khz / 1000, ); } @@ -617,9 +616,8 @@ impl Cpu { })?; log::info!( - "CPU {} max frequency set to {} MHz", - self.number, - frequency_mhz + "CPU {number} max frequency set to {frequency_mhz} MHz", + number = self.number, ); Ok(()) @@ -645,8 +643,8 @@ impl Cpu { if new_frequency_mhz * 1000 > maximum_frequency_khz { bail!( "new software maximum frequency ({new_frequency_mhz} MHz) cannot be \ - higher than the hardware maximum frequency ({} MHz) for {self}", - maximum_frequency_khz / 1000, + higher than the hardware maximum frequency ({mhz} MHz) for {self}", + mhz = maximum_frequency_khz / 1000, ); } @@ -723,6 +721,10 @@ impl Cpu { .map(|x| x.map(|freq| freq / 1000)) } + pub fn is_intel_pstate() -> bool { + fs::exists("/sys/devices/system/cpu/intel_pstate") + } + pub fn turbo() -> anyhow::Result> { log::trace!("reading turbo boost status"); diff --git a/watt/fs.rs b/watt/fs.rs index eb4bb5b..93e0c35 100644 --- a/watt/fs.rs +++ b/watt/fs.rs @@ -25,7 +25,7 @@ pub fn read_dir(path: impl AsRef) -> anyhow::Result> { Err(error) => { Err(error).context(format!( "failed to read directory '{path}'", - path = path.display() + path = path.display(), )) }, } diff --git a/watt/lock.rs b/watt/lock.rs index e94820f..9bd8b4e 100644 --- a/watt/lock.rs +++ b/watt/lock.rs @@ -38,9 +38,13 @@ pub struct LockFileError { impl fmt::Display for LockFileError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "failed to acquire lock on {}", self.path.display())?; - if let Some(msg) = &self.message { - write!(f, ": {}", msg)?; + write!( + f, + "failed to acquire lock on {path}", + path = self.path.display(), + )?; + if let Some(message) = &self.message { + write!(f, ": {message}")?; } Ok(()) } @@ -69,28 +73,26 @@ impl LockFile { pub fn acquire(lock_path: &Path) -> Result { // Ensure parent directory exists with proper permissions - if let Some(parent) = lock_path.parent() { - if !parent.exists() { - fs::DirBuilder::new() - .mode(0o755) - .recursive(true) - .create(parent) - .map_err(|error| { - log::error!( - "failed to create lock directory {}: {}", - parent.display(), - error - ); - LockFileError { - path: lock_path.to_owned(), - message: Some(format!( - "cannot create directory {}: {}", - parent.display(), - error - )), - } - })?; - } + if let Some(parent) = lock_path.parent() + && !parent.exists() + { + fs::DirBuilder::new() + .mode(0o755) + .recursive(true) + .create(parent) + .map_err(|error| { + log::error!( + "failed to create lock directory {parent}: {error}", + parent = parent.display(), + ); + LockFileError { + path: lock_path.to_owned(), + message: Some(format!( + "cannot create directory {parent}: {error}", + parent = parent.display(), + )), + } + })?; } #[allow(clippy::suspicious_open_options)] @@ -102,9 +104,8 @@ impl LockFile { .open(lock_path) .map_err(|error| { log::error!( - "failed to open lock file at {}: {}", - lock_path.display(), - error + "failed to open lock file at {path}: {error}", + path = lock_path.display(), ); LockFileError { path: lock_path.to_owned(), @@ -116,12 +117,12 @@ impl LockFile { |(_, error)| { let message = if error == nix::errno::Errno::EWOULDBLOCK { log::error!( - "another watt instance is already running (lock held on {})", - lock_path.display() + "another watt instance is already running (lock held on {path})", + path = lock_path.display(), ); Some("another instance is running".to_string()) } else { - log::error!("failed to acquire lock: {}", error); + log::error!("failed to acquire lock: {error}"); Some(error.to_string()) }; diff --git a/watt/power_supply.rs b/watt/power_supply.rs index 4e6004b..3af8c3d 100644 --- a/watt/power_supply.rs +++ b/watt/power_supply.rs @@ -170,13 +170,13 @@ impl PowerSupply { power_supplies.push(power_supply); } - log::info!("detected {} power supplies", power_supplies.len()); + log::info!("detected {len} power supplies", len = power_supplies.len()); Ok(power_supplies) } fn scan(&mut self) -> anyhow::Result<()> { - log::trace!("scanning power supply '{}'", self.name); + log::trace!("scanning power supply '{name}'", name = self.name); if !self.path.exists() { bail!("{self} does not exist"); @@ -197,7 +197,7 @@ impl PowerSupply { self.is_from_peripheral = 'is_from_peripheral: { let name_lower = self.name.to_lowercase(); - log::trace!("power supply '{}' type: {}", self.name, self.type_); + log::trace!("power supply '{name}' type: {type_}", name = self.name, type_ = self.type_); // Common peripheral battery names. if name_lower.contains("mouse") @@ -293,9 +293,9 @@ impl PowerSupply { .copied(); log::debug!( - "power supply '{}' threshold config: {:?}", - self.name, - self.threshold_config + "power supply '{name}' threshold config: {threshold_config:?}", + name = self.name, + threshold_config = self.threshold_config, ); } diff --git a/watt/system.rs b/watt/system.rs index 8a5ef19..a57f8bb 100644 --- a/watt/system.rs +++ b/watt/system.rs @@ -406,7 +406,7 @@ impl System { }; log::debug!( "temperature content: {celsius} celsius", - celsius = temperature_mc as f64 / 1000.0 + celsius = temperature_mc as f64 / 1000.0, ); temperatures.insert(number, temperature_mc as f64 / 1000.0); @@ -830,6 +830,11 @@ pub fn run_daemon(config: config::DaemonConfig) -> anyhow::Result<()> { .context("`if` was not a boolean")?; if condition { + log::info!( + "rule '{name}' condition evaluated to true! evaluating members...", + name = rule.name, + ); + let cpu_some = { let (cpu_deltas_lo, cpu_turbo_lo) = rule.cpu.eval(&state)?; @@ -883,7 +888,7 @@ pub fn run_daemon(config: config::DaemonConfig) -> anyhow::Result<()> { .with_context(|| format!("failed to apply delta to {cpu}"))?; } - log::info!("applying CPU deltas to {} CPUs", cpu_deltas.len()); + log::info!("applying CPU deltas to {len} CPUs", len = cpu_deltas.len()); if let Some(turbo) = cpu_turbo { cpu::Cpu::set_turbo(turbo, cpu_deltas.keys().map(|arc| &**arc)) @@ -891,8 +896,8 @@ pub fn run_daemon(config: config::DaemonConfig) -> anyhow::Result<()> { } log::info!( - "applying power supply deltas to {} devices", - power_deltas.len() + "applying power supply deltas to {len} devices", + len = power_deltas.len(), ); for (power, delta) in power_deltas {