fuchsia_triage/plugins/
memory.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::Plugin;
6use crate::act::Action;
7use crate::metrics::fetch::{FileDataFetcher, SelectorString};
8use crate::metrics::metric_value::MetricValue;
9use itertools::Itertools;
10
11pub struct MemoryPlugin();
12
13const SELECTOR: &str = "INSPECT:core/memory_monitor:root:current_digest";
14
15impl Plugin for MemoryPlugin {
16    fn name(&self) -> &'static str {
17        "memory"
18    }
19
20    fn display_name(&self) -> &'static str {
21        "Memory Summary"
22    }
23
24    fn run_structured(&self, inputs: &FileDataFetcher<'_>) -> Vec<Action> {
25        let mut results = Vec::new();
26        let val = match inputs
27            .inspect
28            .fetch(&SelectorString::try_from(SELECTOR.to_string()).expect("invalid selector"))
29            .into_iter()
30            .next()
31        {
32            Some(MetricValue::String(val)) => val,
33            _ => {
34                // Short circuit if value could not be found. This is not an error.
35                return results;
36            }
37        };
38
39        val.lines()
40            .filter_map(|line| {
41                let mut split = line.split(": ");
42                let (name, value) = (split.next(), split.next());
43                match (name, value) {
44                    (Some(name), Some(value)) => {
45                        if value.is_empty() || name == "Free" || name == "timestamp" {
46                            return None;
47                        }
48                        let numeric = value.trim_matches(|c: char| !c.is_ascii_digit());
49                        let (mult, parsed) = if value.ends_with("k") {
50                            (1_000f64, numeric.parse::<f64>().ok())
51                        } else if value.ends_with("M") {
52                            (1_000_000f64, numeric.parse::<f64>().ok())
53                        } else if value.ends_with("G") {
54                            (1_000_000_000f64, numeric.parse::<f64>().ok())
55                        } else {
56                            (1f64, numeric.parse::<f64>().ok())
57                        };
58
59                        match parsed{
60                            Some(parsed) => Some((name, value, mult*parsed)),
61                            None => {
62                                results.push(Action::new_synthetic_error(
63                                    format!(
64                                        "[DEBUG: BAD DATA] Could not parse '{value}' as a valid size. Something is wrong with the output of memory_monitor.",
65                                    ),
66                                    "Tools>ffx>Profile>Memory".to_string(),
67                                ));
68                                None
69                            }
70                        }
71
72                    }
73                    _ => None,
74                }
75            })
76            .sorted_by(|a, b| a.2.partial_cmp(&b.2).unwrap())
77            .rev()
78            .for_each(|entry| {
79                results.push(Action::new_synthetic_string_gauge(entry.1.to_string(), None, Some(entry.0.to_string())));
80            });
81
82        results
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::metrics::fetch::InspectFetcher;
90
91    #[fuchsia::test]
92    fn test_crashes() {
93        let expected_gauges: Vec<String> =
94            vec!["TestCmx: 2G", "Other: 7M", "Abcd: 10.3k", "Bbb: 9999"]
95                .into_iter()
96                .map(|s| s.to_string())
97                .collect();
98        let expected_errors: Vec<String> =
99            vec!["[DEBUG: BAD DATA] Could not parse 'ABCD' as a valid size. Something is wrong with the output of memory_monitor."]
100                .into_iter()
101                .map(|s| s.to_string())
102                .collect();
103        let fetcher: InspectFetcher = r#"
104[
105  {
106    "moniker": "core/memory_monitor",
107    "metadata": {
108        "name": "root"
109    },
110    "payload": {
111        "root": {
112            "current_digest": "Abcd: 10.3k\nOther: 7M\nBbb: 9999\n\nTestCmx: 2G\ninvalid_line\ninvalid: \ninvalid_again: ABCD\n\nFree: 100M\ntimestamp: 10234\n\n"
113        }
114    }
115  }
116]
117"#
118        .try_into().expect("failed to parse inspect");
119
120        let empty_diagnostics_vec = Vec::new();
121
122        let mut inputs = FileDataFetcher::new(&empty_diagnostics_vec);
123        inputs.inspect = &fetcher;
124        let result = MemoryPlugin {}.run(&inputs);
125        assert_eq!(result.gauges, expected_gauges);
126        assert_eq!(result.errors, expected_errors);
127    }
128}