Skip to content

Commit b05628f

Browse files
committed
config: add cpu-usage-since expression for time-windowed CPU metrics
Allow rules to match on average CPU usage over a specified duration and enables detecting sustained load patterns rather than just instantaneous CPU usage. Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I9bea708d7ed5888a697e9a4ede7b8dfa6a6a6964
1 parent f08ce61 commit b05628f

File tree

4 files changed

+40
-0
lines changed

4 files changed

+40
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ env_logger = "0.11.8"
2323
log = "0.4.28"
2424
nix = { features = [ "fs" ], version = "0.31.1" }
2525
num_cpus = "1.17.0"
26+
humantime = "2.3.0"
2627
serde = { features = [ "derive" ], version = "1.0.228" }
2728
toml = "0.9.8"
2829
yansi = { features = [ "detect-env", "detect-tty" ], version = "1.0.1" }

watt/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ clap.workspace = true
1919
clap-verbosity-flag.workspace = true
2020
ctrlc.workspace = true
2121
env_logger.workspace = true
22+
humantime.workspace = true
2223
log.workspace = true
2324
nix.workspace = true
2425
num_cpus.workspace = true

watt/config.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{
22
collections::{
33
HashMap,
44
HashSet,
5+
VecDeque,
56
},
67
fs,
78
path::Path,
@@ -10,6 +11,7 @@ use std::{
1011

1112
use anyhow::{
1213
Context,
14+
anyhow,
1315
bail,
1416
};
1517
use serde::{
@@ -20,6 +22,7 @@ use serde::{
2022
use crate::{
2123
cpu,
2224
power_supply,
25+
system,
2326
};
2427

2528
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
@@ -433,6 +436,11 @@ pub enum Expression {
433436
#[serde(with = "expression::cpu_usage_volatility")]
434437
CpuUsageVolatility,
435438

439+
CpuUsageSince {
440+
#[serde(rename = "cpu-usage-since")]
441+
duration: String,
442+
},
443+
436444
#[serde(with = "expression::cpu_temperature")]
437445
CpuTemperature,
438446

@@ -624,6 +632,7 @@ pub struct EvalState<'peripherals, 'context> {
624632

625633
pub cpus: &'peripherals HashSet<Arc<cpu::Cpu>>,
626634
pub power_supplies: &'peripherals HashSet<Arc<power_supply::PowerSupply>>,
635+
pub cpu_log: &'peripherals VecDeque<system::CpuLog>,
627636
}
628637

629638
#[derive(Debug, Clone, PartialEq)]
@@ -741,6 +750,24 @@ impl Expression {
741750
TurboAvailable => Boolean(state.turbo_available),
742751

743752
CpuUsage => Number(state.cpu_usage),
753+
CpuUsageSince { duration } => {
754+
let duration = humantime::parse_duration(duration)
755+
.map_err(|e| anyhow!("failed to parse duration '{duration}': {e}"))?;
756+
let recent_logs: Vec<&system::CpuLog> = state
757+
.cpu_log
758+
.iter()
759+
.rev()
760+
.take_while(|log| log.at.elapsed() < duration)
761+
.collect();
762+
if recent_logs.len() < 2 {
763+
// Return None for insufficient data, consistent with volatility
764+
// expressions
765+
return Ok(None);
766+
}
767+
let avg = recent_logs.iter().map(|log| log.usage).sum::<f64>()
768+
/ recent_logs.len() as f64;
769+
Number(avg)
770+
},
744771
CpuUsageVolatility => Number(try_ok!(state.cpu_usage_volatility)),
745772
CpuTemperature => Number(try_ok!(state.cpu_temperature)),
746773
CpuTemperatureVolatility => {
@@ -1034,6 +1061,7 @@ mod tests {
10341061
cpus.insert(cpu.clone());
10351062

10361063
let power_supplies = HashSet::new();
1064+
let cpu_log = VecDeque::new();
10371065

10381066
// Create an eval state with the base frequency
10391067
let state = EvalState {
@@ -1052,6 +1080,7 @@ mod tests {
10521080
context: EvalContext::Cpu(&cpu),
10531081
cpus: &cpus,
10541082
power_supplies: &power_supplies,
1083+
cpu_log: &cpu_log,
10551084
};
10561085

10571086
// Create an expression like: { value = "$cpu-frequency-maximum", multiply = 0.65 }
@@ -1119,6 +1148,7 @@ mod tests {
11191148
cpus.insert(cpu.clone());
11201149

11211150
let power_supplies = HashSet::new();
1151+
let cpu_log = VecDeque::new();
11221152

11231153
let state = EvalState {
11241154
frequency_available: true,
@@ -1136,6 +1166,7 @@ mod tests {
11361166
context: EvalContext::Cpu(&cpu),
11371167
cpus: &cpus,
11381168
power_supplies: &power_supplies,
1169+
cpu_log: &cpu_log,
11391170
};
11401171

11411172
// 3333 * 0.65 = 2166.45

0 commit comments

Comments
 (0)