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