Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion watt/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ pub enum Expression {
#[serde(rename = "is-platform-profile-available")]
value: Box<Expression>,
},
IsDriverLoaded {
#[serde(rename = "is-driver-loaded")]
value: Box<Expression>,
},

#[serde(with = "expression::frequency_available")]
FrequencyAvailable,
Expand Down Expand Up @@ -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),

Expand Down Expand Up @@ -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(
Expand All @@ -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(),
Expand Down Expand Up @@ -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:#?}");

Expand Down
29 changes: 14 additions & 15 deletions watt/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -14,48 +14,47 @@ 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" },
{ is-less-than = 75.0, value = "$cpu-temperature" },
]
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" },
{ is-less-than = 80.0, value = "$cpu-temperature" },
]
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" },
Expand All @@ -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" },
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand All @@ -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" }
Expand Down
46 changes: 24 additions & 22 deletions watt/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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()?;
Expand All @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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(())
Expand All @@ -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,
);
}

Expand Down Expand Up @@ -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(())
Expand All @@ -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,
);
}

Expand Down Expand Up @@ -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<Option<bool>> {
log::trace!("reading turbo boost status");

Expand Down
2 changes: 1 addition & 1 deletion watt/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub fn read_dir(path: impl AsRef<Path>) -> anyhow::Result<Option<fs::ReadDir>> {
Err(error) => {
Err(error).context(format!(
"failed to read directory '{path}'",
path = path.display()
path = path.display(),
))
},
}
Expand Down
Loading