1pub(crate) mod arithmetic;
6pub(crate) mod context;
7pub(crate) mod fetch;
8pub(crate) mod metric_value;
9pub(crate) mod parse;
10pub(crate) mod variable;
11
12use fetch::{Fetcher, FileDataFetcher, SelectorString, TrialDataFetcher};
13use metric_value::{MetricValue, Problem};
14use regex::Regex;
15use serde::{Deserialize, Serialize};
16use std::cell::RefCell;
17use std::clone::Clone;
18use std::cmp::min;
19use std::collections::{HashMap, HashSet};
20use variable::VariableName;
21
22#[derive(Clone, Debug, PartialEq, Serialize)]
24pub(crate) enum Metric {
25 Selector(Vec<SelectorString>),
30 Eval(ExpressionContext),
32 #[cfg(test)]
34 Hardcoded(MetricValue),
35}
36
37impl std::fmt::Display for Metric {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Metric::Selector(s) => write!(f, "{:?}", s),
41 Metric::Eval(s) => write!(f, "{}", s),
42 #[cfg(test)]
43 Metric::Hardcoded(value) => write!(f, "{:?}", value),
44 }
45 }
46}
47
48#[derive(Clone, Debug, PartialEq, Serialize)]
51pub struct ValueSource {
52 pub(crate) metric: Metric,
53 pub cached_value: RefCell<Option<MetricValue>>,
54}
55
56impl ValueSource {
57 pub(crate) fn new(metric: Metric) -> Self {
58 Self { metric, cached_value: RefCell::new(None) }
59 }
60
61 pub(crate) fn try_from_expression_with_namespace(
62 expr: &str,
63 namespace: &str,
64 ) -> Result<Self, anyhow::Error> {
65 Ok(ValueSource::new(Metric::Eval(ExpressionContext::try_from_expression_with_namespace(
66 expr, namespace,
67 )?)))
68 }
69
70 #[cfg(test)]
71 pub(crate) fn try_from_expression_with_default_namespace(
72 expr: &str,
73 ) -> Result<Self, anyhow::Error> {
74 ValueSource::try_from_expression_with_namespace(expr, "")
75 }
76}
77
78impl std::fmt::Display for ValueSource {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 write!(f, "{:?}", self.metric)
81 }
82}
83
84pub type Metrics = HashMap<String, HashMap<String, ValueSource>>;
86
87pub struct MetricState<'a> {
94 pub metrics: &'a Metrics,
95 pub fetcher: Fetcher<'a>,
96 now: Option<i64>,
97 stack: RefCell<HashSet<String>>,
98}
99
100#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
101pub enum MathFunction {
102 Add,
103 Sub,
104 Mul,
105 FloatDiv,
106 IntDiv,
107 FloatDivChecked,
108 IntDivChecked,
109 Greater,
110 Less,
111 GreaterEq,
112 LessEq,
113 Max,
114 Min,
115 Abs,
116}
117
118#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
119pub enum Function {
120 Math(MathFunction),
121 Equals,
124 NotEq,
125 And,
126 Or,
127 Not,
128 KlogHas,
129 SyslogHas,
130 BootlogHas,
131 Missing,
132 UnhandledType,
133 Problem,
134 Annotation,
135 Lambda,
136 Apply,
137 Map,
138 Fold,
139 All,
140 Any,
141 Filter,
142 Count,
143 CountChildren,
144 CountProperties,
145 Nanos,
146 Micros,
147 Millis,
148 Seconds,
149 Minutes,
150 Hours,
151 Days,
152 Now,
153 OptionF,
154 StringMatches,
155 True,
156 False,
157}
158
159#[derive(Deserialize, Debug, Clone, Serialize)]
162pub struct Lambda {
163 parameters: Vec<String>,
164 body: ExpressionTree,
165}
166
167impl Lambda {
168 fn valid_parameters(parameters: &ExpressionTree) -> Result<Vec<String>, MetricValue> {
169 match parameters {
170 ExpressionTree::Vector(parameters) => parameters
171 .iter()
172 .map(|param| match param {
173 ExpressionTree::Variable(name) => {
174 if name.includes_namespace() {
175 Err(syntax_error("Namespaces not allowed in function params"))
176 } else {
177 Ok(name.original_name().to_string())
178 }
179 }
180 _ => Err(syntax_error("Function params must be valid identifier names")),
181 })
182 .collect::<Result<Vec<_>, _>>(),
183 _ => Err(syntax_error("Function params must be a vector of names")),
184 }
185 }
186
187 fn as_metric_value(definition: &[ExpressionTree]) -> MetricValue {
188 if definition.len() != 2 {
189 return syntax_error("Function needs two parameters, list of params and expression");
190 }
191 let parameters = match Self::valid_parameters(&definition[0]) {
192 Ok(names) => names,
193 Err(problem) => return problem,
194 };
195 let body = definition[1].clone();
196 MetricValue::Lambda(Box::new(Lambda { parameters, body }))
197 }
198}
199
200#[derive(Copy, Clone, Debug)]
202enum ShortCircuitBehavior {
203 True,
205 False,
207}
208
209struct MapFoldBoolArgs<'a> {
210 pub namespace: &'a str,
211 pub operands: &'a [ExpressionTree],
212 pub function_name: &'a str,
213 pub default_when_empty: bool,
214 pub function: &'a dyn Fn(bool, bool) -> bool,
215 pub short_circuit_behavior: ShortCircuitBehavior,
216}
217
218#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
222pub(crate) enum ExpressionTree {
223 Function(Function, Vec<ExpressionTree>),
227 Vector(Vec<ExpressionTree>),
228 Variable(VariableName),
229 Value(MetricValue),
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize)]
235pub(crate) struct ExpressionContext {
236 pub(crate) raw_expression: String,
237 pub(crate) parsed_expression: ExpressionTree,
238}
239
240impl ExpressionContext {
241 pub fn try_from_expression_with_namespace(
242 raw_expression: &str,
243 namespace: &str,
244 ) -> Result<Self, anyhow::Error> {
245 let parsed_expression = parse::parse_expression(raw_expression, namespace)?;
246 Ok(Self { raw_expression: raw_expression.to_string(), parsed_expression })
247 }
248
249 pub fn try_from_expression_with_default_namespace(
250 raw_expression: &str,
251 ) -> Result<Self, anyhow::Error> {
252 ExpressionContext::try_from_expression_with_namespace(
253 raw_expression,
254 "",
255 )
256 }
257}
258
259impl std::fmt::Display for ExpressionContext {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 write!(f, "{:?}", self.raw_expression)
262 }
263}
264
265fn unwrap_for_math(value: &MetricValue) -> &MetricValue {
270 match value {
271 MetricValue::Vector(v) if v.len() == 1 => &v[0],
272 v => v,
273 }
274}
275
276fn missing(message: impl AsRef<str>) -> MetricValue {
278 MetricValue::Problem(Problem::Missing(message.as_ref().to_string()))
279}
280
281fn syntax_error(message: impl AsRef<str>) -> MetricValue {
282 MetricValue::Problem(Problem::SyntaxError(message.as_ref().to_string()))
283}
284
285fn value_error(message: impl AsRef<str>) -> MetricValue {
286 MetricValue::Problem(Problem::ValueError(message.as_ref().to_string()))
287}
288
289fn internal_bug(message: impl AsRef<str>) -> MetricValue {
290 MetricValue::Problem(Problem::InternalBug(message.as_ref().to_string()))
291}
292
293fn unhandled_type(message: impl AsRef<str>) -> MetricValue {
294 MetricValue::Problem(Problem::UnhandledType(message.as_ref().to_string()))
295}
296
297fn evaluation_error(message: impl AsRef<str>) -> MetricValue {
298 MetricValue::Problem(Problem::EvaluationError(message.as_ref().to_string()))
299}
300
301pub fn safe_float_to_int(float: f64) -> Option<i64> {
302 if !float.is_finite() {
303 return None;
304 }
305 if float > i64::MAX as f64 {
306 return Some(i64::MAX);
307 }
308 if float < i64::MIN as f64 {
309 return Some(i64::MIN);
310 }
311 Some(float as i64)
312}
313
314fn first_usable_value(values: impl Iterator<Item = MetricValue>) -> MetricValue {
315 let mut found_empty = false;
316 for value in values {
317 match value {
318 MetricValue::Problem(Problem::Missing(_)) => {}
319 MetricValue::Vector(ref v)
320 if v.len() == 1 && matches!(v[0], MetricValue::Problem(Problem::Missing(_))) => {}
321 MetricValue::Vector(v) if v.is_empty() => found_empty = true,
322 value => return value,
323 }
324 }
325 if found_empty {
326 return MetricValue::Vector(vec![]);
327 }
328 missing("Every value was missing")
330}
331
332impl<'a> MetricState<'a> {
333 pub fn new(metrics: &'a Metrics, fetcher: Fetcher<'a>, now: Option<i64>) -> MetricState<'a> {
335 MetricState { metrics, fetcher, now, stack: RefCell::new(HashSet::new()) }
336 }
337
338 pub fn evaluate_all_metrics(&self) {
340 for (namespace, metrics) in self.metrics.iter() {
341 for (name, _value_source) in metrics.iter() {
342 self.evaluate_variable(namespace, &VariableName::new(name.clone()));
343 }
344 }
345 }
346
347 fn metric_value_for_trial(
352 &self,
353 fetcher: &TrialDataFetcher<'_>,
354 namespace: &str,
355 variable: &VariableName,
356 ) -> MetricValue {
357 let name = variable.original_name();
358 if fetcher.has_entry(name) {
359 return fetcher.fetch(name);
360 }
361 if variable.includes_namespace() {
362 return syntax_error(format!(
363 "Name {} not in test values and refers outside the file",
364 name
365 ));
366 }
367 match self.metrics.get(namespace) {
368 None => internal_bug(format!("BUG! Bad namespace '{}'", namespace)),
369 Some(metric_map) => match metric_map.get(name) {
370 None => syntax_error(format!("Metric '{}' Not Found in '{}'", name, namespace)),
371 Some(value_source) => {
372 let resolved_value: MetricValue;
373 {
374 let cached_value_cell = value_source.cached_value.borrow();
375 match &*cached_value_cell {
376 None => {
377 resolved_value = match &value_source.metric {
378 Metric::Selector(_) => missing(format!(
379 "Selector {} can't be used in tests; please supply a value",
380 name
381 )),
382 Metric::Eval(expression) => {
383 self.evaluate(namespace, &expression.parsed_expression)
384 }
385 #[cfg(test)]
386 Metric::Hardcoded(value) => value.clone(),
387 };
388 }
389 Some(cached_value) => return cached_value.clone(),
390 }
391 }
392 let mut cached_value_cell = value_source.cached_value.borrow_mut();
393 *cached_value_cell = Some(resolved_value.clone());
394 resolved_value
395 }
396 },
397 }
398 }
399
400 fn metric_value_for_file(
403 &self,
404 fetcher: &FileDataFetcher<'_>,
405 namespace: &str,
406 name: &VariableName,
407 ) -> MetricValue {
408 if let Some((real_namespace, real_name)) = name.name_parts(namespace) {
409 match self.metrics.get(real_namespace) {
410 None => syntax_error(format!("Bad namespace '{}'", real_namespace)),
411 Some(metric_map) => match metric_map.get(real_name) {
412 None => syntax_error(format!(
413 "Metric '{}' Not Found in '{}'",
414 real_name, real_namespace
415 )),
416 Some(value_source) => {
417 if let Some(cached_value) = value_source.cached_value.borrow().as_ref() {
418 return cached_value.clone();
419 }
420 let resolved_value = match &value_source.metric {
421 Metric::Selector(selectors) => first_usable_value(
422 selectors.iter().map(|selector| fetcher.fetch(selector)),
423 ),
424 Metric::Eval(expression) => {
425 self.evaluate(real_namespace, &expression.parsed_expression)
426 }
427 #[cfg(test)]
428 Metric::Hardcoded(value) => value.clone(),
429 };
430 let mut cached_value_cell = value_source.cached_value.borrow_mut();
431 *cached_value_cell = Some(resolved_value.clone());
432 resolved_value
433 }
434 },
435 }
436 } else {
437 syntax_error(format!("Bad name '{}'", name.original_name()))
438 }
439 }
440
441 fn evaluate_variable(&self, namespace: &str, name: &VariableName) -> MetricValue {
443 if self.stack.borrow().contains(&name.full_name(namespace)) {
447 let _ = self.stack.replace(HashSet::new());
449 return evaluation_error(format!(
450 "Cycle encountered while evaluating variable {} in the expression",
451 name.name
452 ));
453 }
454 self.stack.borrow_mut().insert(name.full_name(namespace));
456
457 let value = match &self.fetcher {
458 Fetcher::FileData(fetcher) => self.metric_value_for_file(fetcher, namespace, name),
459 Fetcher::TrialData(fetcher) => self.metric_value_for_trial(fetcher, namespace, name),
460 };
461
462 self.stack.borrow_mut().remove(&name.full_name(namespace));
463 value
464 }
465
466 pub(crate) fn eval_action_metric(
468 &self,
469 namespace: &str,
470 value_source: &ValueSource,
471 ) -> MetricValue {
472 if let Some(cached_value) = &*value_source.cached_value.borrow() {
473 return cached_value.clone();
474 }
475
476 let resolved_value = match &value_source.metric {
477 Metric::Selector(_) => syntax_error("Selectors aren't allowed in action triggers"),
478 Metric::Eval(expression) => {
479 unwrap_for_math(&self.evaluate(namespace, &expression.parsed_expression)).clone()
480 }
481 #[cfg(test)]
482 Metric::Hardcoded(value) => value.clone(),
483 };
484
485 let mut cached_value_cell = value_source.cached_value.borrow_mut();
486 *cached_value_cell = Some(resolved_value.clone());
487 resolved_value
488 }
489
490 #[cfg(test)]
491 fn evaluate_value(&self, namespace: &str, expression: &str) -> MetricValue {
492 match parse::parse_expression(expression, namespace) {
493 Ok(expr) => self.evaluate(namespace, &expr),
494 Err(e) => syntax_error(format!("Expression parse error\n{}", e)),
495 }
496 }
497
498 pub(crate) fn evaluate_math(expr: &str) -> MetricValue {
500 let tree = match ExpressionContext::try_from_expression_with_default_namespace(expr) {
501 Ok(expr_context) => expr_context.parsed_expression,
502 Err(err) => return syntax_error(format!("Failed to parse '{}': {}", expr, err)),
503 };
504 Self::evaluate_const_expression(&tree)
505 }
506
507 pub(crate) fn evaluate_const_expression(tree: &ExpressionTree) -> MetricValue {
508 let values = HashMap::new();
509 let fetcher = Fetcher::TrialData(TrialDataFetcher::new(&values));
510 let files = HashMap::new();
511 let metric_state = MetricState::new(&files, fetcher, None);
512 metric_state.evaluate("", tree)
513 }
514
515 #[cfg(test)]
516 pub(crate) fn evaluate_expression(&self, e: &ExpressionTree) -> MetricValue {
517 self.evaluate("", e)
518 }
519
520 fn evaluate_function(
521 &self,
522 namespace: &str,
523 function: &Function,
524 operands: &[ExpressionTree],
525 ) -> MetricValue {
526 match function {
527 Function::Math(operation) => arithmetic::calculate(
528 operation,
529 &operands.iter().map(|o| self.evaluate(namespace, o)).collect::<Vec<MetricValue>>(),
530 ),
531 Function::Equals => self.apply_boolean_function(namespace, &|a, b| a == b, operands),
532 Function::NotEq => self.apply_boolean_function(namespace, &|a, b| a != b, operands),
533 Function::And => {
534 self.fold_bool(namespace, &|a, b| a && b, operands, ShortCircuitBehavior::False)
535 }
536 Function::Or => {
537 self.fold_bool(namespace, &|a, b| a || b, operands, ShortCircuitBehavior::True)
538 }
539 Function::Not => self.not_bool(namespace, operands),
540 Function::KlogHas | Function::SyslogHas | Function::BootlogHas => {
541 self.log_contains(function, namespace, operands)
542 }
543 Function::Missing => self.is_missing(namespace, operands),
544 Function::UnhandledType => self.is_unhandled_type(namespace, operands),
545 Function::Problem => self.is_problem(namespace, operands),
546 Function::Annotation => self.annotation(namespace, operands),
547 Function::Lambda => Lambda::as_metric_value(operands),
548 Function::Apply => self.apply(namespace, operands),
549 Function::Map => self.map(namespace, operands),
550 Function::Fold => self.fold(namespace, operands),
551 Function::All => self.map_fold_bool(MapFoldBoolArgs {
552 namespace,
553 operands,
554 function_name: "All",
555 default_when_empty: true,
556 function: &|a, b| a && b,
557 short_circuit_behavior: ShortCircuitBehavior::False,
558 }),
559 Function::Any => self.map_fold_bool(MapFoldBoolArgs {
560 namespace,
561 operands,
562 function_name: "Any",
563 default_when_empty: false,
564 function: &|a, b| a || b,
565 short_circuit_behavior: ShortCircuitBehavior::True,
566 }),
567 Function::Filter => self.filter(namespace, operands),
568 Function::Count | Function::CountProperties => {
569 self.count_properties(namespace, operands)
570 }
571 Function::CountChildren => self.count_children(namespace, operands),
572 Function::Nanos => self.time(namespace, operands, 1),
573 Function::Micros => self.time(namespace, operands, 1_000),
574 Function::Millis => self.time(namespace, operands, 1_000_000),
575 Function::Seconds => self.time(namespace, operands, 1_000_000_000),
576 Function::Minutes => self.time(namespace, operands, 1_000_000_000 * 60),
577 Function::Hours => self.time(namespace, operands, 1_000_000_000 * 60 * 60),
578 Function::Days => self.time(namespace, operands, 1_000_000_000 * 60 * 60 * 24),
579 Function::Now => self.now(operands),
580 Function::OptionF => self.option(namespace, operands),
581 Function::StringMatches => self.regex(namespace, operands),
582 Function::True => self.boolean(operands, true),
583 Function::False => self.boolean(operands, false),
584 }
585 }
586
587 fn regex(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
588 if operands.len() != 2 {
589 return syntax_error(
590 "StringMatches(metric, regex) needs one string metric and one string regex",
591 );
592 }
593
594 let (value, regex) = match (
595 self.evaluate(namespace, &operands[0]),
596 self.evaluate(namespace, &operands[1]),
597 ) {
598 (MetricValue::String(value), MetricValue::String(regex)) => (value, regex),
599 _ => {
600 return syntax_error("Arguments to StringMatches must be strings");
601 }
602 };
603
604 let regex = match Regex::new(®ex) {
605 Ok(v) => v,
606 Err(_) => {
607 return syntax_error(format!("Could not parse `{}` as regex", regex));
608 }
609 };
610
611 MetricValue::Bool(regex.is_match(&value))
612 }
613
614 fn option(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
615 first_usable_value(operands.iter().map(|expression| self.evaluate(namespace, expression)))
616 }
617
618 fn now(&self, operands: &'a [ExpressionTree]) -> MetricValue {
619 if !operands.is_empty() {
620 return syntax_error("Now() requires no operands.");
621 }
622 match self.now {
623 Some(time) => MetricValue::Int(time),
624 None => missing("No valid time available"),
625 }
626 }
627
628 fn apply_lambda(&self, namespace: &str, lambda: &Lambda, args: &[&MetricValue]) -> MetricValue {
629 fn substitute_all(
630 expressions: &[ExpressionTree],
631 bindings: &HashMap<&str, &MetricValue>,
632 ) -> Vec<ExpressionTree> {
633 expressions.iter().map(|e| substitute(e, bindings)).collect::<Vec<_>>()
634 }
635
636 fn substitute(
637 expression: &ExpressionTree,
638 bindings: &HashMap<&str, &MetricValue>,
639 ) -> ExpressionTree {
640 match expression {
641 ExpressionTree::Function(function, expressions) => ExpressionTree::Function(
642 function.clone(),
643 substitute_all(expressions, bindings),
644 ),
645 ExpressionTree::Vector(expressions) => {
646 ExpressionTree::Vector(substitute_all(expressions, bindings))
647 }
648 ExpressionTree::Variable(name) => {
649 let original_name = name.original_name();
650 if let Some(value) = bindings.get(original_name) {
651 ExpressionTree::Value((*value).clone())
652 } else {
653 ExpressionTree::Variable(name.clone())
654 }
655 }
656 ExpressionTree::Value(value) => ExpressionTree::Value(value.clone()),
657 }
658 }
659
660 let parameters = &lambda.parameters;
661 if parameters.len() != args.len() {
662 return syntax_error(format!(
663 "Function has {} parameters and needs {} arguments, but has {}.",
664 parameters.len(),
665 parameters.len(),
666 args.len()
667 ));
668 }
669 let mut bindings = HashMap::new();
670 for (name, value) in parameters.iter().zip(args.iter()) {
671 bindings.insert(name as &str, *value);
672 }
673 let expression = substitute(&lambda.body, &bindings);
674 self.evaluate(namespace, &expression)
675 }
676
677 fn unpack_lambda(
678 &self,
679 namespace: &str,
680 operands: &'a [ExpressionTree],
681 function_name: &str,
682 ) -> Result<(Box<Lambda>, Vec<MetricValue>), MetricValue> {
683 if operands.is_empty() {
684 return Err(syntax_error(format!(
685 "{} needs a function in its first argument",
686 function_name
687 )));
688 }
689 let lambda = match self.evaluate(namespace, &operands[0]) {
690 MetricValue::Lambda(lambda) => lambda,
691 _ => {
692 return Err(syntax_error(format!(
693 "{} needs a function in its first argument",
694 function_name
695 )))
696 }
697 };
698 let arguments =
699 operands[1..].iter().map(|expr| self.evaluate(namespace, expr)).collect::<Vec<_>>();
700 Ok((lambda, arguments))
701 }
702
703 fn apply(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
705 let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Apply") {
706 Ok((lambda, arguments)) => (lambda, arguments),
707 Err(problem) => return problem,
708 };
709 if arguments.is_empty() {
710 return syntax_error("Apply needs a second argument (a vector).");
711 }
712 if arguments.len() > 1 {
713 return syntax_error("Apply only accepts one vector argument.");
714 }
715
716 match &arguments[0] {
717 MetricValue::Vector(apply_args) => {
718 self.apply_lambda(namespace, &lambda, &apply_args.iter().collect::<Vec<_>>())
719 }
720 _ => value_error("Apply only accepts a vector as an argument."),
721 }
722 }
723
724 fn map(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
726 let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Map") {
727 Ok((lambda, arguments)) => (lambda, arguments),
728 Err(problem) => return problem,
729 };
730 let vector_args = arguments
732 .iter()
733 .filter(|item| matches!(item, MetricValue::Vector(_)))
734 .collect::<Vec<_>>();
735 let result_length = match vector_args.len() {
736 0 => 0,
737 _ => {
738 let start = match vector_args[0] {
739 MetricValue::Vector(vec) => vec.len(),
740 _ => unreachable!(),
741 };
742 vector_args.iter().fold(start, |accum, item| {
743 min(
744 accum,
745 match item {
746 MetricValue::Vector(items) => items.len(),
747 _ => 0,
748 },
749 )
750 })
751 }
752 };
753 let mut result = Vec::new();
754 for index in 0..result_length {
755 let call_args = arguments
756 .iter()
757 .map(|arg| match arg {
758 MetricValue::Vector(vec) => &vec[index],
759 other => other,
760 })
761 .collect::<Vec<_>>();
762 result.push(self.apply_lambda(namespace, &lambda, &call_args));
763 }
764 MetricValue::Vector(result)
765 }
766
767 fn fold(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
769 let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Fold") {
770 Ok((lambda, arguments)) => (lambda, arguments),
771 Err(problem) => return problem,
772 };
773 if arguments.is_empty() {
774 return syntax_error("Fold needs a second argument, a vector");
775 }
776 let vector = match &arguments[0] {
777 MetricValue::Vector(items) => items,
778 _ => return value_error("Second argument of Fold must be a vector"),
779 };
780 let (first, rest) = match arguments.len() {
781 1 => match vector.split_first() {
782 Some(first_rest) => first_rest,
783 None => return value_error("Fold needs at least one value"),
784 },
785 2 => (&arguments[1], &vector[..]),
786 _ => return syntax_error("Fold needs (function, vec) or (function, vec, start)"),
787 };
788 let mut result = first.clone();
789 for item in rest {
790 result = self.apply_lambda(namespace, &lambda, &[&result, item]);
791 }
792 result
793 }
794
795 fn map_fold_bool(&self, args: MapFoldBoolArgs<'_>) -> MetricValue {
796 let (lambda, arguments) =
797 match self.unpack_lambda(args.namespace, args.operands, args.function_name) {
798 Ok((lambda, arguments)) => (lambda, arguments),
799 Err(problem) => return problem,
800 };
801 if arguments.len() != 1 {
802 return syntax_error(format!(
803 "{} needs two arguments (function, vector)",
804 args.function_name
805 ));
806 }
807 let MetricValue::Vector(v) = &arguments[0] else {
808 return syntax_error(format!(
809 "The second argument passed to {} must be a vector",
810 args.function_name
811 ));
812 };
813 if v.is_empty() {
815 return MetricValue::Bool(args.default_when_empty);
816 }
817 let operands: Vec<_> = v
818 .iter()
819 .map(|item| {
820 let metric_value = self.apply_lambda(args.namespace, &lambda, &[item]);
821 ExpressionTree::Value(metric_value)
822 })
823 .collect();
824 self.fold_bool(args.namespace, args.function, &operands, args.short_circuit_behavior)
825 }
826
827 fn filter(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
829 let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Filter") {
830 Ok((lambda, arguments)) => (lambda, arguments),
831 Err(problem) => return problem,
832 };
833 if arguments.len() != 1 {
834 return syntax_error("Filter needs (function, vector)");
835 }
836 let result = match &arguments[0] {
837 MetricValue::Vector(items) => items
838 .iter()
839 .filter_map(|item| match self.apply_lambda(namespace, &lambda, &[item]) {
840 MetricValue::Bool(true) => Some(item.clone()),
841 MetricValue::Bool(false) => None,
842 MetricValue::Problem(problem) => Some(MetricValue::Problem(problem)),
843 bad_type => Some(value_error(format!(
844 "Bad value {:?} from filter function should be Boolean",
845 bad_type
846 ))),
847 })
848 .collect(),
849 _ => return syntax_error("Filter second argument must be a vector"),
850 };
851 MetricValue::Vector(result)
852 }
853
854 fn count_properties(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
856 if operands.len() != 1 {
857 return syntax_error("CountProperties requires one argument, a vector");
858 }
859 match self.evaluate(namespace, &operands[0]) {
861 MetricValue::Vector(items) => {
862 let errors = items
863 .iter()
864 .filter_map(|item| match item {
865 MetricValue::Problem(problem) => Some(problem),
866 _ => None,
867 })
868 .collect::<Vec<_>>();
869 match errors.len() {
870 0 => MetricValue::Int(
871 items.iter().filter(|metric| !matches!(metric, MetricValue::Node)).count()
872 as i64,
873 ),
874 _ => MetricValue::Problem(Self::important_problem(errors)),
875 }
876 }
877 bad => value_error(format!("CountProperties only works on vectors, not {}", bad)),
878 }
879 }
880
881 fn count_children(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
883 if operands.len() != 1 {
884 return syntax_error("CountChildren requires one argument, a vector");
885 }
886 match self.evaluate(namespace, &operands[0]) {
888 MetricValue::Vector(items) => {
889 let errors = items
890 .iter()
891 .filter_map(|item| match item {
892 MetricValue::Problem(problem) => Some(problem),
893 _ => None,
894 })
895 .collect::<Vec<_>>();
896 match errors.len() {
897 0 => MetricValue::Int(
898 items.iter().filter(|metric| matches!(metric, MetricValue::Node)).count()
899 as i64,
900 ),
901 _ => MetricValue::Problem(Self::important_problem(errors)),
902 }
903 }
904 bad => value_error(format!("CountChildren only works on vectors, not {}", bad)),
905 }
906 }
907
908 fn boolean(&self, operands: &[ExpressionTree], value: bool) -> MetricValue {
909 if !operands.is_empty() {
910 return syntax_error("Boolean functions don't take any arguments");
911 }
912 MetricValue::Bool(value)
913 }
914
915 fn time(&self, namespace: &str, operands: &[ExpressionTree], multiplier: i64) -> MetricValue {
917 if operands.len() != 1 {
918 return syntax_error("Time conversion needs 1 numeric argument");
919 }
920 match self.evaluate(namespace, &operands[0]) {
921 MetricValue::Int(value) => MetricValue::Int(value * multiplier),
922 MetricValue::Float(value) => match safe_float_to_int(value * (multiplier as f64)) {
923 None => value_error(format!(
924 "Time conversion needs 1 numeric argument; couldn't convert {}",
925 value
926 )),
927 Some(value) => MetricValue::Int(value),
928 },
929 MetricValue::Problem(oops) => MetricValue::Problem(oops),
930 bad => value_error(format!("Time conversion needs 1 numeric argument, not {}", bad)),
931 }
932 }
933
934 fn evaluate(&self, namespace: &str, e: &ExpressionTree) -> MetricValue {
935 match e {
936 ExpressionTree::Function(f, operands) => self.evaluate_function(namespace, f, operands),
937 ExpressionTree::Variable(name) => self.evaluate_variable(namespace, name),
938 ExpressionTree::Value(value) => value.clone(),
939 ExpressionTree::Vector(values) => MetricValue::Vector(
940 values.iter().map(|value| self.evaluate(namespace, value)).collect(),
941 ),
942 }
943 }
944
945 fn annotation(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
946 if operands.len() != 1 {
947 return syntax_error("Annotation() needs 1 string argument");
948 }
949 match self.evaluate(namespace, &operands[0]) {
950 MetricValue::String(string) => match &self.fetcher {
951 Fetcher::TrialData(fetcher) => fetcher.annotations,
952 Fetcher::FileData(fetcher) => fetcher.annotations,
953 }
954 .fetch(&string),
955 _ => value_error("Annotation() needs a string argument"),
956 }
957 }
958
959 fn log_contains(
960 &self,
961 log_type: &Function,
962 namespace: &str,
963 operands: &[ExpressionTree],
964 ) -> MetricValue {
965 let log_data = match &self.fetcher {
966 Fetcher::TrialData(fetcher) => match log_type {
967 Function::KlogHas => fetcher.klog,
968 Function::SyslogHas => fetcher.syslog,
969 Function::BootlogHas => fetcher.bootlog,
970 _ => return internal_bug("Internal error, log_contains with non-log function"),
971 },
972 Fetcher::FileData(fetcher) => match log_type {
973 Function::KlogHas => fetcher.klog,
974 Function::SyslogHas => fetcher.syslog,
975 Function::BootlogHas => fetcher.bootlog,
976 _ => return internal_bug("Internal error, log_contains with non-log function"),
977 },
978 };
979 if operands.len() != 1 {
980 return syntax_error("Log matcher must use exactly 1 argument, an RE string.");
981 }
982 match self.evaluate(namespace, &operands[0]) {
983 MetricValue::String(re) => MetricValue::Bool(log_data.contains(&re)),
984 _ => value_error("Log matcher needs a string (RE)."),
985 }
986 }
987
988 fn important_problem(problems: Vec<&Problem>) -> Problem {
991 match problems.len() {
992 0 => Problem::InternalBug("Didn't find a Problem".to_string()),
993 len => {
994 let mut p = problems.clone();
995 p.sort_by_cached_key(|p| p.severity());
996 p[len - 1].clone()
997 }
998 }
999 }
1000
1001 fn apply_boolean_function(
1002 &self,
1003 namespace: &str,
1004 function: &dyn (Fn(&MetricValue, &MetricValue) -> bool),
1005 operands: &[ExpressionTree],
1006 ) -> MetricValue {
1007 if operands.len() != 2 {
1008 return syntax_error(format!("Bad arg list {:?} for binary operator", operands));
1009 }
1010 let operand_values =
1011 operands.iter().map(|operand| self.evaluate(namespace, operand)).collect::<Vec<_>>();
1012 let args = operand_values.iter().map(unwrap_for_math).collect::<Vec<_>>();
1013 match (args[0], args[1]) {
1014 (MetricValue::Problem(p), MetricValue::Problem(q)) => {
1016 MetricValue::Problem(Self::important_problem(vec![p, q]))
1017 }
1018 (MetricValue::Problem(p), _) => MetricValue::Problem(p.clone()),
1019 (_, MetricValue::Problem(p)) => MetricValue::Problem(p.clone()),
1020 _ => MetricValue::Bool(function(args[0], args[1])),
1021 }
1022 }
1023
1024 fn fold_bool(
1025 &self,
1026 namespace: &str,
1027 function: &dyn (Fn(bool, bool) -> bool),
1028 operands: &[ExpressionTree],
1029 short_circuit_behavior: ShortCircuitBehavior,
1030 ) -> MetricValue {
1031 if operands.is_empty() {
1032 return syntax_error("No operands in boolean expression");
1033 }
1034 let first = self.evaluate(namespace, &operands[0]);
1035 let mut result: bool = match unwrap_for_math(&first) {
1036 MetricValue::Bool(value) => *value,
1037 MetricValue::Problem(p) => return MetricValue::Problem(p.clone()),
1038 bad => return value_error(format!("{:?} is not boolean", bad)),
1039 };
1040 for operand in operands[1..].iter() {
1041 match (result, short_circuit_behavior) {
1042 (true, ShortCircuitBehavior::True) => {
1043 break;
1044 }
1045 (false, ShortCircuitBehavior::False) => {
1046 break;
1047 }
1048 _ => {}
1049 };
1050 let nth = self.evaluate(namespace, operand);
1051 result = match unwrap_for_math(&nth) {
1052 MetricValue::Bool(value) => function(result, *value),
1053 MetricValue::Problem(p) => return MetricValue::Problem(p.clone()),
1054 bad => return value_error(format!("{:?} is not boolean", bad)),
1055 }
1056 }
1057 MetricValue::Bool(result)
1058 }
1059
1060 fn not_bool(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1061 if operands.len() != 1 {
1062 return syntax_error(format!(
1063 "Wrong number of arguments ({}) for unary bool operator",
1064 operands.len()
1065 ));
1066 }
1067 match unwrap_for_math(&self.evaluate(namespace, &operands[0])) {
1068 MetricValue::Bool(true) => MetricValue::Bool(false),
1069 MetricValue::Bool(false) => MetricValue::Bool(true),
1070 MetricValue::Problem(p) => MetricValue::Problem(p.clone()),
1071 bad => value_error(format!("{:?} not boolean", bad)),
1072 }
1073 }
1074
1075 fn is_unhandled_type(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1080 if operands.len() != 1 {
1081 return syntax_error(format!(
1082 "Wrong number of operands for UnhandledType(): {}",
1083 operands.len()
1084 ));
1085 }
1086 let value = self.evaluate(namespace, &operands[0]);
1087 MetricValue::Bool(match value {
1088 MetricValue::Problem(Problem::UnhandledType(_)) => true,
1089
1090 MetricValue::Vector(contents) if contents.len() == 1 => match contents[0] {
1095 MetricValue::Problem(Problem::UnhandledType(_)) => true,
1096 MetricValue::Problem(ref problem) => return MetricValue::Problem(problem.clone()),
1097 _ => false,
1098 },
1099 MetricValue::Problem(problem) => return MetricValue::Problem(problem),
1100 _ => false,
1101 })
1102 }
1103
1104 fn is_missing(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1108 if operands.len() != 1 {
1109 return syntax_error(format!(
1110 "Wrong number of operands for Missing(): {}",
1111 operands.len()
1112 ));
1113 }
1114 let value = self.evaluate(namespace, &operands[0]);
1115 MetricValue::Bool(match value {
1116 MetricValue::Problem(Problem::Missing(_)) => true,
1117 MetricValue::Vector(contents) if contents.is_empty() => true,
1119 MetricValue::Vector(contents) if contents.len() == 1 => match contents[0] {
1120 MetricValue::Problem(Problem::Missing(_)) => true,
1121 MetricValue::Problem(ref problem) => return MetricValue::Problem(problem.clone()),
1122 _ => false,
1123 },
1124 MetricValue::Problem(problem) => return MetricValue::Problem(problem),
1125 _ => false,
1126 })
1127 }
1128
1129 fn is_problem(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1131 if operands.len() != 1 {
1132 return syntax_error(format!(
1133 "Wrong number of operands for Problem(): {}",
1134 operands.len()
1135 ));
1136 }
1137 let value = self.evaluate(namespace, &operands[0]);
1138 MetricValue::Bool(matches!(value, MetricValue::Problem(_)))
1139 }
1140}
1141
1142#[cfg(test)]
1149mod test {
1150 use super::*;
1151 use crate::config::{DiagnosticData, Source};
1152 use anyhow::Error;
1153 use std::sync::LazyLock;
1154
1155 #[macro_export]
1156 macro_rules! make_metrics {
1157 ({$($namespace: literal: {
1158 $(eval: {$($ke: literal: $ve: expr),+ $(,)?})?
1159 $(hardcoded: {$($kh: literal: $vh: expr),+ $(,)?})?
1160 $(select: {$($ks: literal: [$($vs: expr),+ $(,)?]),+ $(,)?})?
1161 }),+ $(,)?}) => {{
1162 [$((
1163 $namespace.to_string(),
1164 [
1165 $(
1166 $(
1167 ($ke.to_string(),
1168 ValueSource::try_from_expression_with_namespace($ve, $namespace)
1169 .expect("Unable to parse expression as value source.")),
1170 )+
1171 )?
1172 $(
1173 $(
1174 ($kh.to_string(),
1175 ValueSource::new(Metric::Hardcoded($vh))),
1176 )+
1177 )?
1178 $(
1179 $(
1180 ($ks.to_string(),
1181 ValueSource::new(Metric::Selector(
1182 [$($vs.clone(),)+].to_vec()))),
1183 )+
1184 )?
1185 ]
1186 .into_iter().collect::<HashMap<String, ValueSource>>()
1187 )),+]
1188 .into_iter()
1189 .collect::<HashMap<String, HashMap<String, ValueSource>>>()
1190 }};
1191}
1192
1193 #[macro_export]
1196 macro_rules! assert_problem {
1197 ($missing:expr, $message:expr) => {
1198 match $missing {
1199 MetricValue::Problem(problem) => assert_eq!(format!("{:?}", problem), $message),
1200 oops => {
1201 println!("Non problem type {:?}", oops);
1203 assert!(false, "Non-Problem type");
1204 }
1205 }
1206 };
1207 }
1208
1209 #[macro_export]
1210 macro_rules! assert_not_missing {
1211 ($not_missing:expr) => {
1212 match $not_missing {
1213 MetricValue::Problem(Problem::Missing(message)) => {
1214 assert!(false, "Expected not missing, was: {}", &message)
1215 }
1216 _ => {}
1217 }
1218 };
1219 }
1220
1221 static EMPTY_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1222 let s = r#"[]"#;
1223 vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1224 });
1225 static NO_PAYLOAD_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1226 let s = r#"[{"moniker": "abcd", "metadata": {}, "payload": null}]"#;
1227 vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1228 });
1229 static BAD_PAYLOAD_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1230 let s = r#"[{"moniker": "abcd", "metadata": {}, "payload": ["a", "b"]}]"#;
1231 vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1232 });
1233 static EMPTY_FILE_FETCHER: LazyLock<FileDataFetcher<'static>> =
1234 LazyLock::new(|| FileDataFetcher::new(&EMPTY_F));
1235 static EMPTY_TRIAL_FETCHER: LazyLock<TrialDataFetcher<'static>> =
1236 LazyLock::new(TrialDataFetcher::new_empty);
1237 static NO_PAYLOAD_FETCHER: LazyLock<FileDataFetcher<'static>> =
1238 LazyLock::new(|| FileDataFetcher::new(&NO_PAYLOAD_F));
1239 static BAD_PAYLOAD_FETCHER: LazyLock<FileDataFetcher<'static>> =
1240 LazyLock::new(|| FileDataFetcher::new(&BAD_PAYLOAD_F));
1241
1242 #[fuchsia::test]
1243 fn focus_on_important_errors() {
1244 let metrics = HashMap::new();
1245 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1246 let major = Problem::SyntaxError("Bad".to_string());
1247 let minor = Problem::Ignore(vec![Problem::Missing("Not a big deal".to_string())]);
1248 let major_arg = ExpressionTree::Value(MetricValue::Problem(major.clone()));
1249 let minor_arg = ExpressionTree::Value(MetricValue::Problem(minor));
1250 assert_problem!(
1251 state.apply_boolean_function(
1252 "",
1253 &|a, b| a == b,
1254 &[minor_arg.clone(), major_arg.clone()]
1255 ),
1256 "SyntaxError: Bad"
1257 );
1258 assert_problem!(
1259 state.apply_boolean_function("", &|a, b| a == b, &[major_arg, minor_arg]),
1260 "SyntaxError: Bad"
1261 );
1262 }
1263
1264 #[fuchsia::test]
1265 fn logs_work() -> Result<(), Error> {
1266 let syslog_text = "line 1\nline 2\nsyslog".to_string();
1267 let klog_text = "first line\nsecond line\nklog\n".to_string();
1268 let bootlog_text = "Yes there's a bootlog with one long line".to_string();
1269 let syslog = DiagnosticData::new("sys".to_string(), Source::Syslog, syslog_text)?;
1270 let klog = DiagnosticData::new("k".to_string(), Source::Klog, klog_text)?;
1271 let bootlog = DiagnosticData::new("boot".to_string(), Source::Bootlog, bootlog_text)?;
1272 let metrics = HashMap::new();
1273 let mut data = vec![klog, syslog, bootlog];
1274 let fetcher = FileDataFetcher::new(&data);
1275 let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1276 assert_eq!(state.evaluate_value("", r#"KlogHas("lin")"#), MetricValue::Bool(true));
1277 assert_eq!(state.evaluate_value("", r#"KlogHas("l.ne")"#), MetricValue::Bool(true));
1278 assert_eq!(state.evaluate_value("", r#"KlogHas("fi.*ne")"#), MetricValue::Bool(true));
1279 assert_eq!(state.evaluate_value("", r#"KlogHas("fi.*sec")"#), MetricValue::Bool(false));
1280 assert_eq!(state.evaluate_value("", r#"KlogHas("first line")"#), MetricValue::Bool(true));
1281 assert_eq!(
1283 state.evaluate_value("", r#"KlogHas("f(.)rst \bline")"#),
1284 MetricValue::Bool(true)
1285 );
1286 assert_eq!(
1288 state.evaluate_value("", r#"KlogHas("f(.)rst \bl\1ne")"#),
1289 MetricValue::Bool(false)
1290 );
1291 assert_eq!(state.evaluate_value("", r#"KlogHas("second line")"#), MetricValue::Bool(true));
1292 assert_eq!(
1293 state.evaluate_value("", "KlogHas(\"second line\n\")"),
1294 MetricValue::Bool(false)
1295 );
1296 assert_eq!(state.evaluate_value("", r#"KlogHas("klog")"#), MetricValue::Bool(true));
1297 assert_eq!(state.evaluate_value("", r#"KlogHas("line 2")"#), MetricValue::Bool(false));
1298 assert_eq!(state.evaluate_value("", r#"SyslogHas("line 2")"#), MetricValue::Bool(true));
1299 assert_eq!(state.evaluate_value("", r#"SyslogHas("syslog")"#), MetricValue::Bool(true));
1300 assert_eq!(state.evaluate_value("", r#"BootlogHas("bootlog")"#), MetricValue::Bool(true));
1301 assert_eq!(state.evaluate_value("", r#"BootlogHas("syslog")"#), MetricValue::Bool(false));
1302 data.pop();
1303 let fetcher = FileDataFetcher::new(&data);
1304 let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1305 assert_eq!(state.evaluate_value("", r#"SyslogHas("syslog")"#), MetricValue::Bool(true));
1306 assert_eq!(state.evaluate_value("", r#"BootlogHas("bootlog")"#), MetricValue::Bool(false));
1307 assert_eq!(state.evaluate_value("", r#"BootlogHas("syslog")"#), MetricValue::Bool(false));
1308 Ok(())
1309 }
1310
1311 #[fuchsia::test]
1312 fn annotations_work() -> Result<(), Error> {
1313 let annotation_text = r#"{ "build.board": "chromebook-x64", "answer": 42 }"#.to_string();
1314 let annotations =
1315 DiagnosticData::new("a".to_string(), Source::Annotations, annotation_text)?;
1316 let metrics = HashMap::new();
1317 let data = vec![annotations];
1318 let fetcher = FileDataFetcher::new(&data);
1319 let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1320 assert_eq!(
1321 state.evaluate_value("", "Annotation('build.board')"),
1322 MetricValue::String("chromebook-x64".to_string())
1323 );
1324 assert_eq!(state.evaluate_value("", "Annotation('answer')"), MetricValue::Int(42));
1325 assert_problem!(
1326 state.evaluate_value("", "Annotation('bogus')"),
1327 "Missing: Key 'bogus' not found in annotations"
1328 );
1329 assert_problem!(
1330 state.evaluate_value("", "Annotation('bogus', 'Double bogus')"),
1331 "SyntaxError: Annotation() needs 1 string argument"
1332 );
1333 assert_problem!(
1334 state.evaluate_value("", "Annotation(42)"),
1335 "ValueError: Annotation() needs a string argument"
1336 );
1337 Ok(())
1338 }
1339
1340 #[fuchsia::test]
1341 fn test_fetch_errors() {
1342 assert_eq!(0, NO_PAYLOAD_FETCHER.errors().len());
1344 assert_eq!(1, BAD_PAYLOAD_FETCHER.errors().len());
1345 }
1346
1347 #[fuchsia::test]
1352 fn test_evaluation() {
1353 let metrics = make_metrics!({
1354 "root":{
1355 eval: {"is42": "42", "isOk": "'OK'"}
1356 hardcoded: {"unhandled": unhandled_type("Unhandled")}
1357 }
1358 });
1359 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1360
1361 assert_eq!(state.evaluate_value("root", "is42"), MetricValue::Int(42));
1363
1364 assert_eq!(state.evaluate_value("root", "is42 + 1"), MetricValue::Int(43));
1366 assert_eq!(state.evaluate_value("root", "is42 - 1"), MetricValue::Int(41));
1367 assert_eq!(state.evaluate_value("root", "is42 * 2"), MetricValue::Int(84));
1368 assert_eq!(state.evaluate_value("root", "is42 / 4"), MetricValue::Float(10.5));
1370 assert_eq!(state.evaluate_value("root", "is42 // 4"), MetricValue::Int(10));
1371
1372 assert_eq!(
1374 state.evaluate_value("root", "is42 + 10 / 2 * 10 - 2 "),
1375 MetricValue::Float(90.0)
1376 );
1377 assert_eq!(state.evaluate_value("root", "is42 + 10 // 2 * 10 - 2 "), MetricValue::Int(90));
1378
1379 assert_eq!(
1381 state.evaluate_value("root", "And(is42 == 42, is42 < 100)"),
1382 MetricValue::Bool(true)
1383 );
1384 assert_eq!(
1385 state.evaluate_value("root", "And(is42 == 42, is42 > 100)"),
1386 MetricValue::Bool(false)
1387 );
1388 assert_eq!(
1389 state.evaluate_value("root", "Or(is42 == 42, is42 > 100)"),
1390 MetricValue::Bool(true)
1391 );
1392 assert_eq!(
1393 state.evaluate_value("root", "Or(is42 != 42, is42 < 100)"),
1394 MetricValue::Bool(true)
1395 );
1396 assert_eq!(
1397 state.evaluate_value("root", "Or(is42 != 42, is42 > 100)"),
1398 MetricValue::Bool(false)
1399 );
1400 assert_eq!(state.evaluate_value("root", "Not(is42 == 42)"), MetricValue::Bool(false));
1401
1402 assert_eq!(state.evaluate_value("root", "isOk"), MetricValue::String("OK".to_string()));
1404
1405 assert_problem!(
1407 state.evaluate_value("root", "missing"),
1408 "SyntaxError: Metric 'missing' Not Found in 'root'"
1409 );
1410
1411 assert_problem!(
1413 state.evaluate_value("root", "Or(is42 != 42, missing)"),
1414 "SyntaxError: Metric 'missing' Not Found in 'root'"
1415 );
1416 assert_eq!(
1417 state.evaluate_value("root", "Or(is42 == 42, missing)"),
1418 MetricValue::Bool(true)
1419 );
1420 assert_problem!(
1421 state.evaluate_value("root", "And(is42 == 42, missing)"),
1422 "SyntaxError: Metric 'missing' Not Found in 'root'"
1423 );
1424
1425 assert_eq!(
1426 state.evaluate_value("root", "And(is42 != 42, missing)"),
1427 MetricValue::Bool(false)
1428 );
1429
1430 assert_eq!(state.evaluate_value("root", "Missing(is42)"), MetricValue::Bool(false));
1432 assert_problem!(
1434 state.evaluate_value("root", "Missing(not_found)"),
1435 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1436 );
1437 assert_eq!(
1438 state.evaluate_value("root", "And(Not(Missing(is42)), is42 == 42)"),
1439 MetricValue::Bool(true)
1440 );
1441 assert_problem!(
1442 state.evaluate_value("root", "And(Not(Missing(not_found)), not_found == 'Hello')"),
1443 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1444 );
1445 assert_eq!(
1446 state.evaluate_value("root", "Or(Missing(is42), is42 < 42)"),
1447 MetricValue::Bool(false)
1448 );
1449 assert_problem!(
1450 state.evaluate_value("root", "Or(Missing(not_found), not_found == 'Hello')"),
1451 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1452 );
1453 assert_eq!(state.evaluate_value("root", "Missing([])"), MetricValue::Bool(true));
1454
1455 assert_eq!(state.evaluate_value("root", "UnhandledType(is42)"), MetricValue::Bool(false));
1457 assert_problem!(
1459 state.evaluate_value("root", "UnhandledType(not_found)"),
1460 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1461 );
1462 assert_eq!(
1463 state.evaluate_value("root", "And(Not(UnhandledType(is42)), is42 == 42)"),
1464 MetricValue::Bool(true)
1465 );
1466 assert_eq!(
1467 state.evaluate_value("root", "UnhandledType(unhandled)"),
1468 MetricValue::Bool(true)
1469 );
1470 assert_eq!(state.evaluate_value("root", "UnhandledType([])"), MetricValue::Bool(false));
1471
1472 assert_eq!(state.evaluate_value("root", "Problem(is42)"), MetricValue::Bool(false));
1474 assert_eq!(state.evaluate_value("root", "Problem(not_found)"), MetricValue::Bool(true));
1476 assert_eq!(
1477 state.evaluate_value("root", "And(Not(Problem(is42)), is42 == 42)"),
1478 MetricValue::Bool(true)
1479 );
1480 assert_eq!(
1481 state.evaluate_value("root", "And(Not(Problem(not_found)), not_found == 'Hello')"),
1482 MetricValue::Bool(false)
1483 );
1484 assert_eq!(
1485 state.evaluate_value("root", "Or(Problem(is42), is42 < 42)"),
1486 MetricValue::Bool(false)
1487 );
1488 assert_eq!(
1489 state.evaluate_value("root", "Or(Problem(not_found), not_found == 'Hello')"),
1490 MetricValue::Bool(true)
1491 );
1492 assert_problem!(
1493 state.evaluate_value("root", "Or(not_found == 'Hello', Problem(not_found))"),
1494 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1495 );
1496
1497 assert_eq!(
1499 state.evaluate_value("root", "[0==0]"),
1500 MetricValue::Vector(vec![MetricValue::Bool(true)])
1501 );
1502 assert_eq!(
1503 state.eval_action_metric(
1504 "root",
1505 &ValueSource::try_from_expression_with_namespace("[0==0]", "root").unwrap()
1506 ),
1507 MetricValue::Bool(true)
1508 );
1509
1510 assert_eq!(
1511 state.evaluate_value("root", "[0==0, 0==0]"),
1512 MetricValue::Vector(vec![MetricValue::Bool(true), MetricValue::Bool(true)])
1513 );
1514 assert_eq!(
1515 state.eval_action_metric(
1516 "root",
1517 &ValueSource::try_from_expression_with_namespace("[0==0, 0==0]", "root").unwrap()
1518 ),
1519 MetricValue::Vector(vec![MetricValue::Bool(true), MetricValue::Bool(true)])
1520 );
1521
1522 assert_eq!(
1524 state.eval_action_metric(
1525 "root",
1526 &ValueSource::try_from_expression_with_namespace(
1527 "StringMatches('abcd', '^a.c')",
1528 "root"
1529 )
1530 .unwrap()
1531 ),
1532 MetricValue::Bool(true)
1533 );
1534 assert_eq!(
1535 state.eval_action_metric(
1536 "root",
1537 &ValueSource::try_from_expression_with_namespace(
1538 "StringMatches('abcd', 'a.c$')",
1539 "root"
1540 )
1541 .unwrap()
1542 ),
1543 MetricValue::Bool(false)
1544 );
1545 assert_problem!(
1546 state.eval_action_metric(
1547 "root",
1548 &ValueSource::try_from_expression_with_namespace(
1549 "StringMatches('abcd', '[[')",
1550 "root"
1551 )
1552 .unwrap()
1553 ),
1554 "SyntaxError: Could not parse `[[` as regex"
1555 );
1556 }
1557
1558 #[fuchsia::test]
1560 fn test_caching_after_evaluation() {
1561 let metrics = make_metrics!({
1562 "root":{
1563 eval: {"is42": "42", "is43": "is42 + 1"}
1564 hardcoded: {"unhandled": unhandled_type("Unhandled")}
1565 }
1566 });
1567
1568 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1569 let trial_state =
1570 MetricState::new(&metrics, Fetcher::TrialData(EMPTY_TRIAL_FETCHER.clone()), None);
1571
1572 assert_eq!(
1574 *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1575 None
1576 );
1577 assert_eq!(
1578 *state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1579 None
1580 );
1581 assert_eq!(
1582 *state.metrics.get("root").unwrap().get("unhandled").unwrap().cached_value.borrow(),
1583 None
1584 );
1585 assert_eq!(
1586 *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1587 None
1588 );
1589 assert_eq!(
1590 *trial_state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1591 None
1592 );
1593 assert_eq!(
1594 *trial_state
1595 .metrics
1596 .get("root")
1597 .unwrap()
1598 .get("unhandled")
1599 .unwrap()
1600 .cached_value
1601 .borrow(),
1602 None
1603 );
1604
1605 assert_eq!(state.evaluate_value("root", "is42"), MetricValue::Int(42));
1608 assert_eq!(
1609 *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1610 Some(MetricValue::Int(42))
1611 );
1612 assert_eq!(trial_state.evaluate_value("root", "is42"), MetricValue::Int(42));
1613 assert_eq!(
1614 *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1615 Some(MetricValue::Int(42))
1616 );
1617
1618 assert_eq!(state.evaluate_value("root", "is43"), MetricValue::Int(43));
1622 assert_eq!(
1623 *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1624 Some(MetricValue::Int(42))
1625 );
1626 assert_eq!(
1627 *state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1628 Some(MetricValue::Int(43))
1629 );
1630 assert_eq!(trial_state.evaluate_value("root", "is43"), MetricValue::Int(43));
1631 assert_eq!(
1632 *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1633 Some(MetricValue::Int(42))
1634 );
1635 assert_eq!(
1636 *trial_state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1637 Some(MetricValue::Int(43))
1638 );
1639
1640 state.evaluate_value("root", "unhandled");
1642 assert_problem!(
1643 (*state.metrics.get("root").unwrap().get("unhandled").unwrap().cached_value.borrow())
1644 .as_ref()
1645 .unwrap(),
1646 "UnhandledType: Unhandled"
1647 );
1648 trial_state.evaluate_value("root", "unhandled");
1649 assert_problem!(
1650 (*trial_state
1651 .metrics
1652 .get("root")
1653 .unwrap()
1654 .get("unhandled")
1655 .unwrap()
1656 .cached_value
1657 .borrow())
1658 .as_ref()
1659 .unwrap(),
1660 "UnhandledType: Unhandled"
1661 );
1662 }
1663
1664 macro_rules! eval {
1665 ($e:expr) => {
1666 MetricState::evaluate_math($e)
1667 };
1668 }
1669
1670 #[fuchsia::test]
1672 fn test_missing_hacks() -> Result<(), Error> {
1673 assert_eq!(eval!("Missing(2>'a')"), MetricValue::Bool(true));
1674 assert_eq!(eval!("Missing([])"), MetricValue::Bool(true));
1675 assert_eq!(eval!("Missing([2>'a'])"), MetricValue::Bool(true));
1676 assert_eq!(eval!("Missing([2>'a', 2>'a'])"), MetricValue::Bool(false));
1677 assert_eq!(eval!("Missing([2>1])"), MetricValue::Bool(false));
1678 assert_eq!(eval!("Or(Missing(2>'a'), 2>'a')"), MetricValue::Bool(true));
1679 Ok(())
1680 }
1681
1682 #[fuchsia::test]
1683 fn test_ignores_in_expressions() {
1684 let dbz = "ValueError: Division by zero";
1685 assert_problem!(eval!("CountProperties([1, 2, 3/0])"), dbz);
1686 assert_problem!(eval!("CountProperties([1/0, 2, 3/0])"), dbz);
1687 assert_problem!(eval!("CountProperties([1/?0, 2, 3/?0])"), format!("Ignore: {}", dbz));
1688 assert_problem!(eval!("CountProperties([1/0, 2, 3/?0])"), dbz);
1689 assert_problem!(eval!("And(1 > 0, 3/0 > 0)"), dbz);
1691 assert_problem!(eval!("And(1/0 > 0, 3/0 > 0)"), dbz);
1692 assert_problem!(eval!("And(1/?0 > 0, 3/?0 > 0)"), format!("Ignore: {}", dbz));
1693 assert_problem!(eval!("And(1/?0 > 0, 3/0 > 0)"), format!("Ignore: {}", dbz));
1694 assert_problem!(eval!("1 == 3/0"), dbz);
1695 assert_problem!(eval!("1/0 == 3/0"), dbz);
1696 assert_problem!(eval!("1/?0 == 3/?0"), format!("Ignore: {}", dbz));
1697 assert_problem!(eval!("1/?0 == 3/0"), dbz);
1698 assert_problem!(eval!("1 + 3/0"), dbz);
1699 assert_problem!(eval!("1/0 + 3/0"), dbz);
1700 assert_problem!(eval!("1/?0 + 3/?0"), format!("Ignore: {}", dbz));
1701 assert_problem!(eval!("1/?0 + 3/0"), dbz);
1702 }
1703
1704 #[fuchsia::test]
1706 fn test_checked_divide_preserves_errors() {
1707 let san = "Missing: String(a) not numeric";
1708 assert_problem!(eval!("(1+'a')/1"), san);
1709 assert_problem!(eval!("(1+'a')/?1"), san);
1710 assert_problem!(eval!("1/?(1+'a')"), san);
1711 assert_problem!(eval!("(1+'a')/?(1+'a')"), san);
1712 assert_problem!(eval!("(1+'a')/0"), san);
1715 assert_problem!(eval!("(1+'a')/?0"), san);
1716 assert_problem!(eval!("(1+'a')//1"), san);
1717 assert_problem!(eval!("(1+'a')//?1"), san);
1718 assert_problem!(eval!("1//?(1+'a')"), san);
1719 assert_problem!(eval!("(1+'a')//?(1+'a')"), san);
1720 assert_problem!(eval!("(1+'a')//0"), san);
1721 assert_problem!(eval!("(1+'a')//?0"), san);
1722 }
1723
1724 #[fuchsia::test]
1725 fn test_time() -> Result<(), Error> {
1726 let metrics = Metrics::new();
1727 let files = vec![];
1728 let state_1234 =
1729 MetricState::new(&metrics, Fetcher::FileData(FileDataFetcher::new(&files)), Some(1234));
1730 let state_missing =
1731 MetricState::new(&metrics, Fetcher::FileData(FileDataFetcher::new(&files)), None);
1732 let now_expression = parse::parse_expression("Now()", "").unwrap();
1733 assert_problem!(MetricState::evaluate_math("Now()"), "Missing: No valid time available");
1734 assert_eq!(state_1234.evaluate_expression(&now_expression), MetricValue::Int(1234));
1735 assert_problem!(
1736 state_missing.evaluate_expression(&now_expression),
1737 "Missing: No valid time available"
1738 );
1739 Ok(())
1740 }
1741
1742 #[fuchsia::test]
1743 fn test_expression_context() {
1744 let selector_expr = "INSPECT:foo:bar:baz";
1746 assert_eq!(
1747 format!(
1748 "{:?}",
1749 ExpressionContext::try_from_expression_with_default_namespace(selector_expr)
1750 .err()
1751 .unwrap()
1752 ),
1753 "Expression Error: \n0: at line 1, in Eof:\nINSPECT:foo:bar:baz\n ^\n\n"
1754 );
1755
1756 let invalid_expr = "1 *";
1758 assert_eq!(
1759 format!(
1760 "{:?}",
1761 ExpressionContext::try_from_expression_with_default_namespace(invalid_expr)
1762 .err()
1763 .unwrap()
1764 ),
1765 concat!("Expression Error: \n0: at line 1, in Eof:\n1 *\n ^\n\n")
1766 );
1767
1768 let valid_expr = "42 + 1";
1770 let parsed_expression = parse::parse_expression(valid_expr, "").unwrap();
1771 assert_eq!(
1772 ExpressionContext::try_from_expression_with_default_namespace(valid_expr).unwrap(),
1773 ExpressionContext { raw_expression: valid_expr.to_string(), parsed_expression }
1774 );
1775 }
1776
1777 #[fuchsia::test]
1778 fn test_not_valid_cycle_same_variable() {
1779 let metrics = make_metrics!({
1780 "root":{
1781 eval: {
1782 "is42": "42",
1783 "shouldBe42": "is42 + 0",
1784 }
1785 },
1786 "n2":{
1787 eval: {
1788 "is42": "root::shouldBe42"
1789 }
1790 }
1791 });
1792 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1793
1794 assert_eq!(state.evaluate_value("n2", "is42"), MetricValue::Int(42));
1796 }
1797
1798 #[fuchsia::test]
1799 fn test_cycle_detected_correctly() {
1800 let metrics = make_metrics!({
1802 "root":{
1803 eval: {
1804 "is42": "42",
1805 "shouldBe62": "is42 + 1 + n2::is19",
1806 "b": "is42 + n2::a"
1807 }
1808 },
1809 "n2":{
1810 eval: {
1811 "is19": "19",
1812 "shouldBe44": "root::is42 + 2",
1813 "a": "is19 + c",
1814 "c": "root::b + root::is42"
1815 }
1816 }
1817 });
1818
1819 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1820
1821 assert_eq!(state.evaluate_value("root", "shouldBe62 + 1"), MetricValue::Int(63));
1823
1824 assert_problem!(
1826 state.evaluate_value("root", "n2::a"),
1827 "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1828 );
1829
1830 assert_eq!(state.evaluate_value("root", "n2::shouldBe44"), MetricValue::Int(44));
1832
1833 assert_problem!(
1835 state.evaluate_value("root", "b"),
1836 "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1837 );
1838
1839 assert_problem!(
1841 state.evaluate_value("root", "n2::c"),
1842 "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1843 );
1844
1845 assert_eq!(state.evaluate_value("root", "shouldBe62"), MetricValue::Int(62));
1847 }
1848 }