1use crate::config::ActionConfig;
6
7use super::config::DiagnosticData;
8use super::metrics::fetch::{Fetcher, FileDataFetcher};
9use super::metrics::metric_value::{MetricValue, Problem};
10use super::metrics::{
11 ExpressionContext, ExpressionTree, Function, Metric, MetricState, Metrics, ValueSource,
12};
13use super::plugins::{register_plugins, Plugin};
14use crate::{inspect_logger, metric_value_to_int};
15use anyhow::{bail, Error};
16use fidl_fuchsia_feedback::MAX_CRASH_SIGNATURE_LENGTH;
17use serde::{Deserialize, Serialize};
18use std::cell::RefCell;
19use std::collections::HashMap;
20
21pub struct ActionContext<'a> {
23 actions: &'a Actions,
24 metric_state: MetricState<'a>,
25 action_results: ActionResults,
26 plugins: Vec<Box<dyn Plugin>>,
27}
28
29impl<'a> ActionContext<'a> {
30 pub(crate) fn new(
31 metrics: &'a Metrics,
32 actions: &'a Actions,
33 diagnostic_data: &'a [DiagnosticData],
34 now: Option<i64>,
35 ) -> ActionContext<'a> {
36 let fetcher = FileDataFetcher::new(diagnostic_data);
37 let mut action_results = ActionResults::new();
38 fetcher.errors().iter().for_each(|e| {
39 action_results.errors.push(format!("[DEBUG: BAD DATA] {e}"));
40 });
41 ActionContext {
42 actions,
43 metric_state: MetricState::new(metrics, Fetcher::FileData(fetcher), now),
44 action_results,
45 plugins: register_plugins(),
46 }
47 }
48}
49
50#[derive(Clone, Debug)]
53pub struct ActionResults {
54 pub infos: Vec<String>,
55 pub warnings: Vec<String>,
56 pub errors: Vec<String>,
57 pub gauges: Vec<String>,
58 pub broken_gauges: Vec<String>,
59 pub snapshots: Vec<SnapshotTrigger>,
60 pub sort_gauges: bool,
61 pub verbose: bool,
62 pub sub_results: Vec<(String, Box<ActionResults>)>,
63}
64
65impl Default for ActionResults {
66 fn default() -> Self {
67 ActionResults {
68 infos: Vec::new(),
69 warnings: Vec::new(),
70 errors: Vec::new(),
71 gauges: Vec::new(),
72 broken_gauges: Vec::new(),
73 snapshots: Vec::new(),
74 sort_gauges: true,
75 verbose: false,
76 sub_results: Vec::new(),
77 }
78 }
79}
80
81impl ActionResults {
82 pub fn new() -> ActionResults {
83 ActionResults::default()
84 }
85
86 pub fn all_issues(&self) -> impl Iterator<Item = &str> {
87 self.infos.iter().chain(self.warnings.iter()).chain(self.errors.iter()).map(|s| s.as_ref())
88 }
89}
90
91#[derive(Debug, Clone, PartialEq)]
94pub struct SnapshotTrigger {
95 pub interval: i64, pub signature: String,
97}
98
99pub(crate) type Actions = HashMap<String, ActionsSchema>;
102
103pub(crate) type ActionsSchema = HashMap<String, Action>;
108
109#[derive(Clone, Debug, Serialize, PartialEq)]
111#[serde(tag = "type")]
112pub enum Action {
113 Alert(Alert),
114 Gauge(Gauge),
115 Snapshot(Snapshot),
116}
117
118impl Action {
119 pub fn from_config_with_namespace(
120 action_config: ActionConfig,
121 namespace: &str,
122 ) -> Result<Action, anyhow::Error> {
123 let action = match action_config {
124 ActionConfig::Alert { trigger, print, file_bug, tag, severity } => {
125 Action::Alert(Alert {
126 trigger: ValueSource::try_from_expression_with_namespace(&trigger, namespace)?,
127 print,
128 file_bug,
129 tag,
130 severity,
131 })
132 }
133 ActionConfig::Warning { trigger, print, file_bug, tag } => Action::Alert(Alert {
134 trigger: ValueSource::try_from_expression_with_namespace(&trigger, namespace)?,
135 print,
136 file_bug,
137 tag,
138 severity: Severity::Warning,
140 }),
141 ActionConfig::Gauge { value, format, tag } => Action::Gauge(Gauge {
142 value: ValueSource::try_from_expression_with_namespace(&value, namespace)?,
143 format,
144 tag,
145 }),
146 ActionConfig::Snapshot { trigger, repeat, signature } => Action::Snapshot(Snapshot {
147 trigger: ValueSource::try_from_expression_with_namespace(&trigger, namespace)?,
148 repeat: ValueSource::try_from_expression_with_namespace(&repeat, namespace)?,
149 signature,
150 }),
151 };
152 Ok(action)
153 }
154}
155
156#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
157pub enum Severity {
159 Info,
160 Warning,
161 Error,
162}
163
164pub(crate) fn validate_action(
165 action_name: &str,
166 action_config: &ActionConfig,
167 namespace: &str,
168) -> Result<(), Error> {
169 match action_config {
170 ActionConfig::Snapshot { signature, repeat, .. } => {
172 if signature.len() > MAX_CRASH_SIGNATURE_LENGTH as usize {
173 bail!("Signature too long in {}", action_name);
174 }
175 let repeat = ValueSource::try_from_expression_with_namespace(repeat, namespace)?;
176 match repeat.metric {
178 Metric::Eval(repeat_expression) => {
179 let repeat_value = MetricState::evaluate_const_expression(
180 &repeat_expression.parsed_expression,
181 );
182 if let MetricValue::Int(repeat_int) = repeat_value {
183 repeat.cached_value.borrow_mut().replace(MetricValue::Int(repeat_int));
184 } else {
185 bail!(
186 "Snapshot {} repeat expression '{}' must evaluate to int, not {:?}",
187 action_name,
188 repeat_expression.raw_expression,
189 repeat_value
190 );
191 }
192 }
193 _ => unreachable!("ValueSource::try_from() only produces an Eval"),
194 }
195 }
196 ActionConfig::Alert { severity, file_bug, .. } => {
198 if *severity == Severity::Error && file_bug.is_none() {
199 bail!("Error severity requires file_bug field in {}", action_name);
200 }
201 }
202 _ => {}
203 }
204 Ok(())
205}
206
207#[derive(Clone, Debug, Serialize, PartialEq)]
209pub struct Alert {
210 pub trigger: ValueSource,
212 pub print: String,
214 pub file_bug: Option<String>,
216 pub tag: Option<String>,
218 pub severity: Severity,
221}
222
223#[derive(Clone, Debug, Serialize, PartialEq)]
225pub struct Gauge {
226 pub value: ValueSource,
228 pub format: Option<String>,
230 pub tag: Option<String>,
232}
233
234#[derive(Clone, Debug, Serialize, PartialEq)]
236pub struct Snapshot {
237 pub trigger: ValueSource,
239 pub repeat: ValueSource,
241 pub signature: String,
243 }
245
246impl Gauge {
247 pub fn get_formatted_value(&self, metric_value: MetricValue) -> String {
248 match metric_value {
249 MetricValue::Float(value) => match &self.format {
250 Some(format) if format.as_str() == "percentage" => {
251 format!("{:.2}%", value * 100.0f64)
252 }
253 _ => format!("{value}"),
254 },
255 MetricValue::Int(value) => match &self.format {
256 Some(format) if format.as_str() == "percentage" => format!("{}%", value * 100),
257 _ => format!("{value}"),
258 },
259 MetricValue::Problem(Problem::Ignore(_)) => "N/A".to_string(),
260 value => format!("{value:?}"),
261 }
262 }
263}
264
265impl Action {
266 pub fn get_tag(&self) -> Option<String> {
267 match self {
268 Action::Alert(action) => action.tag.clone(),
269 Action::Gauge(action) => action.tag.clone(),
270 Action::Snapshot(_) => None,
271 }
272 }
273
274 pub fn new_synthetic_warning(print: String) -> Action {
276 let trigger_true = get_trigger_true();
277 Action::Alert(Alert {
278 trigger: trigger_true,
279 print,
280 file_bug: None,
281 tag: None,
282 severity: Severity::Warning,
283 })
284 }
285
286 pub fn new_synthetic_error(print: String, file_bug: String) -> Action {
287 let trigger_true = get_trigger_true();
288 Action::Alert(Alert {
289 trigger: trigger_true,
290 print,
291 file_bug: Some(file_bug),
292 tag: None,
293 severity: Severity::Error,
294 })
295 }
296
297 pub fn new_synthetic_string_gauge(
300 raw_value: String,
301 format: Option<String>,
302 tag: Option<String>,
303 ) -> Action {
304 let value = ValueSource {
305 metric: Metric::Eval(ExpressionContext {
306 raw_expression: format!("'{raw_value}'"),
307 parsed_expression: ExpressionTree::Value(MetricValue::String(raw_value.clone())),
308 }),
309 cached_value: RefCell::new(Some(MetricValue::String(raw_value))),
310 };
311 Action::Gauge(Gauge { value, format, tag })
312 }
313
314 pub(crate) fn has_reportable_issue(&self) -> bool {
317 let value = match self {
318 Action::Alert(alert) => &alert.trigger.cached_value,
319 Action::Snapshot(snapshot) => &snapshot.trigger.cached_value,
320 Action::Gauge(gauge) => &gauge.value.cached_value,
321 };
322 let reportable_on_true = match self {
323 Action::Gauge(_) => false,
324 Action::Snapshot(_) => true,
325 Action::Alert(alert) if alert.severity == Severity::Info => false,
326 Action::Alert(_) => true,
327 };
328 let result = match *value.borrow() {
329 Some(MetricValue::Bool(true)) if reportable_on_true => true,
330 Some(MetricValue::Problem(Problem::Missing(_))) => false,
331 Some(MetricValue::Problem(Problem::Ignore(_))) => false,
332 Some(MetricValue::Problem(_)) => true,
333 _ => false,
334 };
335 result
336 }
337}
338
339fn get_trigger_true() -> ValueSource {
340 ValueSource {
341 metric: Metric::Eval(ExpressionContext {
342 raw_expression: "True()".to_string(),
343 parsed_expression: ExpressionTree::Function(Function::True, vec![]),
344 }),
345 cached_value: RefCell::new(Some(MetricValue::Bool(true))),
346 }
347}
348
349pub type WarningVec = Vec<String>;
351
352impl ActionContext<'_> {
353 pub fn process(&mut self) -> &ActionResults {
355 if let Fetcher::FileData(file_data) = &self.metric_state.fetcher {
356 for plugin in &self.plugins {
357 self.action_results
358 .sub_results
359 .push((plugin.display_name().to_string(), Box::new(plugin.run(file_data))));
360 }
361 }
362
363 for (namespace, actions) in self.actions.iter() {
364 for (name, action) in actions.iter() {
365 match action {
366 Action::Alert(alert) => self.update_alerts(alert, namespace, name),
367 Action::Gauge(gauge) => self.update_gauges(gauge, namespace, name),
368 Action::Snapshot(snapshot) => self.update_snapshots(snapshot, namespace, name),
369 };
370 }
371 }
372
373 &self.action_results
374 }
375
376 pub(crate) fn set_verbose(&mut self, verbose: bool) {
377 self.action_results.verbose = verbose;
378 }
379
380 pub fn into_snapshots(mut self) -> (Vec<SnapshotTrigger>, WarningVec) {
382 for (namespace, actions) in self.actions.iter() {
383 for (name, action) in actions.iter() {
384 if let Action::Snapshot(snapshot) = action {
385 self.update_snapshots(snapshot, namespace, name)
386 }
387 }
388 }
389 let mut alerts = vec![];
390 alerts.extend(self.action_results.errors);
391 alerts.extend(self.action_results.warnings);
392 alerts.extend(self.action_results.infos);
393 (self.action_results.snapshots, alerts)
394 }
395
396 fn update_alerts(&mut self, action: &Alert, namespace: &String, name: &String) {
398 match self.metric_state.eval_action_metric(namespace, &action.trigger) {
399 MetricValue::Bool(true) => {
400 if let Some(file_bug) = &action.file_bug {
401 self.action_results
402 .errors
403 .push(format!("[BUG:{}] {}.", file_bug, action.print));
404 } else {
405 self.action_results.warnings.push(format!("[WARNING] {}.", action.print));
406 }
407 }
408 MetricValue::Bool(false) => (),
409 MetricValue::Problem(Problem::Ignore(_)) => (),
410 MetricValue::Problem(Problem::Missing(reason)) => {
411 self.action_results.infos.push(format!(
412 "[MISSING] In config '{namespace}::{name}': (need boolean trigger) {reason:?}",
413 ));
414 }
415 MetricValue::Problem(problem) => {
416 self.action_results.errors.push(format!(
417 "[ERROR] In config '{namespace}::{name}': (need boolean trigger): {problem:?}",
418 ));
419 }
420 other => {
421 self.action_results.errors.push(format!(
422 "[DEBUG: BAD CONFIG] Unexpected value type in config '{namespace}::{name}' (need boolean trigger): {other}",
423 ));
424 }
425 };
426 }
427
428 fn update_snapshots(&mut self, action: &Snapshot, namespace: &str, name: &str) {
430 match self.metric_state.eval_action_metric(namespace, &action.trigger) {
431 MetricValue::Bool(true) => {
432 let repeat_value = self.metric_state.eval_action_metric(namespace, &action.repeat);
433 let interval = metric_value_to_int(repeat_value);
434 match interval {
435 Ok(interval) => {
436 let signature = action.signature.clone();
437 let output = SnapshotTrigger { interval, signature };
438 self.action_results.snapshots.push(output);
439 }
440 Err(ref bad_type) => {
441 self.action_results.errors.push(format!(
442 "Bad interval in config '{namespace}::{name}': {bad_type:?}",
443 ));
444 inspect_logger::log_error(
445 "Bad interval",
446 namespace,
447 name,
448 &format!("{interval:?}"),
449 );
450 }
451 }
452 }
453 MetricValue::Bool(false) => (),
454 MetricValue::Problem(Problem::Ignore(_)) => (),
455 MetricValue::Problem(reason) => {
456 inspect_logger::log_warn(
457 "Snapshot trigger not boolean",
458 namespace,
459 name,
460 &format!("{reason:?}"),
461 );
462 self.action_results
463 .infos
464 .push(format!("[MISSING] In config '{namespace}::{name}': {reason:?}",));
465 }
466 other => {
467 inspect_logger::log_error(
468 "Bad config: Unexpected value type (need boolean)",
469 namespace,
470 name,
471 &format!("{other}"),
472 );
473 self.action_results.errors.push(format!(
474 "Bad config: Unexpected value type in config '{namespace}::{name}' (need boolean): {other}",
475 ));
476 }
477 };
478 }
479
480 fn update_gauges(&mut self, action: &Gauge, namespace: &str, name: &str) {
482 let value = self.metric_state.eval_action_metric(namespace, &action.value);
483 match value {
484 MetricValue::Problem(Problem::Ignore(_)) => {
485 self.action_results.broken_gauges.push(format!("{name}: N/A"));
486 }
487 MetricValue::Problem(problem) => {
488 self.action_results.broken_gauges.push(format!("{name}: {problem:?}"));
489 }
490 value => {
491 self.action_results.gauges.push(format!(
492 "{}: {}",
493 name,
494 action.get_formatted_value(value)
495 ));
496 }
497 }
498 }
499}
500
501#[cfg(test)]
502mod test {
503 use super::*;
504 use crate::config::Source;
505 use crate::make_metrics;
506
507 fn includes(values: &Vec<String>, substring: &str) -> bool {
509 for value in values {
510 if value.contains(substring) {
511 return true;
512 }
513 }
514 false
515 }
516
517 #[fuchsia::test]
518 fn actions_fire_correctly() {
519 let metrics = make_metrics!({
520 "file":{
521 eval: {
522 "true": "0 == 0",
523 "false": "0 == 1",
524 "true_array": "[0 == 0]",
525 "false_array": "[0 == 1]"
526 }
527 }
528 });
529 let mut actions = Actions::new();
530 let mut action_file = ActionsSchema::new();
531 action_file.insert(
532 "do_true".to_string(),
533 Action::Alert(Alert {
534 trigger: ValueSource::try_from_expression_with_namespace("true", "file").unwrap(),
535 print: "True was fired".to_string(),
536 file_bug: Some("Some>Monorail>Component".to_string()),
537 tag: None,
538 severity: Severity::Warning,
539 }),
540 );
541 action_file.insert(
542 "do_false".to_string(),
543 Action::Alert(Alert {
544 trigger: ValueSource::try_from_expression_with_namespace("false", "file").unwrap(),
545 print: "False was fired".to_string(),
546 file_bug: None,
547 tag: None,
548 severity: Severity::Warning,
549 }),
550 );
551 action_file.insert(
552 "do_true_array".to_string(),
553 Action::Alert(Alert {
554 trigger: ValueSource::try_from_expression_with_namespace("true_array", "file")
555 .unwrap(),
556 print: "True array was fired".to_string(),
557 file_bug: None,
558 tag: None,
559 severity: Severity::Warning,
560 }),
561 );
562 action_file.insert(
563 "do_false_array".to_string(),
564 Action::Alert(Alert {
565 trigger: ValueSource::try_from_expression_with_namespace("false_array", "file")
566 .unwrap(),
567 print: "False array was fired".to_string(),
568 file_bug: None,
569 tag: None,
570 severity: Severity::Warning,
571 }),
572 );
573
574 action_file.insert(
575 "do_operation".to_string(),
576 Action::Alert(Alert {
577 trigger: ValueSource::try_from_expression_with_namespace("0 < 10", "file").unwrap(),
578 print: "Inequality triggered".to_string(),
579 file_bug: None,
580 tag: None,
581 severity: Severity::Warning,
582 }),
583 );
584 actions.insert("file".to_string(), action_file);
585 let no_data = Vec::new();
586 let mut context = ActionContext::new(&metrics, &actions, &no_data, None);
587 let results = context.process();
588 assert!(includes(&results.errors, "[BUG:Some>Monorail>Component] True was fired."));
589 assert!(includes(&results.warnings, "[WARNING] Inequality triggered."));
590 assert!(includes(&results.warnings, "[WARNING] True array was fired"));
591 assert!(!includes(&results.warnings, "False was fired"));
592 assert!(!includes(&results.warnings, "False array was fired"));
593 }
594
595 #[fuchsia::test]
596 fn gauges_fire_correctly() {
597 let metrics = make_metrics!({
598 "file":{
599 eval: {
600 "gauge_f1": "2 / 5",
601 "gauge_f2": "4 / 5",
602 "gauge_f3": "6 / 5",
603 "gauge_i4": "9 // 2",
604 "gauge_i5": "11 // 2",
605 "gauge_i6": "13 // 2",
606 "gauge_b7": "2 == 2",
607 "gauge_b8": "2 > 2",
608 "gauge_s9": "'foo'"
609 }
610 }
611 });
612 let mut actions = Actions::new();
613 let mut action_file = ActionsSchema::new();
614 macro_rules! insert_gauge {
615 ($name:expr, $format:expr) => {
616 action_file.insert(
617 $name.to_string(),
618 Action::Gauge(Gauge {
619 value: ValueSource::try_from_expression_with_namespace($name, "file")
620 .unwrap(),
621 format: $format,
622 tag: None,
623 }),
624 );
625 };
626 }
627 insert_gauge!("gauge_f1", None);
628 insert_gauge!("gauge_f2", Some("percentage".to_string()));
629 insert_gauge!("gauge_f3", Some("unknown".to_string()));
630 insert_gauge!("gauge_i4", None);
631 insert_gauge!("gauge_i5", Some("percentage".to_string()));
632 insert_gauge!("gauge_i6", Some("unknown".to_string()));
633 insert_gauge!("gauge_b7", None);
634 insert_gauge!("gauge_b8", None);
635 insert_gauge!("gauge_s9", None);
636 actions.insert("file".to_string(), action_file);
637 let no_data = Vec::new();
638 let mut context = ActionContext::new(&metrics, &actions, &no_data, None);
639
640 let results = context.process();
641
642 assert!(includes(&results.gauges, "gauge_f1: 0.4"));
643 assert!(includes(&results.gauges, "gauge_f2: 80.00%"));
644 assert!(includes(&results.gauges, "gauge_f3: 1.2"));
645 assert!(includes(&results.gauges, "gauge_i4: 4"));
646 assert!(includes(&results.gauges, "gauge_i5: 500%"));
647 assert!(includes(&results.gauges, "gauge_i6: 6"));
648 assert!(includes(&results.gauges, "gauge_b7: Bool(true)"));
649 assert!(includes(&results.gauges, "gauge_b8: Bool(false)"));
650 assert!(includes(&results.gauges, "gauge_s9: String(\"foo\")"));
651 }
652
653 #[fuchsia::test]
654 fn action_context_errors() {
655 let metrics = Metrics::new();
656 let actions = Actions::new();
657 let data = vec![DiagnosticData::new(
658 "inspect.json".to_string(),
659 Source::Inspect,
660 r#"
661 [
662 {
663 "moniker": "abcd",
664 "metadata": {},
665 "payload": {"root": {"val": 10}}
666 },
667 {
668 "moniker": "abcd2",
669 "metadata": {},
670 "payload": ["a", "b"]
671 },
672 {
673 "moniker": "abcd3",
674 "metadata": {},
675 "payload": null
676 }
677 ]
678 "#
679 .to_string(),
680 )
681 .expect("create data")];
682 let action_context = ActionContext::new(&metrics, &actions, &data, None);
683 assert_eq!(
686 vec!["[DEBUG: BAD DATA] Unable to deserialize Inspect contents for abcd2 to node hierarchy"
687 .to_string()],
688 action_context.action_results.errors
689 );
690 }
691
692 #[fuchsia::test]
693 fn time_propagates_correctly() {
694 let metrics = Metrics::new();
695 let mut actions = Actions::new();
696 let mut action_file = ActionsSchema::new();
697 action_file.insert(
698 "time_1234".to_string(),
699 Action::Alert(Alert {
700 trigger: ValueSource::try_from_expression_with_namespace("Now() == 1234", "file")
701 .unwrap(),
702 print: "1234".to_string(),
703 tag: None,
704 file_bug: None,
705 severity: Severity::Warning,
706 }),
707 );
708 action_file.insert(
709 "time_missing".to_string(),
710 Action::Alert(Alert {
711 trigger: ValueSource::try_from_expression_with_namespace("Problem(Now())", "file")
712 .unwrap(),
713 print: "missing".to_string(),
714 tag: None,
715 file_bug: None,
716 severity: Severity::Warning,
717 }),
718 );
719 actions.insert("file".to_string(), action_file);
720 let data = vec![];
721 let actions_missing = actions.clone();
722 let mut context_1234 = ActionContext::new(&metrics, &actions, &data, Some(1234));
723 let results_1234 = context_1234.process();
724 let mut context_missing = ActionContext::new(&metrics, &actions_missing, &data, None);
725 let results_no_time = context_missing.process();
726
727 assert_eq!(vec!["[WARNING] 1234.".to_string()], results_1234.warnings);
728 assert!(results_no_time
729 .infos
730 .contains(&"[MISSING] In config \'file::time_1234\': (need boolean trigger) \"No valid time available\"".to_string()));
731 assert!(results_no_time.warnings.contains(&"[WARNING] missing.".to_string()));
732 }
733
734 #[fuchsia::test]
735 fn snapshots_update_correctly() -> Result<(), Error> {
736 let metrics = Metrics::new();
737 let actions = Actions::new();
738 let data = vec![];
739 let mut action_context = ActionContext::new(&metrics, &actions, &data, None);
740 let true_value = ValueSource::try_from_expression_with_default_namespace("1==1")?;
741 let false_value = ValueSource::try_from_expression_with_default_namespace("1==2")?;
742 let five_value = ValueSource {
743 metric: Metric::Eval(ExpressionContext::try_from_expression_with_default_namespace(
744 "5",
745 )?),
746 cached_value: RefCell::new(Some(MetricValue::Int(5))),
747 };
748 let foo_value = ValueSource::try_from_expression_with_default_namespace("'foo'")?;
749 let missing_value = ValueSource::try_from_expression_with_default_namespace("foo")?;
750 let snapshot_5_sig = SnapshotTrigger { interval: 5, signature: "signature".to_string() };
751 macro_rules! tester {
753 ($trigger:expr, $repeat:expr, $func:expr) => {
754 let selector_interval_action = Snapshot {
755 trigger: $trigger.clone(),
756 repeat: $repeat.clone(),
757 signature: "signature".to_string(),
758 };
759 action_context.update_snapshots(&selector_interval_action, "", "");
760 assert!($func(&action_context.action_results.snapshots));
761 };
762 }
763 type VT = Vec<SnapshotTrigger>;
764
765 tester!(true_value, foo_value, |s: &VT| s.is_empty());
767 tester!(true_value, missing_value, |s: &VT| s.is_empty());
768 tester!(foo_value, five_value, |s: &VT| s.is_empty());
769 tester!(five_value, five_value, |s: &VT| s.is_empty());
770 tester!(missing_value, five_value, |s: &VT| s.is_empty());
771 assert_eq!(action_context.action_results.infos.len(), 1);
773 assert_eq!(action_context.action_results.warnings.len(), 0);
774 assert_eq!(action_context.action_results.errors.len(), 4);
775 tester!(false_value, five_value, |s: &VT| s.is_empty());
777 tester!(true_value, five_value, |s| s == &vec![snapshot_5_sig.clone()]);
778 tester!(true_value, five_value, |s| s
780 == &vec![snapshot_5_sig.clone(), snapshot_5_sig.clone()]);
781 assert_eq!(action_context.action_results.infos.len(), 1);
782 assert_eq!(action_context.action_results.warnings.len(), 0);
783 assert_eq!(action_context.action_results.errors.len(), 4);
784 let (snapshots, warnings) = action_context.into_snapshots();
785 assert_eq!(snapshots.len(), 2);
786 assert_eq!(warnings.len(), 5);
787 Ok(())
788 }
789
790 #[fuchsia::test]
791 fn actions_cache_correctly() {
792 let metrics = make_metrics!({
793 "file":{
794 eval: {
795 "true": "0 == 0",
796 "false": "0 == 1",
797 "five": "5"
798 }
799 }
800 });
801 let mut actions = Actions::new();
802 let mut action_file = ActionsSchema::new();
803 action_file.insert(
804 "true_warning".to_string(),
805 Action::Alert(Alert {
806 trigger: ValueSource::try_from_expression_with_namespace("true", "file").unwrap(),
807 print: "True was fired".to_string(),
808 file_bug: None,
809 tag: None,
810 severity: Severity::Warning,
811 }),
812 );
813 action_file.insert(
814 "false_gauge".to_string(),
815 Action::Gauge(Gauge {
816 value: ValueSource::try_from_expression_with_namespace("false", "file").unwrap(),
817 format: None,
818 tag: None,
819 }),
820 );
821 action_file.insert(
822 "true_snapshot".to_string(),
823 Action::Snapshot(Snapshot {
824 trigger: ValueSource::try_from_expression_with_namespace("true", "file").unwrap(),
825 repeat: ValueSource {
826 metric: Metric::Eval(
827 ExpressionContext::try_from_expression_with_namespace("five", "file")
828 .unwrap(),
829 ),
830 cached_value: RefCell::new(Some(MetricValue::Int(5))),
831 },
832 signature: "signature".to_string(),
833 }),
834 );
835 action_file.insert(
836 "test_snapshot".to_string(),
837 Action::Snapshot(Snapshot {
838 trigger: ValueSource::try_from_expression_with_namespace("true", "file").unwrap(),
839 repeat: ValueSource::try_from_expression_with_namespace("five", "file").unwrap(),
840 signature: "signature".to_string(),
841 }),
842 );
843 actions.insert("file".to_string(), action_file);
844 let no_data = Vec::new();
845 let mut context = ActionContext::new(&metrics, &actions, &no_data, None);
846 context.process();
847
848 if let Action::Alert(warning) = actions.get("file").unwrap().get("true_warning").unwrap() {
850 assert_eq!(*warning.trigger.cached_value.borrow(), Some(MetricValue::Bool(true)));
851 } else {
852 unreachable!("'true_warning' must be an Action::Alert")
853 }
854
855 if let Action::Gauge(gauge) = actions.get("file").unwrap().get("false_gauge").unwrap() {
857 assert_eq!(*gauge.value.cached_value.borrow(), Some(MetricValue::Bool(false)));
858 } else {
859 unreachable!("'false_gauge' must be an Action::Gauge")
860 }
861
862 if let Action::Snapshot(snapshot) =
864 actions.get("file").unwrap().get("true_snapshot").unwrap()
865 {
866 assert_eq!(*snapshot.trigger.cached_value.borrow(), Some(MetricValue::Bool(true)));
867 assert_eq!(*snapshot.repeat.cached_value.borrow(), Some(MetricValue::Int(5)));
868 } else {
869 unreachable!("'true_snapshot' must be an Action::Snapshot")
870 }
871
872 if let Action::Snapshot(snapshot) =
876 actions.get("file").unwrap().get("test_snapshot").unwrap()
877 {
878 assert_eq!(*snapshot.trigger.cached_value.borrow(), Some(MetricValue::Bool(true)));
879 assert_eq!(*snapshot.repeat.cached_value.borrow(), Some(MetricValue::Int(5)));
880 } else {
881 unreachable!("'true_snapshot' must be an Action::Snapshot")
882 }
883 }
884}