mirror of
https://github.com/RGBCube/superfreq
synced 2025-07-27 17:07:44 +00:00
expr: strict eval
This commit is contained in:
parent
917ed77255
commit
2c154cd589
2 changed files with 172 additions and 145 deletions
189
src/config.rs
189
src/config.rs
|
@ -195,10 +195,11 @@ mod expression {
|
|||
named!(cpu_temperature => "$cpu-temperature");
|
||||
named!(cpu_temperature_volatility => "$cpu-temperature-volatility");
|
||||
named!(cpu_idle_seconds => "$cpu-idle-seconds");
|
||||
|
||||
named!(power_supply_charge => "%power-supply-charge");
|
||||
named!(power_supply_discharge_rate => "%power-supply-discharge-rate");
|
||||
named!(charging => "?charging");
|
||||
named!(on_battery => "?on-battery");
|
||||
|
||||
named!(discharging => "?discharging");
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
|
@ -225,65 +226,80 @@ pub enum Expression {
|
|||
#[serde(with = "expression::power_supply_discharge_rate")]
|
||||
PowerSupplyDischargeRate,
|
||||
|
||||
#[serde(with = "expression::charging")]
|
||||
Charging,
|
||||
#[serde(with = "expression::on_battery")]
|
||||
OnBattery,
|
||||
#[serde(with = "expression::discharging")]
|
||||
Discharging,
|
||||
|
||||
Boolean(bool),
|
||||
|
||||
Number(f64),
|
||||
|
||||
Plus {
|
||||
value: Box<Expression>,
|
||||
plus: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "plus")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
Minus {
|
||||
value: Box<Expression>,
|
||||
minus: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "minus")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
Multiply {
|
||||
value: Box<Expression>,
|
||||
multiply: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "multiply")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
Power {
|
||||
value: Box<Expression>,
|
||||
power: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "power")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
Divide {
|
||||
value: Box<Expression>,
|
||||
divide: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "divide")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
LessThan {
|
||||
value: Box<Expression>,
|
||||
is_less_than: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "is-less-than")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
MoreThan {
|
||||
value: Box<Expression>,
|
||||
is_more_than: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "is-more-than")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Equal {
|
||||
value: Box<Expression>,
|
||||
is_equal: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "is-equal")]
|
||||
b: Box<Expression>,
|
||||
leeway: Box<Expression>,
|
||||
},
|
||||
|
||||
And {
|
||||
value: Box<Expression>,
|
||||
and: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "and")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
All {
|
||||
all: Vec<Expression>,
|
||||
},
|
||||
|
||||
Or {
|
||||
value: Box<Expression>,
|
||||
or: Box<Expression>,
|
||||
#[serde(rename = "value")]
|
||||
a: Box<Expression>,
|
||||
#[serde(rename = "or")]
|
||||
b: Box<Expression>,
|
||||
},
|
||||
Any {
|
||||
any: Vec<Expression>,
|
||||
|
@ -318,6 +334,121 @@ impl Expression {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EvalState {
|
||||
pub cpu_usage: f64,
|
||||
pub cpu_usage_volatility: Option<f64>,
|
||||
pub cpu_temperature: f64,
|
||||
pub cpu_temperature_volatility: Option<f64>,
|
||||
pub cpu_idle_seconds: f64,
|
||||
|
||||
pub power_supply_charge: f64,
|
||||
pub power_supply_discharge_rate: Option<f64>,
|
||||
|
||||
pub discharging: bool,
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn eval(&self, state: &EvalState) -> anyhow::Result<Option<Expression>> {
|
||||
use Expression::*;
|
||||
|
||||
macro_rules! try_ok {
|
||||
($expression:expr) => {
|
||||
match $expression {
|
||||
Some(value) => value,
|
||||
None => return Ok(None),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! eval {
|
||||
($expression:expr) => {
|
||||
try_ok!($expression.eval(state)?)
|
||||
};
|
||||
}
|
||||
|
||||
// [e8dax09]: This may be look inefficient, and it definitely isn't optimal,
|
||||
// but expressions in rules are usually so small that it doesn't matter or
|
||||
// make a perceiveable performance difference.
|
||||
//
|
||||
// We also want to be strict, instead of lazy in binary operations, because
|
||||
// we want to catch type errors immediately.
|
||||
//
|
||||
// FIXME: We currently cannot catch errors that will happen when propagating None.
|
||||
// You can have a type error go uncaught on first startup by using $cpu-usage-volatility
|
||||
// incorrectly, for example.
|
||||
Ok(Some(match self {
|
||||
CpuUsage => Number(state.cpu_usage),
|
||||
CpuUsageVolatility => Number(try_ok!(state.cpu_usage_volatility)),
|
||||
CpuTemperature => Number(state.cpu_temperature),
|
||||
CpuTemperatureVolatility => Number(try_ok!(state.cpu_temperature_volatility)),
|
||||
CpuIdleSeconds => Number(state.cpu_idle_seconds),
|
||||
|
||||
PowerSupplyCharge => Number(state.cpu_idle_seconds),
|
||||
PowerSupplyDischargeRate => Number(try_ok!(state.power_supply_discharge_rate)),
|
||||
|
||||
Discharging => Boolean(state.discharging),
|
||||
|
||||
literal @ (Boolean(_) | Number(_)) => literal.clone(),
|
||||
|
||||
Plus { a, b } => Number(eval!(a).as_number()? + eval!(b).as_number()?),
|
||||
Minus { a, b } => Number(eval!(a).as_number()? - eval!(b).as_number()?),
|
||||
Multiply { a, b } => Number(eval!(a).as_number()? * eval!(b).as_number()?),
|
||||
Power { a, b } => Number(eval!(a).as_number()?.powf(eval!(b).as_number()?)),
|
||||
Divide { a, b } => Number(eval!(a).as_number()? / eval!(b).as_number()?),
|
||||
|
||||
LessThan { a, b } => Boolean(eval!(a).as_number()? < eval!(b).as_number()?),
|
||||
MoreThan { a, b } => Boolean(eval!(a).as_number()? > eval!(b).as_number()?),
|
||||
Equal { a, b, leeway } => {
|
||||
let a = eval!(a).as_number()?;
|
||||
let b = eval!(b).as_number()?;
|
||||
let leeway = eval!(leeway).as_number()?;
|
||||
|
||||
let minimum = a - leeway;
|
||||
let maximum = a + leeway;
|
||||
|
||||
Boolean(minimum < b && b < maximum)
|
||||
}
|
||||
|
||||
And { a, b } => {
|
||||
let a = eval!(a).as_boolean()?;
|
||||
let b = eval!(b).as_boolean()?;
|
||||
|
||||
Boolean(a && b)
|
||||
}
|
||||
All { all } => {
|
||||
let mut result = true;
|
||||
|
||||
for value in all {
|
||||
let value = eval!(value).as_boolean()?;
|
||||
|
||||
result = result && value;
|
||||
}
|
||||
|
||||
Boolean(result)
|
||||
}
|
||||
Or { a, b } => {
|
||||
let a = eval!(a).as_boolean()?;
|
||||
let b = eval!(b).as_boolean()?;
|
||||
|
||||
Boolean(a || b)
|
||||
}
|
||||
Any { any } => {
|
||||
let mut result = false;
|
||||
|
||||
for value in any {
|
||||
let value = eval!(value).as_boolean()?;
|
||||
|
||||
result = result || value;
|
||||
}
|
||||
|
||||
Boolean(result)
|
||||
}
|
||||
Not { not } => Boolean(!eval!(not).as_boolean()?),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Rule {
|
||||
|
|
128
src/daemon.rs
128
src/daemon.rs
|
@ -306,121 +306,6 @@ impl Daemon {
|
|||
}
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
fn eval(&self, expression: &config::Expression) -> anyhow::Result<Option<config::Expression>> {
|
||||
use config::Expression::*;
|
||||
|
||||
macro_rules! try_ok {
|
||||
($expression:expr) => {
|
||||
match $expression {
|
||||
Some(value) => value,
|
||||
None => return Ok(None),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Some(match expression {
|
||||
CpuUsage => Number(self.cpu_log.back().unwrap().usage),
|
||||
CpuUsageVolatility => Number(try_ok!(self.cpu_volatility()).usage),
|
||||
CpuTemperature => Number(self.cpu_log.back().unwrap().temperature),
|
||||
CpuTemperatureVolatility => Number(try_ok!(self.cpu_volatility()).temperature),
|
||||
CpuIdleSeconds => Number(self.last_user_activity.elapsed().as_secs_f64()),
|
||||
PowerSupplyCharge => Number(self.power_supply_log.back().unwrap().charge),
|
||||
PowerSupplyDischargeRate => Number(try_ok!(self.power_supply_discharge_rate())),
|
||||
|
||||
Charging => Boolean(!self.discharging()),
|
||||
OnBattery => Boolean(self.discharging()),
|
||||
|
||||
literal @ Boolean(_) | literal @ Number(_) => literal.clone(),
|
||||
|
||||
Plus { value, plus } => Number(
|
||||
try_ok!(self.eval(value)?).as_number()? + try_ok!(self.eval(plus)?).as_number()?,
|
||||
),
|
||||
Minus { value, minus } => Number(
|
||||
try_ok!(self.eval(value)?).as_number()? - try_ok!(self.eval(minus)?).as_number()?,
|
||||
),
|
||||
Multiply { value, multiply } => Number(
|
||||
try_ok!(self.eval(value)?).as_number()?
|
||||
* try_ok!(self.eval(multiply)?).as_number()?,
|
||||
),
|
||||
Power { value, power } => Number(
|
||||
try_ok!(self.eval(value)?)
|
||||
.as_number()?
|
||||
.powf(try_ok!(self.eval(power)?).as_number()?),
|
||||
),
|
||||
Divide { value, divide } => Number(
|
||||
try_ok!(self.eval(value)?).as_number()?
|
||||
/ try_ok!(self.eval(divide)?).as_number()?,
|
||||
),
|
||||
|
||||
LessThan {
|
||||
value,
|
||||
is_less_than,
|
||||
} => Boolean(
|
||||
try_ok!(self.eval(value)?).as_number()?
|
||||
< try_ok!(self.eval(is_less_than)?).as_number()?,
|
||||
),
|
||||
MoreThan {
|
||||
value,
|
||||
is_more_than,
|
||||
} => Boolean(
|
||||
try_ok!(self.eval(value)?).as_number()?
|
||||
> try_ok!(self.eval(is_more_than)?).as_number()?,
|
||||
),
|
||||
Equal {
|
||||
value,
|
||||
is_equal,
|
||||
leeway,
|
||||
} => {
|
||||
let value = try_ok!(self.eval(value)?).as_number()?;
|
||||
let leeway = try_ok!(self.eval(leeway)?).as_number()?;
|
||||
|
||||
let is_equal = try_ok!(self.eval(is_equal)?).as_number()?;
|
||||
|
||||
let minimum = value - leeway;
|
||||
let maximum = value + leeway;
|
||||
|
||||
Boolean(minimum < is_equal && is_equal < maximum)
|
||||
}
|
||||
|
||||
And { value, and } => Boolean(
|
||||
try_ok!(self.eval(value)?).as_boolean()?
|
||||
&& try_ok!(self.eval(and)?).as_boolean()?,
|
||||
),
|
||||
All { all } => {
|
||||
let mut result = true;
|
||||
|
||||
for value in all {
|
||||
result = result && try_ok!(self.eval(value)?).as_boolean()?;
|
||||
|
||||
if !result {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Boolean(result)
|
||||
}
|
||||
Or { value, or } => Boolean(
|
||||
try_ok!(self.eval(value)?).as_boolean()? || try_ok!(self.eval(or)?).as_boolean()?,
|
||||
),
|
||||
Any { any } => {
|
||||
let mut result = false;
|
||||
|
||||
for value in any {
|
||||
result = result || try_ok!(self.eval(value)?).as_boolean()?;
|
||||
|
||||
if result {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Boolean(result)
|
||||
}
|
||||
Not { not } => Boolean(!try_ok!(self.eval(not)?).as_boolean()?),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(config: config::DaemonConfig) -> anyhow::Result<()> {
|
||||
assert!(config.rules.is_sorted_by_key(|rule| rule.priority));
|
||||
|
||||
|
@ -451,8 +336,19 @@ pub fn run(config: config::DaemonConfig) -> anyhow::Result<()> {
|
|||
|
||||
let sleep_until = Instant::now() + daemon.polling_interval();
|
||||
|
||||
let state = config::EvalState {
|
||||
cpu_usage: daemon.cpu_log.back().unwrap().usage,
|
||||
cpu_usage_volatility: daemon.cpu_volatility().map(|vol| vol.usage),
|
||||
cpu_temperature: daemon.cpu_log.back().unwrap().temperature,
|
||||
cpu_temperature_volatility: daemon.cpu_volatility().map(|vol| vol.temperature),
|
||||
cpu_idle_seconds: daemon.last_user_activity.elapsed().as_secs_f64(),
|
||||
power_supply_charge: daemon.power_supply_log.back().unwrap().charge,
|
||||
power_supply_discharge_rate: daemon.power_supply_discharge_rate(),
|
||||
discharging: daemon.discharging(),
|
||||
};
|
||||
|
||||
for rule in &config.rules {
|
||||
let Some(condition) = daemon.eval(&rule.if_)? else {
|
||||
let Some(condition) = rule.if_.eval(&state)? else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue