Skip to content

Commit 69bbd82

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 69bbd82

5 files changed

Lines changed: 43 additions & 1 deletion

File tree

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: 33 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,
@@ -20,6 +21,7 @@ use serde::{
2021
use crate::{
2122
cpu,
2223
power_supply,
24+
system,
2325
};
2426

2527
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
@@ -433,6 +435,11 @@ pub enum Expression {
433435
#[serde(with = "expression::cpu_usage_volatility")]
434436
CpuUsageVolatility,
435437

438+
CpuUsageSince {
439+
#[serde(rename = "cpu-usage-since")]
440+
duration: String,
441+
},
442+
436443
#[serde(with = "expression::cpu_temperature")]
437444
CpuTemperature,
438445

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

625632
pub cpus: &'peripherals HashSet<Arc<cpu::Cpu>>,
626633
pub power_supplies: &'peripherals HashSet<Arc<power_supply::PowerSupply>>,
634+
pub cpu_log: &'peripherals VecDeque<system::CpuLog>,
627635
}
628636

629637
#[derive(Debug, Clone, PartialEq)]
@@ -741,6 +749,27 @@ impl Expression {
741749
TurboAvailable => Boolean(state.turbo_available),
742750

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

10361065
let power_supplies = HashSet::new();
1066+
let cpu_log = VecDeque::new();
10371067

10381068
// Create an eval state with the base frequency
10391069
let state = EvalState {
@@ -1052,6 +1082,7 @@ mod tests {
10521082
context: EvalContext::Cpu(&cpu),
10531083
cpus: &cpus,
10541084
power_supplies: &power_supplies,
1085+
cpu_log: &cpu_log,
10551086
};
10561087

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

11211152
let power_supplies = HashSet::new();
1153+
let cpu_log = VecDeque::new();
11221154

11231155
let state = EvalState {
11241156
frequency_available: true,
@@ -1136,6 +1168,7 @@ mod tests {
11361168
context: EvalContext::Cpu(&cpu),
11371169
cpus: &cpus,
11381170
power_supplies: &power_supplies,
1171+
cpu_log: &cpu_log,
11391172
};
11401173

11411174
// 3333 * 0.65 = 2166.45

watt/system.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ impl System {
8686
let start = Instant::now();
8787

8888
// Preserve previous stats for delta calculation
89-
let previous_stats: std::collections::HashMap<u32, cpu::CpuStat> = self
89+
let previous_stats: HashMap<u32, cpu::CpuStat> = self
9090
.cpus
9191
.iter()
9292
.map(|cpu| (cpu.number, cpu.stat.clone()))

0 commit comments

Comments
 (0)