1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use {
crate::act::ActionContext,
crate::act_structured::StructuredActionContext,
crate::metrics::{metric_value::MetricValue, MetricState},
anyhow::{bail, Error},
injectable_time::{MonotonicTime, TimeSource},
regex::Regex,
};
pub(crate) mod act; pub(crate) mod act_structured; pub(crate) mod config; pub(crate) mod metrics; pub(crate) mod plugins; pub(crate) mod result_format; pub(crate) mod validate; 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: &Vec<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
}
pub fn analyze(
diagnostic_data: &Vec<DiagnosticData>,
parse_result: &ParseResult,
) -> 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);
Ok(action_context.process().clone())
}
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())
}
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);
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));
}
}