persistence/
inspect_server.rs

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
// 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::file_handler;
use anyhow::Error;
use serde_json::{json, Value as JsonValue};
use tracing::*;

// Make sure extremely deep-tree data doesn't overflow a stack.
const MAX_TREE_DEPTH: u32 = 128;

fn store_data(inspect_node: &fuchsia_inspect::Node, name: &str, data: &JsonValue, depth: u32) {
    if depth > MAX_TREE_DEPTH {
        return;
    }
    match data {
        JsonValue::String(value) => inspect_node.record_string(name, value),
        JsonValue::Number(value) => {
            if value.is_i64() {
                inspect_node.record_int(name, value.as_i64().unwrap());
            } else if value.is_u64() {
                inspect_node.record_uint(name, value.as_u64().unwrap());
            } else if value.is_f64() {
                inspect_node.record_double(name, value.as_f64().unwrap());
            }
        }
        JsonValue::Bool(value) => inspect_node.record_bool(name, *value),
        JsonValue::Object(object) => inspect_node.record_child(name, |node| {
            for (name, value) in object.iter() {
                store_data(node, name, value, depth + 1);
            }
        }),
        JsonValue::Null => {}
        // TODO(https://fxbug.dev/42150693): If the array is all numbers, publish them (and test).
        JsonValue::Array(_) => {}
    }
}

pub fn serve_persisted_data(persist_root: &fuchsia_inspect::Node) -> Result<(), Error> {
    let remembered_data = file_handler::remembered_data()?;
    for service in remembered_data {
        persist_root.record_child(service.name, |service_node| {
            for tag in service.data {
                let json_data = serde_json::from_str(&tag.data).unwrap_or_else(|err| {
                    error!("Error {:?} parsing stored data", err);
                    json!("<<Error parsing saved data>>")
                });
                store_data(service_node, &tag.name, &json_data, 0);
            }
        });
    }
    Ok(())
}

#[cfg(test)]
mod test {
    use super::*;
    use diagnostics_assertions::assert_data_tree;
    use fuchsia_inspect::Inspector;

    // This tests all the types, and also the stack-safety mechanism by including data that should
    // be clipped.
    #[fuchsia::test]
    fn test_json_export() -> Result<(), Error> {
        let inspector = Inspector::default();
        let inspect = inspector.root();
        assert_data_tree!(
            inspector,
            root: contains {
            }
        );

        let data_json = "{ negint: -5, int: 42, unsigned: 0, float: 45.6, \
                         bool: true, obj: { child: 'child', grandchild: { hello: 'world', \
                         great_grand: { should_be_clipped: true } } } }";
        let mut data_parsed = serde_json5::from_str::<serde_json::Value>(data_json)?;
        // serde_json5::from_str() won't parse a number this big, so I have to insert it afterward.
        *data_parsed.get_mut("unsigned").unwrap() = serde_json::json!(9223372036854775808u64);
        store_data(inspect, "types", &data_parsed, MAX_TREE_DEPTH - 3);

        assert_data_tree!(
            inspector,
            root: {
                types: {
                    negint: -5i64,
                    int: 42i64,
                    unsigned: 9223372036854775808u64,
                    float: 45.6f64,
                    bool: true,
                    obj: {
                        child: "child",
                        grandchild: {
                            hello: "world",
                            great_grand: {}
                        }
                    }
                }
            }
        );
        Ok(())
    }
}