inspect_fidl_load/
lib.rs

1// Copyright 2019 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 anyhow::{format_err, Error};
6use fidl_fuchsia_inspect_deprecated::{InspectProxy, MetricValue, PropertyValue};
7use fuchsia_async as fasync;
8use fuchsia_inspect::reader::{DiagnosticsHierarchy, Property};
9
10/// Loads an inspect node hierarchy in the given path.
11pub async fn load_hierarchy_from_path(path: &str) -> Result<DiagnosticsHierarchy, Error> {
12    let (client, server) = zx::Channel::create();
13    fdio::service_connect(path, server)?;
14    let inspect_proxy = InspectProxy::new(fasync::Channel::from_channel(client));
15    let hierarchy = load_hierarchy(inspect_proxy).await?;
16    Ok(hierarchy)
17}
18
19/// Loads an inspect node hierarchy from the given root inspect node.
20pub async fn load_hierarchy(proxy: InspectProxy) -> Result<DiagnosticsHierarchy, Error> {
21    let mut pending_nodes = vec![];
22    let root = read_node(proxy).await?;
23    pending_nodes.push(root);
24    while let Some(mut current_node) = pending_nodes.pop() {
25        if current_node.pending_children.is_empty() {
26            if pending_nodes.is_empty() {
27                return Ok(current_node.hierarchy);
28            }
29            match pending_nodes.pop() {
30                Some(mut parent) => {
31                    parent.hierarchy.children.push(current_node.hierarchy);
32                    pending_nodes.push(parent);
33                }
34                None => return Err(format_err!("failed to load hierarchy")),
35            }
36        } else {
37            let next_child = current_node.pending_children.pop().unwrap();
38            let (client, server) = zx::Channel::create();
39            current_node
40                .proxy
41                .open_child(&next_child, fidl::endpoints::ServerEnd::new(server))
42                .await?;
43            let child_proxy = InspectProxy::new(fasync::Channel::from_channel(client));
44            let child_node = read_node(child_proxy).await?;
45            pending_nodes.push(current_node);
46            pending_nodes.push(child_node);
47        }
48    }
49    Err(format_err!("failed to load hierarchy"))
50}
51
52struct PartialNodeHierarchy {
53    proxy: InspectProxy,
54    hierarchy: DiagnosticsHierarchy,
55    pending_children: Vec<String>,
56}
57
58/// Loads an inspect hierarchy from a deprecated FIDL service.
59async fn read_node(proxy: InspectProxy) -> Result<PartialNodeHierarchy, Error> {
60    let object = proxy.read_data().await?;
61    let mut properties = object
62        .properties
63        .into_iter()
64        .map(|property| match property.value {
65            PropertyValue::Str(v) => Property::String(property.key, v),
66            PropertyValue::Bytes(v) => Property::Bytes(property.key, v),
67        })
68        .collect::<Vec<Property>>();
69
70    properties.extend(object.metrics.into_iter().map(|metric| match metric.value {
71        MetricValue::IntValue(v) => Property::Int(metric.key, v),
72        MetricValue::DoubleValue(v) => Property::Double(metric.key, v),
73        MetricValue::UintValue(v) => Property::Uint(metric.key, v),
74    }));
75
76    let pending_children = proxy.list_children().await?;
77    let hierarchy = DiagnosticsHierarchy::new(&object.name, properties, vec![]);
78    Ok(PartialNodeHierarchy { proxy, hierarchy, pending_children })
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use diagnostics_assertions::assert_data_tree;
85    use fidl_fuchsia_inspect_deprecated::{
86        InspectMarker, InspectRequest, InspectRequestStream, Metric, Object, Property,
87    };
88    use futures::{TryFutureExt, TryStreamExt};
89    use maplit::{hashmap, hashset};
90    use std::collections::{HashMap, HashSet};
91    use std::sync::LazyLock;
92
93    #[fuchsia::test]
94    async fn test_load_hierarchy() -> Result<(), Error> {
95        let (client_proxy, server_stream) =
96            fidl::endpoints::create_proxy_and_stream::<InspectMarker>();
97        spawn_server(server_stream, "root".to_string());
98        let hierarchy = load_hierarchy(client_proxy).await?;
99        assert_data_tree!(hierarchy, root: {
100            double_value: 5.2,
101            a: {
102                int_value: -3i64,
103                uint_value: 2u64,
104                c: {}
105            },
106            b: {
107                bytes_value: vec![0x12u8, 0x34, 0x56],
108                string_value: "test",
109            }
110        });
111        Ok(())
112    }
113
114    static OBJECTS: LazyLock<HashMap<String, TestObject>> = LazyLock::new(|| {
115        hashmap! {
116            "root".to_string() => TestObject {
117                object: Object {
118                    name: "root".to_string(),
119                    metrics: vec![
120                        Metric {
121                            key: "double_value".to_string(),
122                            value: MetricValue::DoubleValue(5.2),
123                        },
124                    ],
125                    properties: vec![],
126                },
127                children: hashset!("a".to_string(), "b".to_string()),
128            },
129            "a".to_string() => TestObject {
130                object: Object {
131                    name: "a".to_string(),
132                    metrics: vec![
133                        Metric {
134                            key: "int_value".to_string(),
135                            value: MetricValue::IntValue(-3),
136                        },
137                        Metric {
138                            key: "uint_value".to_string(),
139                            value: MetricValue::UintValue(2),
140                        },
141                    ],
142                    properties: vec![],
143                },
144                children: hashset!("c".to_string()),
145            },
146            "b".to_string() => TestObject {
147                object: Object {
148                    name: "b".to_string(),
149                    metrics: vec![],
150                    properties: vec![
151                        Property{
152                            key: "string_value".to_string(),
153                            value: PropertyValue::Str("test".to_string()),
154                        },
155                        Property {
156                            key: "bytes_value".to_string(),
157                            value: PropertyValue::Bytes(vec![0x12u8, 0x34, 0x56]),
158                        },
159                    ],
160                },
161                children: hashset!(),
162            },
163            "c".to_string() => TestObject {
164                object: Object {
165                    name: "c".to_string(),
166                    metrics: vec![],
167                    properties: vec![],
168                },
169                children: hashset!(),
170            }
171        }
172    });
173
174    struct TestObject {
175        object: Object,
176        children: HashSet<String>,
177    }
178
179    fn spawn_server(mut stream: InspectRequestStream, object_name: String) {
180        fasync::Task::spawn(
181            async move {
182                let object = OBJECTS.get(&object_name).unwrap();
183                while let Some(req) = stream.try_next().await? {
184                    match req {
185                        InspectRequest::ReadData { responder } => {
186                            responder.send(&object.object)?;
187                        }
188                        InspectRequest::ListChildren { responder } => {
189                            responder.send(&object.children.iter().cloned().collect::<Vec<_>>())?;
190                        }
191                        InspectRequest::OpenChild { child_name, child_channel, responder } => {
192                            let stream = child_channel.into_stream();
193                            if object.children.contains(&child_name) {
194                                spawn_server(stream, child_name);
195                                responder.send(true)?;
196                            } else {
197                                responder.send(false)?;
198                            }
199                        }
200                    }
201                }
202                Ok(())
203            }
204            .unwrap_or_else(|e: Error| eprintln!("error running inspect server: {:?}", e)),
205        )
206        .detach();
207    }
208}