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