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 '{}::{}': (need boolean trigger) {:?}",
413 namespace, name, reason,
414 ));
415 }
416 MetricValue::Problem(problem) => {
417 self.action_results.errors.push(format!(
418 "[ERROR] In config '{}::{}': (need boolean trigger): {:?}",
419 namespace, name, problem,
420 ));
421 }
422 other => {
423 self.action_results.errors.push(format!(
424 "[DEBUG: BAD CONFIG] Unexpected value type in config '{}::{}' (need boolean trigger): {}",
425 namespace,
426 name,
427 other,
428 ));
429 }
430 };
431 }
432
433 fn update_snapshots(&mut self, action: &Snapshot, namespace: &str, name: &str) {
435 match self.metric_state.eval_action_metric(namespace, &action.trigger) {
436 MetricValue::Bool(true) => {
437 let repeat_value = self.metric_state.eval_action_metric(namespace, &action.repeat);
438 let interval = metric_value_to_int(repeat_value);
439 match interval {
440 Ok(interval) => {
441 let signature = action.signature.clone();
442 let output = SnapshotTrigger { interval, signature };
443 self.action_results.snapshots.push(output);
444 }
445 Err(ref bad_type) => {
446 self.action_results.errors.push(format!(
447 "Bad interval in config '{}::{}': {:?}",
448 namespace, name, bad_type,
449 ));
450 inspect_logger::log_error(
451 "Bad interval",
452 namespace,
453 name,
454 &format!("{:?}", interval),
455 );
456 }
457 }
458 }
459 MetricValue::Bool(false) => (),
460 MetricValue::Problem(Problem::Ignore(_)) => (),
461 MetricValue::Problem(reason) => {
462 inspect_logger::log_warn(
463 "Snapshot trigger not boolean",
464 namespace,
465 name,
466 &format!("{:?}", reason),
467 );
468 self.action_results
469 .infos
470 .push(format!("[MISSING] In config '{}::{}': {:?}", namespace, name, reason,));
471 }
472 other => {
473 inspect_logger::log_error(
474 "Bad config: Unexpected value type (need boolean)",
475 namespace,
476 name,
477 &format!("{}", other),
478 );
479 self.action_results.errors.push(format!(
480 "Bad config: Unexpected value type in config '{}::{}' (need boolean): {}",
481 namespace, name, other,
482 ));
483 }
484 };
485 }
486
487 fn update_gauges(&mut self, action: &Gauge, namespace: &str, name: &str) {
489 let value = self.metric_state.eval_action_metric(namespace, &action.value);
490 match value {
491 MetricValue::Problem(Problem::Ignore(_)) => {
492 self.action_results.broken_gauges.push(format!("{}: N/A", name));
493 }
494 MetricValue::Problem(problem) => {
495 self.action_results.broken_gauges.push(format!("{}: {:?}", name, problem));
496 }
497 value => {
498 self.action_results.gauges.push(format!(
499 "{}: {}",
500 name,
501 action.get_formatted_value(value)
502 ));
503 }
504 }
505 }
506}
507
508#[cfg(test)]
509mod test {
510 use super::*;
511 use crate::config::Source;
512 use crate::make_metrics;
513
514 fn includes(values: &Vec<String>, substring: &str) -> bool {
516 for value in values {
517 if value.contains(substring) {
518 return true;
519 }
520 }
521 false
522 }
523
524 #[fuchsia::test]
525 fn actions_fire_correctly() {
526 let metrics = make_metrics!({
527 "file":{
528 eval: {
529 "true": "0 == 0",
530 "false": "0 == 1",
531 "true_array": "[0 == 0]",
532 "false_array": "[0 == 1]"
533 }
534 }
535 });
536 let mut actions = Actions::new();
537 let mut action_file = ActionsSchema::new();
538 action_file.insert(
539 "do_true".to_string(),
540 Action::Alert(Alert {
541 trigger: ValueSource::try_from_expression_with_namespace("true", "file").unwrap(),
542 print: "True was fired".to_string(),
543 file_bug: Some("Some>Monorail>Component".to_string()),
544 tag: None,
545 severity: Severity::Warning,
546 }),
547 );
548 action_file.insert(
549 "do_false".to_string(),
550 Action::Alert(Alert {
551 trigger: ValueSource::try_from_expression_with_namespace("false", "file").unwrap(),
552 print: "False was fired".to_string(),
553 file_bug: None,
554 tag: None,
555 severity: Severity::Warning,
556 }),
557 );
558 action_file.insert(
559 "do_true_array".to_string(),
560 Action::Alert(Alert {
561 trigger: ValueSource::try_from_expression_with_namespace("true_array", "file")
562 .unwrap(),
563 print: "True array was fired".to_string(),
564 file_bug: None,
565 tag: None,
566 severity: Severity::Warning,
567 }),
568 );
569 action_file.insert(
570 "do_false_array".to_string(),
571 Action::Alert(Alert {
572 trigger: ValueSource::try_from_expression_with_namespace("false_array", "file")
573 .unwrap(),
574 print: "False array was fired".to_string(),
575 file_bug: None,
576 tag: None,
577 severity: Severity::Warning,
578 }),
579 );
580
581 action_file.insert(
582 "do_operation".to_string(),
583 Action::Alert(Alert {
584 trigger: ValueSource::try_from_expression_with_namespace("0 < 10", "file").unwrap(),
585 print: "Inequality triggered".to_string(),
586 file_bug: None,
587 tag: None,
588 severity: Severity::Warning,
589 }),
590 );
591 actions.insert("file".to_string(), action_file);
592 let no_data = Vec::new();
593 let mut context = ActionContext::new(&metrics, &actions, &no_data, None);
594 let results = context.process();
595 assert!(includes(&results.errors, "[BUG:Some>Monorail>Component] True was fired."));
596 assert!(includes(&results.warnings, "[WARNING] Inequality triggered."));
597 assert!(includes(&results.warnings, "[WARNING] True array was fired"));
598 assert!(!includes(&results.warnings, "False was fired"));
599 assert!(!includes(&results.warnings, "False array was fired"));
600 }
601
602 #[fuchsia::test]
603 fn gauges_fire_correctly() {
604 let metrics = make_metrics!({
605 "file":{
606 eval: {
607 "gauge_f1": "2 / 5",
608 "gauge_f2": "4 / 5",
609 "gauge_f3": "6 / 5",
610 "gauge_i4": "9 // 2",
611 "gauge_i5": "11 // 2",
612 "gauge_i6": "13 // 2",
613 "gauge_b7": "2 == 2",
614 "gauge_b8": "2 > 2",
615 "gauge_s9": "'foo'"
616 }
617 }
618 });
619 let mut actions = Actions::new();
620 let mut action_file = ActionsSchema::new();
621 macro_rules! insert_gauge {
622 ($name:expr, $format:expr) => {
623 action_file.insert(
624 $name.to_string(),
625 Action::Gauge(Gauge {
626 value: ValueSource::try_from_expression_with_namespace($name, "file")
627 .unwrap(),
628 format: $format,
629 tag: None,
630 }),
631 );
632 };
633 }
634 insert_gauge!("gauge_f1", None);
635 insert_gauge!("gauge_f2", Some("percentage".to_string()));
636 insert_gauge!("gauge_f3", Some("unknown".to_string()));
637 insert_gauge!("gauge_i4", None);
638 insert_gauge!("gauge_i5", Some("percentage".to_string()));
639 insert_gauge!("gauge_i6", Some("unknown".to_string()));
640 insert_gauge!("gauge_b7", None);
641 insert_gauge!("gauge_b8", None);
642 insert_gauge!("gauge_s9", None);
643 actions.insert("file".to_string(), action_file);
644 let no_data = Vec::new();
645 let mut context = ActionContext::new(&metrics, &actions, &no_data, None);
646
647 let results = context.process();
648
649 assert!(includes(&results.gauges, "gauge_f1: 0.4"));
650 assert!(includes(&results.gauges, "gauge_f2: 80.00%"));
651 assert!(includes(&results.gauges, "gauge_f3: 1.2"));
652 assert!(includes(&results.gauges, "gauge_i4: 4"));
653 assert!(includes(&results.gauges, "gauge_i5: 500%"));
654 assert!(includes(&results.gauges, "gauge_i6: 6"));
655 assert!(includes(&results.gauges, "gauge_b7: Bool(true)"));
656 assert!(includes(&results.gauges, "gauge_b8: Bool(false)"));
657 assert!(includes(&results.gauges, "gauge_s9: String(\"foo\")"));
658 }
659
660 #[fuchsia::test]
661 fn action_context_errors() {
662 let metrics = Metrics::new();
663 let actions = Actions::new();
664 let data = vec![DiagnosticData::new(
665 "inspect.json".to_string(),
666 Source::Inspect,
667 r#"
668 [
669 {
670 "moniker": "abcd",
671 "metadata": {},
672 "payload": {"root": {"val": 10}}
673 },
674 {
675 "moniker": "abcd2",
676 "metadata": {},
677 "payload": ["a", "b"]
678 },
679 {
680 "moniker": "abcd3",
681 "metadata": {},
682 "payload": null
683 }
684 ]
685 "#
686 .to_string(),
687 )
688 .expect("create data")];
689 let action_context = ActionContext::new(&metrics, &actions, &data, None);
690 assert_eq!(
693 vec!["[DEBUG: BAD DATA] Unable to deserialize Inspect contents for abcd2 to node hierarchy"
694 .to_string()],
695 action_context.action_results.errors
696 );
697 }
698
699 #[fuchsia::test]
700 fn time_propagates_correctly() {
701 let metrics = Metrics::new();
702 let mut actions = Actions::new();
703 let mut action_file = ActionsSchema::new();
704 action_file.insert(
705 "time_1234".to_string(),
706 Action::Alert(Alert {
707 trigger: ValueSource::try_from_expression_with_namespace("Now() == 1234", "file")
708 .unwrap(),
709 print: "1234".to_string(),
710 tag: None,
711 file_bug: None,
712 severity: Severity::Warning,
713 }),
714 );
715 action_file.insert(
716 "time_missing".to_string(),
717 Action::Alert(Alert {
718 trigger: ValueSource::try_from_expression_with_namespace("Problem(Now())", "file")
719 .unwrap(),
720 print: "missing".to_string(),
721 tag: None,
722 file_bug: None,
723 severity: Severity::Warning,
724 }),
725 );
726 actions.insert("file".to_string(), action_file);
727 let data = vec![];
728 let actions_missing = actions.clone();
729 let mut context_1234 = ActionContext::new(&metrics, &actions, &data, Some(1234));
730 let results_1234 = context_1234.process();
731 let mut context_missing = ActionContext::new(&metrics, &actions_missing, &data, None);
732 let results_no_time = context_missing.process();
733
734 assert_eq!(vec!["[WARNING] 1234.".to_string()], results_1234.warnings);
735 assert!(results_no_time
736 .infos
737 .contains(&"[MISSING] In config \'file::time_1234\': (need boolean trigger) \"No valid time available\"".to_string()));
738 assert!(results_no_time.warnings.contains(&"[WARNING] missing.".to_string()));
739 }
740
741 #[fuchsia::test]
742 fn snapshots_update_correctly() -> Result<(), Error> {
743 let metrics = Metrics::new();
744 let actions = Actions::new();
745 let data = vec![];
746 let mut action_context = ActionContext::new(&metrics, &actions, &data, None);
747 let true_value = ValueSource::try_from_expression_with_default_namespace("1==1")?;
748 let false_value = ValueSource::try_from_expression_with_default_namespace("1==2")?;
749 let five_value = ValueSource {
750 metric: Metric::Eval(ExpressionContext::try_from_expression_with_default_namespace(
751 "5",
752 )?),
753 cached_value: RefCell::new(Some(MetricValue::Int(5))),
754 };
755 let foo_value = ValueSource::try_from_expression_with_default_namespace("'foo'")?;
756 let missing_value = ValueSource::try_from_expression_with_default_namespace("foo")?;
757 let snapshot_5_sig = SnapshotTrigger { interval: 5, signature: "signature".to_string() };
758 macro_rules! tester {
760 ($trigger:expr, $repeat:expr, $func:expr) => {
761 let selector_interval_action = Snapshot {
762 trigger: $trigger.clone(),
763 repeat: $repeat.clone(),
764 signature: "signature".to_string(),
765 };
766 action_context.update_snapshots(&selector_interval_action, "", "");
767 assert!($func(&action_context.action_results.snapshots));
768 };
769 }
770 type VT = Vec<SnapshotTrigger>;
771
772 tester!(true_value, foo_value, |s: &VT| s.is_empty());
774 tester!(true_value, missing_value, |s: &VT| s.is_empty());
775 tester!(foo_value, five_value, |s: &VT| s.is_empty());
776 tester!(five_value, five_value, |s: &VT| s.is_empty());
777 tester!(missing_value, five_value, |s: &VT| s.is_empty());
778 assert_eq!(action_context.action_results.infos.len(), 1);
780 assert_eq!(action_context.action_results.warnings.len(), 0);
781 assert_eq!(action_context.action_results.errors.len(), 4);
782 tester!(false_value, five_value, |s: &VT| s.is_empty());
784 tester!(true_value, five_value, |s| s == &vec![snapshot_5_sig.clone()]);
785 tester!(true_value, five_value, |s| s
787 == &vec![snapshot_5_sig.clone(), snapshot_5_sig.clone()]);
788 assert_eq!(action_context.action_results.infos.len(), 1);
789 assert_eq!(action_context.action_results.warnings.len(), 0);
790 assert_eq!(action_context.action_results.errors.len(), 4);
791 let (snapshots, warnings) = action_context.into_snapshots();
792 assert_eq!(snapshots.len(), 2);
793 assert_eq!(warnings.len(), 5);
794 Ok(())
795 }
796
797 #[fuchsia::test]
798 fn actions_cache_correctly() {
799 let metrics = make_metrics!({
800 "file":{
801 eval: {
802 "true": "0 == 0",
803 "false": "0 == 1",
804 "five": "5"
805 }
806 }
807 });
808 let mut actions = Actions::new();
809 let mut action_file = ActionsSchema::new();
810 action_file.insert(
811 "true_warning".to_string(),
812 Action::Alert(Alert {
813 trigger: ValueSource::try_from_expression_with_namespace("true", "file").unwrap(),
814 print: "True was fired".to_string(),
815 file_bug: None,
816 tag: None,
817 severity: Severity::Warning,
818 }),
819 );
820 action_file.insert(
821 "false_gauge".to_string(),
822 Action::Gauge(Gauge {
823 value: ValueSource::try_from_expression_with_namespace("false", "file").unwrap(),
824 format: None,
825 tag: None,
826 }),
827 );
828 action_file.insert(
829 "true_snapshot".to_string(),
830 Action::Snapshot(Snapshot {
831 trigger: ValueSource::try_from_expression_with_namespace("true", "file").unwrap(),
832 repeat: ValueSource {
833 metric: Metric::Eval(
834 ExpressionContext::try_from_expression_with_namespace("five", "file")
835 .unwrap(),
836 ),
837 cached_value: RefCell::new(Some(MetricValue::Int(5))),
838 },
839 signature: "signature".to_string(),
840 }),
841 );
842 action_file.insert(
843 "test_snapshot".to_string(),
844 Action::Snapshot(Snapshot {
845 trigger: ValueSource::try_from_expression_with_namespace("true", "file").unwrap(),
846 repeat: ValueSource::try_from_expression_with_namespace("five", "file").unwrap(),
847 signature: "signature".to_string(),
848 }),
849 );
850 actions.insert("file".to_string(), action_file);
851 let no_data = Vec::new();
852 let mut context = ActionContext::new(&metrics, &actions, &no_data, None);
853 context.process();
854
855 if let Action::Alert(warning) = actions.get("file").unwrap().get("true_warning").unwrap() {
857 assert_eq!(*warning.trigger.cached_value.borrow(), Some(MetricValue::Bool(true)));
858 } else {
859 unreachable!("'true_warning' must be an Action::Alert")
860 }
861
862 if let Action::Gauge(gauge) = actions.get("file").unwrap().get("false_gauge").unwrap() {
864 assert_eq!(*gauge.value.cached_value.borrow(), Some(MetricValue::Bool(false)));
865 } else {
866 unreachable!("'false_gauge' must be an Action::Gauge")
867 }
868
869 if let Action::Snapshot(snapshot) =
871 actions.get("file").unwrap().get("true_snapshot").unwrap()
872 {
873 assert_eq!(*snapshot.trigger.cached_value.borrow(), Some(MetricValue::Bool(true)));
874 assert_eq!(*snapshot.repeat.cached_value.borrow(), Some(MetricValue::Int(5)));
875 } else {
876 unreachable!("'true_snapshot' must be an Action::Snapshot")
877 }
878
879 if let Action::Snapshot(snapshot) =
883 actions.get("file").unwrap().get("test_snapshot").unwrap()
884 {
885 assert_eq!(*snapshot.trigger.cached_value.borrow(), Some(MetricValue::Bool(true)));
886 assert_eq!(*snapshot.repeat.cached_value.borrow(), Some(MetricValue::Int(5)));
887 } else {
888 unreachable!("'true_snapshot' must be an Action::Snapshot")
889 }
890 }
891}