fuchsia_triage/metrics/
metric_value.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 super::{unhandled_type, Lambda};
6use diagnostics_hierarchy::{ArrayContent, Property as DiagnosticProperty};
7use serde::{Deserialize, Serialize};
8use serde_json::Value as JsonValue;
9
10/// The calculated or selected value of a Metric.
11///
12/// Problem means that the value could not be calculated.
13#[derive(Deserialize, Debug, Clone, Serialize)]
14pub enum MetricValue {
15    // Ensure every variant of MetricValue is tested in metric_value_traits().
16    // TODO(cphoenix): Support u64.
17    Int(i64),
18    Float(f64),
19    String(String),
20    Bool(bool),
21    Vector(Vec<MetricValue>),
22    Bytes(Vec<u8>),
23    Problem(Problem),
24    Lambda(Box<Lambda>),
25    Node,
26}
27
28/// Some kind of problematic non-value. In most cases, this should be treated as a thrown error.
29#[derive(Deserialize, Clone, Serialize)]
30pub enum Problem {
31    Missing(String),
32    /// An unknown (not supported yet) value that is present but cannot be used in computation.
33    UnhandledType(String),
34    /// An error in the grammar or semantics of a .triage file, usually detectable by inspection.
35    SyntaxError(String),
36    /// An incorrect type for an operation.
37    ValueError(String),
38    /// An internal bug; these should never be seen in the wild.
39    InternalBug(String),
40    /// One or more expected errors - don't bother humans with the result.
41    Ignore(Vec<Problem>),
42    /// An error which can occur in evaluation of an expression.
43    EvaluationError(String),
44}
45
46impl Problem {
47    // Ranks problems in order of severity; bigger numbers are worse.
48    // This is NOT an API or contract. The ranking can change between program versions.
49    // Multiple problems can share a rank.
50    pub(crate) fn severity(&self) -> i32 {
51        match self {
52            Problem::Ignore(_) => 1,
53            Problem::Missing(_) => 2,
54            Problem::UnhandledType(_) => 3,
55            Problem::ValueError(_) => 4,
56            Problem::EvaluationError(_) => 5,
57            Problem::SyntaxError(_) => 6,
58            Problem::InternalBug(_) => 7,
59        }
60    }
61}
62
63impl PartialEq for MetricValue {
64    fn eq(&self, other: &Self) -> bool {
65        match (self, other) {
66            (MetricValue::Int(l), MetricValue::Int(r)) => l == r,
67            (MetricValue::Float(l), MetricValue::Float(r)) => l == r,
68            (MetricValue::Bytes(l), MetricValue::Bytes(r)) => l == r,
69            (MetricValue::Int(l), MetricValue::Float(r)) => *l as f64 == *r,
70            (MetricValue::Float(l), MetricValue::Int(r)) => *l == *r as f64,
71            (MetricValue::String(l), MetricValue::String(r)) => l == r,
72            (MetricValue::Bool(l), MetricValue::Bool(r)) => l == r,
73            (MetricValue::Vector(l), MetricValue::Vector(r)) => l == r,
74            _ => false,
75        }
76    }
77}
78
79impl Eq for MetricValue {}
80
81impl std::fmt::Display for MetricValue {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            MetricValue::Int(n) => write!(f, "Int({})", n),
85            MetricValue::Float(n) => write!(f, "Float({})", n),
86            MetricValue::Bool(n) => write!(f, "Bool({})", n),
87            MetricValue::String(n) => write!(f, "String({})", n),
88            MetricValue::Vector(n) => write!(f, "Vector({:?})", n),
89            MetricValue::Bytes(n) => write!(f, "Bytes({:?})", n),
90            MetricValue::Problem(p) => write!(f, "{:?}", p),
91            MetricValue::Lambda(n) => write!(f, "Fn({:?})", n),
92            MetricValue::Node => write!(f, "Node"),
93        }
94    }
95}
96
97impl From<f64> for MetricValue {
98    fn from(val: f64) -> MetricValue {
99        MetricValue::Float(val)
100    }
101}
102
103impl From<i64> for MetricValue {
104    fn from(val: i64) -> MetricValue {
105        MetricValue::Int(val)
106    }
107}
108
109impl From<DiagnosticProperty> for MetricValue {
110    fn from(property: DiagnosticProperty) -> Self {
111        match property {
112            DiagnosticProperty::String(_name, value) => Self::String(value),
113            DiagnosticProperty::Bytes(_name, value) => Self::Bytes(value),
114            DiagnosticProperty::Int(_name, value) => Self::Int(value),
115            DiagnosticProperty::Uint(_name, value) => Self::Int(value as i64),
116            DiagnosticProperty::Double(_name, value) => Self::Float(value),
117            DiagnosticProperty::Bool(_name, value) => Self::Bool(value),
118            // TODO(cphoenix): Figure out what to do about histograms.
119            DiagnosticProperty::DoubleArray(_name, ArrayContent::Values(values)) => {
120                Self::Vector(values.iter().map(|value| Self::Float(*value)).collect())
121            }
122            DiagnosticProperty::IntArray(_name, ArrayContent::Values(values)) => {
123                Self::Vector(values.iter().map(|value| Self::Int(*value)).collect())
124            }
125            DiagnosticProperty::UintArray(_name, ArrayContent::Values(values)) => {
126                Self::Vector(values.iter().map(|value| Self::Int(*value as i64)).collect())
127            }
128            DiagnosticProperty::DoubleArray(_name, ArrayContent::LinearHistogram(_))
129            | DiagnosticProperty::IntArray(_name, ArrayContent::LinearHistogram(_))
130            | DiagnosticProperty::UintArray(_name, ArrayContent::LinearHistogram(_))
131            | DiagnosticProperty::DoubleArray(_name, ArrayContent::ExponentialHistogram(_))
132            | DiagnosticProperty::IntArray(_name, ArrayContent::ExponentialHistogram(_))
133            | DiagnosticProperty::UintArray(_name, ArrayContent::ExponentialHistogram(_)) => {
134                unhandled_type("Histogram is not supported")
135            }
136            DiagnosticProperty::StringList(_name, _list) => {
137                unhandled_type("StringList is not supported")
138            }
139        }
140    }
141}
142
143impl From<JsonValue> for MetricValue {
144    fn from(value: JsonValue) -> Self {
145        match value {
146            JsonValue::String(value) => Self::String(value),
147            JsonValue::Bool(value) => Self::Bool(value),
148            JsonValue::Number(_) => Self::from(&value),
149            JsonValue::Array(values) => Self::Vector(values.into_iter().map(Self::from).collect()),
150            _ => unhandled_type("Unsupported JSON type"),
151        }
152    }
153}
154
155impl From<&JsonValue> for MetricValue {
156    fn from(value: &JsonValue) -> Self {
157        match value {
158            JsonValue::String(value) => Self::String(value.clone()),
159            JsonValue::Bool(value) => Self::Bool(*value),
160            JsonValue::Number(value) => {
161                if value.is_i64() {
162                    Self::Int(value.as_i64().unwrap())
163                } else if value.is_u64() {
164                    Self::Int(value.as_u64().unwrap() as i64)
165                } else if value.is_f64() {
166                    Self::Float(value.as_f64().unwrap())
167                } else {
168                    unhandled_type("Unable to convert JSON number")
169                }
170            }
171            JsonValue::Array(values) => Self::Vector(values.iter().map(Self::from).collect()),
172            _ => unhandled_type("Unsupported JSON type"),
173        }
174    }
175}
176
177impl std::fmt::Debug for Problem {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        match self {
180            Problem::Missing(s) => write!(f, "Missing: {}", s),
181            Problem::Ignore(problems) => {
182                if problems.len() == 1 {
183                    write!(f, "Ignore: {:?}", problems[0])
184                } else {
185                    write!(f, "Ignore: [")?;
186                    for problem in problems.iter() {
187                        write!(f, "{:?}; ", problem)?;
188                    }
189                    write!(f, "]")
190                }
191            }
192            Problem::SyntaxError(s) => write!(f, "SyntaxError: {}", s),
193            Problem::ValueError(s) => write!(f, "ValueError: {}", s),
194            Problem::InternalBug(s) => write!(f, "InternalBug: {}", s),
195            Problem::UnhandledType(s) => write!(f, "UnhandledType: {}", s),
196            Problem::EvaluationError(s) => write!(f, "EvaluationError: {}", s),
197        }
198    }
199}
200
201#[cfg(test)]
202pub(crate) mod test {
203    use super::*;
204    use crate::assert_problem;
205    use diagnostics_hierarchy::ArrayFormat;
206    use serde_json::{json, Number as JsonNumber};
207
208    #[fuchsia::test]
209    fn test_equality() {
210        // Equal Value, Equal Type
211        assert_eq!(MetricValue::Int(1), MetricValue::Int(1));
212        assert_eq!(MetricValue::Float(1.0), MetricValue::Float(1.0));
213        assert_eq!(MetricValue::String("A".to_string()), MetricValue::String("A".to_string()));
214        assert_eq!(MetricValue::Bool(true), MetricValue::Bool(true));
215        assert_eq!(MetricValue::Bool(false), MetricValue::Bool(false));
216        assert_eq!(
217            MetricValue::Vector(vec![
218                MetricValue::Int(1),
219                MetricValue::Float(1.0),
220                MetricValue::String("A".to_string()),
221                MetricValue::Bool(true),
222            ]),
223            MetricValue::Vector(vec![
224                MetricValue::Int(1),
225                MetricValue::Float(1.0),
226                MetricValue::String("A".to_string()),
227                MetricValue::Bool(true),
228            ])
229        );
230        assert_eq!(MetricValue::Bytes(vec![1, 2, 3]), MetricValue::Bytes(vec![1, 2, 3]));
231
232        // Floats and ints interconvert. Test both ways for full code coverage.
233        assert_eq!(MetricValue::Int(1), MetricValue::Float(1.0));
234        assert_eq!(MetricValue::Float(1.0), MetricValue::Int(1));
235
236        // Numbers, vectors, and byte arrays do not interconvert when compared with Rust ==.
237        // Note, though, that the expression "1 == [1]" will evaluate to true.
238        assert!(MetricValue::Int(1) != MetricValue::Vector(vec![MetricValue::Int(1)]));
239        assert!(MetricValue::Bytes(vec![1]) != MetricValue::Vector(vec![MetricValue::Int(1)]));
240        assert!(MetricValue::Int(1) != MetricValue::Bytes(vec![1]));
241
242        // Nested array
243        assert_eq!(
244            MetricValue::Vector(vec![
245                MetricValue::Int(1),
246                MetricValue::Float(1.0),
247                MetricValue::String("A".to_string()),
248                MetricValue::Bool(true),
249            ]),
250            MetricValue::Vector(vec![
251                MetricValue::Int(1),
252                MetricValue::Float(1.0),
253                MetricValue::String("A".to_string()),
254                MetricValue::Bool(true),
255            ])
256        );
257
258        // Problem should never be equal
259        assert!(
260            MetricValue::Problem(Problem::Missing("err".to_string()))
261                != MetricValue::Problem(Problem::Missing("err".to_string()))
262        );
263        // Use assert_problem() macro to test error messages.
264        assert_problem!(MetricValue::Problem(Problem::Missing("err".to_string())), "Missing: err");
265
266        // We don't have a contract for Lambda equality. We probably don't need one.
267    }
268
269    #[fuchsia::test]
270    fn test_inequality() {
271        // Different Value, Equal Type
272        assert_ne!(MetricValue::Int(1), MetricValue::Int(2));
273        assert_ne!(MetricValue::Float(1.0), MetricValue::Float(2.0));
274        assert_ne!(MetricValue::String("A".to_string()), MetricValue::String("B".to_string()));
275        assert_ne!(MetricValue::Bool(true), MetricValue::Bool(false));
276        assert_ne!(
277            MetricValue::Vector(vec![
278                MetricValue::Int(1),
279                MetricValue::Float(1.0),
280                MetricValue::String("A".to_string()),
281                MetricValue::Bool(true),
282            ]),
283            MetricValue::Vector(vec![
284                MetricValue::Int(2),
285                MetricValue::Float(2.0),
286                MetricValue::String("B".to_string()),
287                MetricValue::Bool(false),
288            ])
289        );
290
291        // Different Type
292        assert_ne!(MetricValue::Int(2), MetricValue::Float(1.0));
293        assert_ne!(MetricValue::Int(1), MetricValue::String("A".to_string()));
294        assert_ne!(MetricValue::Int(1), MetricValue::Bool(true));
295        assert_ne!(MetricValue::Float(1.0), MetricValue::String("A".to_string()));
296        assert_ne!(MetricValue::Float(1.0), MetricValue::Bool(true));
297        assert_ne!(MetricValue::String("A".to_string()), MetricValue::Bool(true));
298    }
299
300    #[fuchsia::test]
301    fn test_fmt() {
302        assert_eq!(format!("{}", MetricValue::Int(3)), "Int(3)");
303        assert_eq!(format!("{}", MetricValue::Float(3.5)), "Float(3.5)");
304        assert_eq!(format!("{}", MetricValue::Bool(true)), "Bool(true)");
305        assert_eq!(format!("{}", MetricValue::Bool(false)), "Bool(false)");
306        assert_eq!(format!("{}", MetricValue::String("cat".to_string())), "String(cat)");
307        assert_eq!(
308            format!("{}", MetricValue::Vector(vec![MetricValue::Int(1), MetricValue::Float(2.5)])),
309            "Vector([Int(1), Float(2.5)])"
310        );
311        assert_eq!(format!("{}", MetricValue::Bytes(vec![1u8, 2u8])), "Bytes([1, 2])");
312        assert_eq!(
313            format!("{}", MetricValue::Problem(Problem::Missing("Where is Foo?".to_string()))),
314            "Missing: Where is Foo?"
315        );
316        assert_eq!(
317            format!("{}", MetricValue::Problem(Problem::ValueError("Where is Foo?".to_string()))),
318            "ValueError: Where is Foo?"
319        );
320        assert_eq!(
321            format!("{}", MetricValue::Problem(Problem::SyntaxError("Where is Foo?".to_string()))),
322            "SyntaxError: Where is Foo?"
323        );
324        assert_eq!(
325            format!("{}", MetricValue::Problem(Problem::InternalBug("Where is Foo?".to_string()))),
326            "InternalBug: Where is Foo?"
327        );
328        assert_eq!(
329            format!(
330                "{}",
331                MetricValue::Problem(Problem::UnhandledType("Where is Foo?".to_string()))
332            ),
333            "UnhandledType: Where is Foo?"
334        );
335        assert_eq!(
336            format!(
337                "{}",
338                MetricValue::Problem(Problem::Ignore(vec![Problem::SyntaxError(
339                    "Where is Foo?".to_string()
340                ),]))
341            ),
342            "Ignore: SyntaxError: Where is Foo?"
343        );
344        assert_eq!(
345            format!(
346                "{}",
347                MetricValue::Problem(Problem::Ignore(vec![
348                    Problem::SyntaxError("Where is Foo?".to_string()),
349                    Problem::ValueError("Where is Bar?".to_string()),
350                ]))
351            ),
352            "Ignore: [SyntaxError: Where is Foo?; ValueError: Where is Bar?; ]"
353        );
354    }
355
356    #[fuchsia::test]
357    fn metric_value_from_json() {
358        /*
359            JSON subtypes:
360                Bool(bool),
361                Number(Number),
362                String(String),
363                Array(Vec<Value>),
364                Object(Map<String, Value>),
365        */
366        macro_rules! test_from {
367            ($json:path, $metric:path, $value:expr) => {
368                test_from_to!($json, $metric, $value, $value);
369            };
370        }
371        macro_rules! test_from_int {
372            ($json:path, $metric:path, $value:expr) => {
373                test_from_to!($json, $metric, JsonNumber::from($value), $value);
374            };
375        }
376        macro_rules! test_from_float {
377            ($json:path, $metric:path, $value:expr) => {
378                test_from_to!($json, $metric, JsonNumber::from_f64($value).unwrap(), $value);
379            };
380        }
381        macro_rules! test_from_to {
382            ($json:path, $metric:path, $json_value:expr, $metric_value:expr) => {
383                let metric_value = $metric($metric_value);
384                let json_value = $json($json_value);
385                assert_eq!(metric_value, MetricValue::from(json_value));
386            };
387        }
388        test_from!(JsonValue::String, MetricValue::String, "Hi World".to_string());
389        test_from_int!(JsonValue::Number, MetricValue::Int, 3);
390        test_from_int!(JsonValue::Number, MetricValue::Int, i64::MAX);
391        test_from_int!(JsonValue::Number, MetricValue::Int, i64::MIN);
392        test_from_to!(JsonValue::Number, MetricValue::Int, JsonNumber::from(u64::MAX), -1);
393        test_from_float!(JsonValue::Number, MetricValue::Float, std::f64::consts::PI);
394        test_from_float!(JsonValue::Number, MetricValue::Float, f64::MAX);
395        test_from_float!(JsonValue::Number, MetricValue::Float, f64::MIN);
396        test_from!(JsonValue::Bool, MetricValue::Bool, true);
397        test_from!(JsonValue::Bool, MetricValue::Bool, false);
398        let json_vec = vec![json!(1), json!(2), json!(3)];
399        let metric_vec = vec![MetricValue::Int(1), MetricValue::Int(2), MetricValue::Int(3)];
400        test_from_to!(JsonValue::Array, MetricValue::Vector, json_vec, metric_vec);
401        assert_problem!(
402            MetricValue::from(JsonValue::Object(serde_json::Map::new())),
403            "UnhandledType: Unsupported JSON type"
404        );
405    }
406
407    #[fuchsia::test]
408    fn metric_value_from_diagnostic_property() {
409        /*
410            DiagnosticProperty subtypes:
411                String(Key, String),
412                Bytes(Key, Vec<u8>),
413                Int(Key, i64),
414                Uint(Key, u64),
415                Double(Key, f64),
416                Bool(Key, bool),
417                DoubleArray(Key, ArrayContent<f64>),
418                IntArray(Key, ArrayContent<i64>),
419                UintArray(Key, ArrayContent<u64>),
420                DoubleArray(Key, LinearHistogram<f64>),
421                IntArray(Key, LinearHistogram<i64>),
422                UintArray(Key, LinearHistogram<u64>),
423                DoubleArray(Key, ExponentialHistogram<f64>),
424                IntArray(Key, ExponentialHistogram<i64>),
425                UintArray(Key, ExponentialHistogram<u64>),
426        */
427        macro_rules! test_from {
428            ($diagnostic:path, $metric:path, $value:expr) => {
429                test_from_to!($diagnostic, $metric, $value, $value);
430            };
431        }
432        macro_rules! test_from_to {
433            ($diagnostic:path, $metric:path, $diagnostic_value:expr, $metric_value:expr) => {
434                assert_eq!(
435                    $metric($metric_value),
436                    MetricValue::from($diagnostic("foo".to_string(), $diagnostic_value))
437                );
438            };
439        }
440        test_from!(DiagnosticProperty::String, MetricValue::String, "Hi World".to_string());
441        test_from!(DiagnosticProperty::Bytes, MetricValue::Bytes, vec![1, 2, 3]);
442        test_from!(DiagnosticProperty::Int, MetricValue::Int, 3);
443        test_from!(DiagnosticProperty::Int, MetricValue::Int, i64::MAX);
444        test_from!(DiagnosticProperty::Int, MetricValue::Int, i64::MIN);
445        test_from!(DiagnosticProperty::Uint, MetricValue::Int, 3);
446        test_from_to!(DiagnosticProperty::Uint, MetricValue::Int, u64::MAX, -1);
447        test_from!(DiagnosticProperty::Double, MetricValue::Float, std::f64::consts::PI);
448        test_from!(DiagnosticProperty::Double, MetricValue::Float, f64::MAX);
449        test_from!(DiagnosticProperty::Double, MetricValue::Float, f64::MIN);
450        test_from!(DiagnosticProperty::Bool, MetricValue::Bool, true);
451        test_from!(DiagnosticProperty::Bool, MetricValue::Bool, false);
452        let diagnostic_array = ArrayContent::Values(vec![1.5, 2.5, 3.5]);
453        test_from_to!(
454            DiagnosticProperty::DoubleArray,
455            MetricValue::Vector,
456            diagnostic_array,
457            vec![MetricValue::Float(1.5), MetricValue::Float(2.5), MetricValue::Float(3.5)]
458        );
459        let diagnostic_array = ArrayContent::Values(vec![1, 2, 3]);
460        test_from_to!(
461            DiagnosticProperty::IntArray,
462            MetricValue::Vector,
463            diagnostic_array,
464            vec![MetricValue::Int(1), MetricValue::Int(2), MetricValue::Int(3)]
465        );
466        let diagnostic_array = ArrayContent::Values(vec![1, 2, 3]);
467        test_from_to!(
468            DiagnosticProperty::UintArray,
469            MetricValue::Vector,
470            diagnostic_array,
471            vec![MetricValue::Int(1), MetricValue::Int(2), MetricValue::Int(3)]
472        );
473
474        let diagnostic_array = ArrayContent::new(vec![0, 1, 0, 0, 0], ArrayFormat::LinearHistogram)
475            .expect("create histogram");
476        assert_problem!(
477            DiagnosticProperty::UintArray("foo".to_string(), diagnostic_array).into(),
478            "UnhandledType: Histogram is not supported"
479        );
480
481        let diagnostic_array =
482            ArrayContent::new(vec![-10, 1, 0, 0, 0], ArrayFormat::LinearHistogram)
483                .expect("create histogram");
484        assert_problem!(
485            DiagnosticProperty::IntArray("foo".to_string(), diagnostic_array).into(),
486            "UnhandledType: Histogram is not supported"
487        );
488
489        let diagnostic_array =
490            ArrayContent::new(vec![0., 0.1, 0., 0., 0.], ArrayFormat::LinearHistogram)
491                .expect("create histogram");
492        assert_problem!(
493            DiagnosticProperty::DoubleArray("foo".to_string(), diagnostic_array).into(),
494            "UnhandledType: Histogram is not supported"
495        );
496    }
497}