| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::act::ActionContext; |
| use crate::act_structured::StructuredActionContext; |
| use crate::metrics::metric_value::MetricValue; |
| use crate::metrics::MetricState; |
| use anyhow::{bail, Error}; |
| use injectable_time::{MonotonicTime, TimeSource}; |
| use regex::Regex; |
| |
| pub(crate) mod act; // Perform appropriate actions and collect results as strings. |
| pub(crate) mod act_structured; // Perform appropriate actions and collect serializable actions. |
| pub(crate) mod config; // Read the config file(s) for metric and action specs. |
| pub(crate) mod metrics; // Retrieve and calculate the metrics. |
| pub(crate) mod plugins; // Plugins for additional analysis. |
| pub(crate) mod result_format; // Formats the triage results. |
| pub(crate) mod validate; // Check config - including that metrics/triggers work correctly. |
| |
| pub use act::{Action, ActionResults, SnapshotTrigger, WarningVec}; |
| pub use act_structured::TriageOutput; |
| pub use config::{ActionTagDirective, DataFetcher, DiagnosticData, ParseResult, Source}; |
| pub use result_format::ActionResultFormatter; |
| |
| const DEVICE_UPTIME_KEY: &str = "device.uptime"; |
| |
| fn time_from_snapshot(files: &[DiagnosticData]) -> Option<i64> { |
| if let Some(file) = files.iter().find(|file| file.source == Source::Annotations) { |
| if let DataFetcher::KeyValue(fetcher) = &file.data { |
| if let MetricValue::String(duration) = fetcher.fetch(DEVICE_UPTIME_KEY) { |
| let re = Regex::new(r"^(\d+)d(\d+)h(\d+)m(\d+)s$").unwrap(); |
| if let Some(c) = re.captures(&duration) { |
| let dhms = (c.get(1), c.get(2), c.get(3), c.get(4)); |
| if let (Some(d), Some(h), Some(m), Some(s)) = dhms { |
| let dhms = ( |
| d.as_str().parse::<i64>(), |
| h.as_str().parse::<i64>(), |
| m.as_str().parse::<i64>(), |
| s.as_str().parse::<i64>(), |
| ); |
| if let (Ok(d), Ok(h), Ok(m), Ok(s)) = dhms { |
| return Some(1_000_000_000 * (s + 60 * (m + 60 * (h + 24 * d)))); |
| } |
| } |
| } |
| } |
| } |
| } |
| None |
| } |
| |
| /// Analyze all DiagnosticData against loaded configs and generate corresponding ActionResults. |
| /// Each DiagnosticData will yield a single ActionResults instance. |
| /// Minor errors will not be included. |
| pub fn analyze( |
| diagnostic_data: &Vec<DiagnosticData>, |
| parse_result: &ParseResult, |
| ) -> Result<ActionResults, Error> { |
| inner_analyze(diagnostic_data, parse_result, false /* verbose */) |
| } |
| |
| /// Analyze all DiagnosticData against loaded configs and generate corresponding ActionResults. |
| /// Each DiagnosticData will yield a single ActionResults instance. |
| /// Include minor errors. |
| pub fn analyze_verbose( |
| diagnostic_data: &[DiagnosticData], |
| parse_result: &ParseResult, |
| ) -> Result<ActionResults, Error> { |
| inner_analyze(diagnostic_data, parse_result, true /* verbose */) |
| } |
| |
| fn inner_analyze( |
| diagnostic_data: &[DiagnosticData], |
| parse_result: &ParseResult, |
| verbose: bool, |
| ) -> Result<ActionResults, Error> { |
| parse_result.reset_state(); |
| let now = time_from_snapshot(diagnostic_data); |
| let mut action_context = |
| ActionContext::new(&parse_result.metrics, &parse_result.actions, diagnostic_data, now); |
| action_context.set_verbose(verbose); |
| Ok(action_context.process().clone()) |
| } |
| |
| /// Analyze all DiagnosticData against loaded configs and generate the corresponding TriageOutput. |
| /// A single TriageOutput instance is returned regardless of the length of DiagnosticData. |
| pub fn analyze_structured( |
| diagnostic_data: &Vec<DiagnosticData>, |
| parse_result: &ParseResult, |
| ) -> Result<TriageOutput, Error> { |
| parse_result.reset_state(); |
| let now = time_from_snapshot(diagnostic_data); |
| let mut structured_action_context = StructuredActionContext::new( |
| &parse_result.metrics, |
| &parse_result.actions, |
| diagnostic_data, |
| now, |
| ); |
| Ok(structured_action_context.process().clone()) |
| } |
| |
| // Do not call this from WASM - WASM does not provde a monotonic clock. |
| pub fn snapshots( |
| data: &Vec<DiagnosticData>, |
| parse_result: &ParseResult, |
| ) -> (Vec<SnapshotTrigger>, act::WarningVec) { |
| parse_result.reset_state(); |
| let now = Some(MonotonicTime::new().now()); |
| let evaluator = ActionContext::new(&parse_result.metrics, &parse_result.actions, data, now); |
| evaluator.into_snapshots() |
| } |
| |
| pub fn all_selectors(parse: &ParseResult) -> Vec<String> { |
| parse.all_selectors() |
| } |
| |
| pub fn evaluate_int_math(expression: &str) -> Result<i64, Error> { |
| return metric_value_to_int(MetricState::evaluate_math(expression)); |
| } |
| |
| pub fn metric_value_to_int(metric_value: MetricValue) -> Result<i64, Error> { |
| match metric_value { |
| MetricValue::Int(i) => Ok(i), |
| MetricValue::Float(f) => match metrics::safe_float_to_int(f) { |
| Some(i) => Ok(i), |
| None => bail!("Non-numeric float result {}", f), |
| }, |
| MetricValue::Problem(problem) => bail!("Eval error: {:?}", problem), |
| bad_type => bail!("Non-numeric result: {:?}", bad_type), |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| #[fuchsia::test] |
| fn time_parses_correctly() { |
| fn file(name: &str, source: Source, contents: &str) -> DiagnosticData { |
| DiagnosticData::new(name.to_string(), source, contents.to_string()).unwrap() |
| } |
| assert_eq!(time_from_snapshot(&vec![]), None); |
| // DiagnosticData can't be created with invalid JSON. |
| let files = vec![file("foo.json", Source::Annotations, r#"{"a":"b"}"#)]; |
| assert_eq!(time_from_snapshot(&files), None); |
| let files = vec![file("any.name.works", Source::Annotations, r#"{"device.uptime":"b"}"#)]; |
| assert_eq!(time_from_snapshot(&files), None); |
| let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"1h1m1s"}"#)]; |
| assert_eq!(time_from_snapshot(&files), None); |
| let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"1d1h1m"}"#)]; |
| assert_eq!(time_from_snapshot(&files), None); |
| let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"0d0h0m0s"}"#)]; |
| assert_eq!(time_from_snapshot(&files), Some(0)); |
| let files = vec![file("a.b", Source::Annotations, r#"{"device.uptime":"2d3h4m5s"}"#)]; |
| assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * 183845)); |
| let files = vec![file("a.b", Source::Annotations, r#"{"device.uptime":"11d13h17m19s"}"#)]; |
| let seconds = 19 + 17 * 60 + 13 * 3600 + 11 * 3600 * 24; |
| assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * seconds)); |
| let files = vec![file("", Source::Annotations, r#"{"device.uptime":"3d5h7m11s"}"#)]; |
| let hours = 5 + 24 * 3; |
| let minutes = 7 + 60 * hours; |
| let seconds = 11 + 60 * minutes; |
| assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * seconds)); |
| let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"0d0h0m1s"}"#)]; |
| assert_eq!(time_from_snapshot(&files), Some(1_000_000_000)); |
| let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"0d0h1m0s"}"#)]; |
| assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * 60)); |
| let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"0d1h0m0s"}"#)]; |
| assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * 60 * 60)); |
| let files = vec![file("foo.json", Source::Annotations, r#"{"device.uptime":"1d0h0m0s"}"#)]; |
| assert_eq!(time_from_snapshot(&files), Some(1_000_000_000 * 60 * 60 * 24)); |
| } |
| } |