fluxbench_logic/
context.rs1use evalexpr::{
6 ContextWithMutableVariables, EvalexprError, HashMapContext, Value, eval_with_context,
7};
8use fxhash::FxHashMap;
9use thiserror::Error;
10
11#[derive(Debug, Error)]
13#[non_exhaustive]
14pub enum ContextError {
15 #[error("Unknown metric: {0}")]
16 UnknownMetric(String),
17
18 #[error("Evaluation error: {0}")]
19 EvalError(String),
20}
21
22#[derive(Debug, Clone)]
24pub struct MetricContext {
25 metrics: FxHashMap<String, f64>,
26}
27
28impl MetricContext {
29 pub fn new() -> Self {
31 Self {
32 metrics: FxHashMap::default(),
33 }
34 }
35
36 pub fn set(&mut self, name: impl Into<String>, value: f64) {
38 self.metrics.insert(name.into(), value);
39 }
40
41 pub fn get(&self, name: &str) -> Option<f64> {
43 self.metrics.get(name).copied()
44 }
45
46 pub fn evaluate(&self, expression: &str) -> Result<f64, ContextError> {
48 let mut ctx = HashMapContext::new();
49
50 for (name, value) in &self.metrics {
53 let safe_name = name.replace('@', "__");
54 ctx.set_value(safe_name, Value::Float(*value))
55 .map_err(|e: EvalexprError| ContextError::EvalError(e.to_string()))?;
56 }
57
58 let expression = expression.replace('@', "__");
60
61 let result = eval_with_context(&expression, &ctx)
63 .map_err(|e| ContextError::EvalError(e.to_string()))?;
64
65 match result {
67 Value::Float(f) => Ok(f),
68 Value::Int(i) => Ok(i as f64),
69 Value::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
70 other => Err(ContextError::EvalError(format!(
71 "Expected numeric result, got {:?}",
72 other
73 ))),
74 }
75 }
76
77 pub fn has(&self, name: &str) -> bool {
79 self.metrics.contains_key(name)
80 }
81
82 pub fn metric_names(&self) -> impl Iterator<Item = &String> {
84 self.metrics.keys()
85 }
86}
87
88impl Default for MetricContext {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_basic_evaluation() {
100 let mut ctx = MetricContext::new();
101 ctx.set("x", 10.0);
102 ctx.set("y", 5.0);
103
104 let result = ctx.evaluate("x + y").unwrap();
105 assert!((result - 15.0).abs() < f64::EPSILON);
106 }
107
108 #[test]
109 fn test_comparison() {
110 let mut ctx = MetricContext::new();
111 ctx.set("latency", 100.0);
112 ctx.set("threshold", 200.0);
113
114 let result = ctx.evaluate("latency < threshold").unwrap();
115 assert!((result - 1.0).abs() < f64::EPSILON); }
117
118 #[test]
119 fn test_complex_expression() {
120 let mut ctx = MetricContext::new();
121 ctx.set("raw", 150.0);
122 ctx.set("overhead", 50.0);
123
124 let result = ctx.evaluate("(raw - overhead) < 200").unwrap();
125 assert!((result - 1.0).abs() < f64::EPSILON);
126 }
127}