fuchsia_triage/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::act::ActionContext;
6use crate::act_structured::StructuredActionContext;
7use crate::metrics::metric_value::MetricValue;
8use crate::metrics::MetricState;
9use anyhow::{bail, Error};
10use injectable_time::{MonotonicInstant, TimeSource};
11use regex::Regex;
12
13pub(crate) mod act; // Perform appropriate actions and collect results as strings.
14pub(crate) mod act_structured; // Perform appropriate actions and collect serializable actions.
15pub(crate) mod config; // Read the config file(s) for metric and action specs.
16pub mod inspect_logger;
17pub(crate) mod metrics; // Retrieve and calculate the metrics.
18pub(crate) mod plugins; // Plugins for additional analysis.
19pub(crate) mod result_format; // Formats the triage results.
20pub(crate) mod validate; // Check config - including that metrics/triggers work correctly.
21
22pub use act::{Action, ActionResults, SnapshotTrigger, WarningVec};
23pub use act_structured::TriageOutput;
24pub use config::{ActionTagDirective, DataFetcher, DiagnosticData, ParseResult, Source};
25pub use result_format::ActionResultFormatter;
26
27const DEVICE_UPTIME_KEY: &str = "device.uptime";
28
29fn time_from_snapshot(files: &[DiagnosticData]) -> Option<i64> {
30    if let Some(file) = files.iter().find(|file| file.source == Source::Annotations) {
31        if let DataFetcher::KeyValue(fetcher) = &file.data {
32            if let MetricValue::String(duration) = fetcher.fetch(DEVICE_UPTIME_KEY) {
33                let re = Regex::new(r"^(\d+)d(\d+)h(\d+)m(\d+)s$").unwrap();
34                if let Some(c) = re.captures(&duration) {
35                    let dhms = (c.get(1), c.get(2), c.get(3), c.get(4));
36                    if let (Some(d), Some(h), Some(m), Some(s)) = dhms {
37                        let dhms = (
38                            d.as_str().parse::<i64>(),
39                            h.as_str().parse::<i64>(),
40                            m.as_str().parse::<i64>(),
41                            s.as_str().parse::<i64>(),
42                        );
43                        if let (Ok(d), Ok(h), Ok(m), Ok(s)) = dhms {
44                            return Some(1_000_000_000 * (s + 60 * (m + 60 * (h + 24 * d))));
45                        }
46                    }
47                }
48            }
49        }
50    }
51    None
52}
53
54/// Analyze all DiagnosticData against loaded configs and generate corresponding ActionResults.
55/// Each DiagnosticData will yield a single ActionResults instance.
56/// Minor errors will not be included.
57pub fn analyze(
58    diagnostic_data: &[DiagnosticData],
59    parse_result: &ParseResult,
60) -> Result<ActionResults, Error> {
61    inner_analyze(diagnostic_data, parse_result, false /* verbose */)
62}
63
64/// Analyze all DiagnosticData against loaded configs and generate corresponding ActionResults.
65/// Each DiagnosticData will yield a single ActionResults instance.
66/// Include minor errors.
67pub fn analyze_verbose(
68    diagnostic_data: &[DiagnosticData],
69    parse_result: &ParseResult,
70) -> Result<ActionResults, Error> {
71    inner_analyze(diagnostic_data, parse_result, true /* verbose */)
72}
73
74fn inner_analyze(
75    diagnostic_data: &[DiagnosticData],
76    parse_result: &ParseResult,
77    verbose: bool,
78) -> Result<ActionResults, Error> {
79    parse_result.reset_state();
80    let now = time_from_snapshot(diagnostic_data);
81    let mut action_context =
82        ActionContext::new(&parse_result.metrics, &parse_result.actions, diagnostic_data, now);
83    action_context.set_verbose(verbose);
84    Ok(action_context.process().clone())
85}
86
87/// Analyze all DiagnosticData against loaded configs and generate the corresponding TriageOutput.
88/// A single TriageOutput instance is returned regardless of the length of DiagnosticData.
89pub fn analyze_structured(
90    diagnostic_data: &[DiagnosticData],
91    parse_result: &ParseResult,
92) -> Result<TriageOutput, Error> {
93    parse_result.reset_state();
94    let now = time_from_snapshot(diagnostic_data);
95    let mut structured_action_context = StructuredActionContext::new(
96        &parse_result.metrics,
97        &parse_result.actions,
98        diagnostic_data,
99        now,
100    );
101    Ok(structured_action_context.process().clone())
102}
103
104// Do not call this from WASM - WASM does not provde a monotonic clock.
105pub fn snapshots(
106    data: &[DiagnosticData],
107    parse_result: &ParseResult,
108) -> (Vec<SnapshotTrigger>, act::WarningVec) {
109    parse_result.reset_state();
110    let now = Some(MonotonicInstant::new().now());
111    let evaluator = ActionContext::new(&parse_result.metrics, &parse_result.actions, data, now);
112    evaluator.into_snapshots()
113}
114
115pub fn all_selectors(parse: &ParseResult) -> Vec<String> {
116    parse.all_selectors()
117}
118
119pub fn evaluate_int_math(expression: &str) -> Result<i64, Error> {
120    metric_value_to_int(MetricState::evaluate_math(expression))
121}
122
123pub fn metric_value_to_int(metric_value: MetricValue) -> Result<i64, Error> {
124    match metric_value {
125        MetricValue::Int(i) => Ok(i),
126        MetricValue::Float(f) => match metrics::safe_float_to_int(f) {
127            Some(i) => Ok(i),
128            None => bail!("Non-numeric float result {}", f),
129        },
130        MetricValue::Problem(problem) => bail!("Eval error: {:?}", problem),
131        bad_type => bail!("Non-numeric result: {:?}", bad_type),
132    }
133}
134
135#[cfg(test)]
136mod test {
137    use super::*;
138
139    #[fuchsia::test]
140    fn time_parses_correctly() {
141        fn file(name: &str, source: Source, contents: &str) -> DiagnosticData {
142            DiagnosticData::new(name.to_string(), source, contents.to_string()).unwrap()
143        }
144        assert_eq!(time_from_snapshot(&[]), None);
145        // DiagnosticData can't be created with invalid JSON.
146        let files = vec![file("foo.json", Source::Annotations, r#"{"a":"b"}"#)];
147        assert_eq!(time_from_snapshot(&files), None);
148        let files = vec![file("any.name.works", Source::Annotations, r#"{"device.uptime":"b"}"#)];
149        assert_eq!(time_from_snapshot(&files), None);
150        let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"1h1m1s"}"#)];
151        assert_eq!(time_from_snapshot(&files), None);
152        let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"1d1h1m"}"#)];
153        assert_eq!(time_from_snapshot(&files), None);
154        let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"0d0h0m0s"}"#)];
155        assert_eq!(time_from_snapshot(&files), Some(0));
156        let files = vec![file("a.b", Source::Annotations, r#"{"device.uptime":"2d3h4m5s"}"#)];
157        assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * 183845));
158        let files = vec![file("a.b", Source::Annotations, r#"{"device.uptime":"11d13h17m19s"}"#)];
159        let seconds = 19 + 17 * 60 + 13 * 3600 + 11 * 3600 * 24;
160        assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * seconds));
161        let files = vec![file("", Source::Annotations, r#"{"device.uptime":"3d5h7m11s"}"#)];
162        let hours = 5 + 24 * 3;
163        let minutes = 7 + 60 * hours;
164        let seconds = 11 + 60 * minutes;
165        assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * seconds));
166        let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"0d0h0m1s"}"#)];
167        assert_eq!(time_from_snapshot(&files), Some(1_000_000_000));
168        let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"0d0h1m0s"}"#)];
169        assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * 60));
170        let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"0d1h0m0s"}"#)];
171        assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * 60 * 60));
172        let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"1d0h0m0s"}"#)];
173        assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * 60 * 60 * 24));
174    }
175}