1use crate::reader::error::ReaderError;
6use crate::reader::{
7 DiagnosticsHierarchy, MissingValueReason, PartialNodeHierarchy, ReadableTree, Snapshot,
8};
9use crate::{NumericProperty, UintProperty};
10use fuchsia_async::{DurationExt, TimeoutExt};
11use fuchsia_sync::Mutex;
12use futures::prelude::*;
13use futures::stream::FuturesUnordered;
14use inspect_format::LinkNodeDisposition;
15use std::borrow::Cow;
16use std::collections::BTreeMap;
17use std::pin::Pin;
18use std::sync::{Arc, Weak};
19use std::time::Duration;
20
21const MAX_READ_TIME: std::time::Duration = std::time::Duration::from_secs(60 * 10);
26
27#[derive(Debug)]
29pub struct SnapshotTree {
30 snapshot: Snapshot,
31 children: SnapshotTreeMap,
32}
33
34impl SnapshotTree {
35 #[cfg(target_os = "fuchsia")]
37 pub async fn try_from_proxy(
38 tree: &fidl_fuchsia_inspect::TreeProxy,
39 ) -> Result<SnapshotTree, ReaderError> {
40 load_snapshot_tree(tree, MAX_READ_TIME, &Default::default()).await
41 }
42
43 pub async fn try_from_with_timeout<T: ReadableTree + Send + Sync + Clone>(
44 tree: &T,
45 lazy_child_timeout: Duration,
46 timeout_counter: &UintProperty,
47 ) -> Result<SnapshotTree, ReaderError> {
48 load_snapshot_tree(tree, lazy_child_timeout, timeout_counter).await
49 }
50}
51
52impl<'a, T> TryFrom<Trees<'a, T>> for SnapshotTree
53where
54 T: ReadableTree + Send + Sync + Clone,
55{
56 type Error = ReaderError;
57
58 fn try_from(trees: Trees<'a, T>) -> Result<Self, Self::Error> {
59 let snapshot =
60 trees.resolved_node.into_inner().expect("lazy initialization must be performed")?;
61
62 let mut children_map: SnapshotTreeMap = BTreeMap::new();
63
64 for child_arc in trees.children.into_inner().into_iter() {
66 if child_arc.name.is_some() {
68 let Some(mut child) = Arc::into_inner(child_arc) else {
69 return Err(ReaderError::Internal);
70 };
71 let name = child.name.take().unwrap_or_default();
72 let child_result = SnapshotTree::try_from(child);
73
74 children_map.insert(name, child_result);
75 }
76 }
78
79 for (name, error) in trees.child_errors.into_inner().into_iter() {
80 children_map.insert(name, Err(error));
81 }
82
83 Ok(SnapshotTree { snapshot, children: children_map })
84 }
85}
86
87type SnapshotTreeMap = BTreeMap<String, Result<SnapshotTree, ReaderError>>;
88
89impl TryInto<DiagnosticsHierarchy> for SnapshotTree {
90 type Error = ReaderError;
91
92 fn try_into(mut self) -> Result<DiagnosticsHierarchy, Self::Error> {
93 let partial = PartialNodeHierarchy::try_from(self.snapshot)?;
94 Ok(expand(partial, &mut self.children))
95 }
96}
97
98fn expand(
99 partial: PartialNodeHierarchy,
100 snapshot_children: &mut SnapshotTreeMap,
101) -> DiagnosticsHierarchy {
102 let children =
104 partial.children.into_iter().map(|child| expand(child, snapshot_children)).collect();
105 let mut hierarchy = DiagnosticsHierarchy::new(partial.name, partial.properties, children);
106 for link_value in partial.links {
107 let Some(result) = snapshot_children.remove(&link_value.content) else {
108 hierarchy.add_missing(MissingValueReason::LinkNotFound, link_value.name);
109 continue;
110 };
111
112 let result: Result<DiagnosticsHierarchy, ReaderError> =
114 result.and_then(|snapshot_tree| snapshot_tree.try_into());
115 match result {
116 Err(ReaderError::TreeTimedOut) => {
117 hierarchy.add_missing(MissingValueReason::Timeout, link_value.name);
118 }
119 Err(_) => {
120 hierarchy.add_missing(MissingValueReason::LinkParseFailure, link_value.name);
121 }
122 Ok(mut child_hierarchy) => match link_value.disposition {
123 LinkNodeDisposition::Child => {
124 child_hierarchy.name = link_value.name;
125 hierarchy.children.push(child_hierarchy);
126 }
127 LinkNodeDisposition::Inline => {
128 hierarchy.children.extend(child_hierarchy.children.into_iter());
129 hierarchy.properties.extend(child_hierarchy.properties.into_iter());
130 hierarchy.missing.extend(child_hierarchy.missing.into_iter());
131 }
132 },
133 }
134 }
135 hierarchy
136}
137
138pub async fn read<T>(tree: &T) -> Result<DiagnosticsHierarchy, ReaderError>
141where
142 T: ReadableTree + Send + Sync + Clone,
143{
144 load_snapshot_tree(tree, MAX_READ_TIME, &Default::default()).await?.try_into()
145}
146
147pub async fn read_with_timeout<T>(
150 tree: &T,
151 lazy_node_timeout: Duration,
152 timeout_counter: &UintProperty,
153) -> Result<DiagnosticsHierarchy, ReaderError>
154where
155 T: ReadableTree + Send + Sync + Clone,
156{
157 load_snapshot_tree(tree, lazy_node_timeout, timeout_counter).await?.try_into()
158}
159
160struct Trees<'a, T: Clone> {
163 node: Cow<'a, T>,
164 name: Option<String>,
165 resolved_node: Mutex<Option<Result<Snapshot, ReaderError>>>,
166 children: Mutex<Vec<Arc<Trees<'a, T>>>>,
168 parent: Mutex<Option<Weak<Trees<'a, T>>>>,
169 child_errors: Mutex<BTreeMap<String, ReaderError>>,
170}
171
172async fn load_snapshot_tree<'a, T>(
173 tree: &'a T,
174 timeout: Duration,
175 timeout_counter: &UintProperty,
176) -> Result<SnapshotTree, ReaderError>
177where
178 T: ReadableTree + Send + Sync + Clone,
179{
180 let trees = Arc::new(Trees {
181 node: Cow::Borrowed(tree),
182 name: None,
183 children: Mutex::new(vec![]),
184 parent: Mutex::new(None),
185 resolved_node: Mutex::new(None),
186 child_errors: Mutex::new(BTreeMap::new()),
187 });
188 let vmo_resolver = FuturesUnordered::new();
189 let moveable_for_read_tree_queue = Arc::clone(&trees);
190 let mut read_tree_resolver = FuturesUnordered::<
191 Pin<Box<dyn Future<Output = Option<Arc<Trees<'a, T>>>> + Send>>,
192 >::from_iter([
193 async move { Some(moveable_for_read_tree_queue) }.boxed(),
194 ]);
195
196 while let Some(current) = read_tree_resolver.next().await {
197 let Some(current) = current else {
198 continue;
199 };
200 let moveable = Arc::clone(¤t);
201 vmo_resolver.push(async move {
202 let resolution = moveable
203 .node
204 .vmo()
205 .on_timeout(timeout.after_now(), || {
206 timeout_counter.add(1);
207 Err(ReaderError::TreeTimedOut)
208 })
209 .await
210 .and_then(|data| Snapshot::try_from(&data));
211 let _ = moveable.resolved_node.lock().insert(resolution);
212 });
213 let tree_names = match current
214 .node
215 .tree_names()
216 .on_timeout(timeout.after_now(), || {
217 Err(ReaderError::TreeTimedOut)
220 })
221 .await
222 {
223 Ok(tree_names) => tree_names,
224 Err(err) => {
225 let guard = current.parent.lock();
226 let Some(Some(parent)) = guard.as_ref().map(|weak| weak.upgrade()) else {
227 continue;
231 };
232 if let Some(name) = ¤t.name {
233 parent.child_errors.lock().insert(String::clone(name), err);
234 }
235 continue;
236 }
237 };
238
239 for child in tree_names.into_iter() {
240 let current = Arc::clone(¤t);
241 let next_node = async move {
242 current
243 .node
244 .read_tree(&child)
245 .map(|node| match node {
246 Ok(node) => {
247 let next = Arc::new(Trees {
248 node: Cow::Owned(node),
249 name: Some(String::clone(&child)),
250 resolved_node: Mutex::new(None),
251 children: Mutex::new(vec![]),
252 parent: Mutex::new(Some(Arc::downgrade(¤t))),
253 child_errors: Mutex::new(BTreeMap::new()),
254 });
255 let mut guard = current.children.lock();
256 guard.push(next);
257 guard.last().map(Arc::clone)
258 }
259 Err(e) => {
260 current.child_errors.lock().insert(String::clone(&child), e);
261 None
262 }
263 })
264 .on_timeout(timeout.after_now(), || {
265 timeout_counter.add(1);
266 current
267 .child_errors
268 .lock()
269 .insert(String::clone(&child), ReaderError::TreeTimedOut);
270 None
271 })
272 .await
273 };
274 read_tree_resolver.push(next_node.boxed());
275 }
276 }
277
278 let _ = vmo_resolver.collect::<Vec<()>>().await;
279 Arc::into_inner(trees).map(SnapshotTree::try_from).unwrap_or(Err(ReaderError::Internal))
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use crate::{reader, Inspector, InspectorConfig};
286 use diagnostics_assertions::{assert_data_tree, assert_json_diff};
287 use inspect_format::constants;
288
289 #[fuchsia::test]
290 async fn test_read() -> Result<(), anyhow::Error> {
291 let inspector = test_inspector();
292 let hierarchy = read(&inspector).await?;
293 assert_data_tree!(hierarchy, root: {
294 int: 3i64,
295 "lazy-node": {
296 a: "test",
297 child: {
298 double: 3.25,
299 },
300 }
301 });
302 Ok(())
303 }
304
305 #[fuchsia::test]
306 async fn test_load_snapshot_tree() -> Result<(), anyhow::Error> {
307 let instrumentation = Inspector::default();
308 let counter = instrumentation.root().create_uint("counter", 0);
309
310 let inspector = test_inspector();
311 let mut snapshot_tree = load_snapshot_tree(&inspector, MAX_READ_TIME, &counter).await?;
312
313 assert_data_tree!(instrumentation, root: { counter: 0u64 });
314
315 let root_hierarchy: DiagnosticsHierarchy =
316 PartialNodeHierarchy::try_from(snapshot_tree.snapshot)?.into();
317 assert_eq!(snapshot_tree.children.keys().collect::<Vec<&String>>(), vec!["lazy-node-0"]);
318 assert_data_tree!(root_hierarchy, root: {
319 int: 3i64,
320 });
321
322 let mut lazy_node = snapshot_tree.children.remove("lazy-node-0").unwrap().unwrap();
323 let lazy_node_hierarchy: DiagnosticsHierarchy =
324 PartialNodeHierarchy::try_from(lazy_node.snapshot)?.into();
325 assert_eq!(lazy_node.children.keys().collect::<Vec<&String>>(), vec!["lazy-values-0"]);
326 assert_data_tree!(lazy_node_hierarchy, root: {
327 a: "test",
328 child: {},
329 });
330
331 let lazy_values = lazy_node.children.remove("lazy-values-0").unwrap().unwrap();
332 let lazy_values_hierarchy = PartialNodeHierarchy::try_from(lazy_values.snapshot)?;
333 assert_eq!(lazy_values.children.keys().len(), 0);
334 assert_data_tree!(lazy_values_hierarchy, root: {
335 double: 3.25,
336 });
337
338 Ok(())
339 }
340
341 #[fuchsia::test]
342 async fn read_with_hanging_lazy_node() -> Result<(), anyhow::Error> {
343 let instrumentation = Inspector::default();
344 let counter = instrumentation.root().create_uint("counter", 0);
345
346 let inspector = Inspector::default();
347 let root = inspector.root();
348 root.record_string("child", "value");
349
350 root.record_lazy_values("lazy-node-always-hangs", || {
351 async move {
352 fuchsia_async::Timer::new(Duration::from_secs(30 * 60).after_now()).await;
353 Ok(Inspector::default())
354 }
355 .boxed()
356 });
357
358 root.record_int("int", 3);
359
360 let hierarchy = read_with_timeout(&inspector, Duration::from_secs(2), &counter).await?;
361
362 assert_data_tree!(instrumentation, root: { counter: 1u64 });
363
364 assert_json_diff!(hierarchy, root: {
365 child: "value",
366 int: 3i64,
367 });
368
369 Ok(())
370 }
371
372 #[fuchsia::test]
373 async fn read_too_big_string() {
374 let magic_size_found_by_experiment = 259076;
377 let inspector =
378 Inspector::new(InspectorConfig::default().size(constants::DEFAULT_VMO_SIZE_BYTES));
379 let string_head = "X".repeat(magic_size_found_by_experiment);
380 let string_tail =
381 "Y".repeat((constants::DEFAULT_VMO_SIZE_BYTES * 2) - magic_size_found_by_experiment);
382 let full_string = format!("{string_head}{string_tail}");
383
384 inspector.root().record_int(full_string, 5);
385 let hierarchy = reader::read(&inspector).await.unwrap();
386 assert_eq!(hierarchy.properties[0].key().len(), string_head.len());
389 assert_eq!(hierarchy.properties[0].key(), &string_head);
390 }
391
392 #[fuchsia::test]
393 async fn missing_value_parse_failure() -> Result<(), anyhow::Error> {
394 let inspector = Inspector::default();
395 let _lazy_child = inspector.root().create_lazy_child("lazy", || {
396 async move {
397 Ok(Inspector::new(InspectorConfig::default().no_op()))
399 }
400 .boxed()
401 });
402 let hierarchy = reader::read(&inspector).await?;
403 assert_eq!(hierarchy.missing.len(), 1);
404 assert_eq!(hierarchy.missing[0].reason, MissingValueReason::LinkParseFailure);
405 assert_data_tree!(hierarchy, root: {});
406 Ok(())
407 }
408
409 #[fuchsia::test]
410 async fn missing_value_not_found() -> Result<(), anyhow::Error> {
411 let inspector = Inspector::default();
412 if let Some(state) = inspector.state() {
413 let mut state = state.try_lock().expect("lock state");
414 state
415 .allocate_link("missing", "missing-404", LinkNodeDisposition::Child, 0.into())
416 .unwrap();
417 }
418 let hierarchy = reader::read(&inspector).await?;
419 assert_eq!(hierarchy.missing.len(), 1);
420 assert_eq!(hierarchy.missing[0].reason, MissingValueReason::LinkNotFound);
421 assert_eq!(hierarchy.missing[0].name, "missing");
422 assert_data_tree!(hierarchy, root: {});
423 Ok(())
424 }
425
426 fn test_inspector() -> Inspector {
427 let inspector = Inspector::default();
428 let root = inspector.root();
429 root.record_int("int", 3);
430 root.record_lazy_child("lazy-node", || {
431 async move {
432 let inspector = Inspector::default();
433 inspector.root().record_string("a", "test");
434 let child = inspector.root().create_child("child");
435 child.record_lazy_values("lazy-values", || {
436 async move {
437 let inspector = Inspector::default();
438 inspector.root().record_double("double", 3.25);
439 Ok(inspector)
440 }
441 .boxed()
442 });
443 inspector.root().record(child);
444 Ok(inspector)
445 }
446 .boxed()
447 });
448 inspector
449 }
450
451 #[fuchsia::test]
452 async fn try_from_trees_for_snapshot_tree() {
453 let inspector_root = Inspector::default();
455 inspector_root.root().record_int("val", 1);
456 let vmo_root = inspector_root.vmo().await.unwrap();
457 let snapshot_root = Snapshot::try_from(&vmo_root).unwrap();
458
459 let inspector_child1 = Inspector::default();
460 inspector_child1.root().record_int("val", 2);
461 let vmo_child1 = inspector_child1.vmo().await.unwrap();
462 let snapshot_child1 = Snapshot::try_from(&vmo_child1).unwrap();
463
464 let child1 = Arc::new(Trees::<Inspector> {
465 node: Cow::Owned(inspector_child1),
466 name: Some("child1".to_string()),
467 resolved_node: Mutex::new(Some(Ok(snapshot_child1))),
468 children: Mutex::new(vec![]),
469 parent: Mutex::new(None),
470 child_errors: Mutex::new(BTreeMap::new()),
471 });
472
473 let mut child_errors = BTreeMap::new();
474 child_errors.insert("child2".to_string(), ReaderError::TreeTimedOut);
475
476 let root_trees = Trees::<Inspector> {
477 node: Cow::Owned(inspector_root),
478 name: Some("root".to_string()),
479 resolved_node: Mutex::new(Some(Ok(snapshot_root))),
480 children: Mutex::new(vec![child1]),
481 parent: Mutex::new(None),
482 child_errors: Mutex::new(child_errors),
483 };
484
485 let mut snapshot_tree = SnapshotTree::try_from(root_trees).unwrap();
487
488 let root_hierarchy: DiagnosticsHierarchy =
490 PartialNodeHierarchy::try_from(snapshot_tree.snapshot).unwrap().into();
491 assert_data_tree!(root_hierarchy, root: {
492 val: 1i64,
493 });
494
495 assert_eq!(snapshot_tree.children.len(), 2);
496
497 let child1_tree = snapshot_tree.children.remove("child1").unwrap().unwrap();
499 let child1_hierarchy: DiagnosticsHierarchy =
500 PartialNodeHierarchy::try_from(child1_tree.snapshot).unwrap().into();
501 assert_data_tree!(child1_hierarchy, root: {
502 val: 2i64,
503 });
504 assert!(child1_tree.children.is_empty());
505
506 let child2_error = snapshot_tree.children.remove("child2").unwrap().unwrap_err();
508 assert!(matches!(child2_error, ReaderError::TreeTimedOut));
509 }
510
511 #[fuchsia::test]
512 async fn try_from_trees_for_snapshot_tree_child_with_no_name() {
513 let inspector_root = Inspector::default();
515 inspector_root.root().record_int("val", 1);
516 let vmo_root = inspector_root.vmo().await.unwrap();
517 let snapshot_root = Snapshot::try_from(&vmo_root).unwrap();
518
519 let inspector_child1 = Inspector::default();
520 inspector_child1.root().record_int("val", 2);
521 let vmo_child1 = inspector_child1.vmo().await.unwrap();
522 let snapshot_child1 = Snapshot::try_from(&vmo_child1).unwrap();
523
524 let child1 = Arc::new(Trees::<Inspector> {
525 node: Cow::Owned(inspector_child1),
526 name: None, resolved_node: Mutex::new(Some(Ok(snapshot_child1))),
528 children: Mutex::new(vec![]),
529 parent: Mutex::new(None),
530 child_errors: Mutex::new(BTreeMap::new()),
531 });
532
533 let root_trees = Trees::<Inspector> {
534 node: Cow::Owned(inspector_root),
535 name: Some("root".to_string()),
536 resolved_node: Mutex::new(Some(Ok(snapshot_root))),
537 children: Mutex::new(vec![child1]),
538 parent: Mutex::new(None),
539 child_errors: Mutex::new(BTreeMap::new()),
540 };
541
542 let snapshot_tree = SnapshotTree::try_from(root_trees).unwrap();
544
545 assert!(snapshot_tree.children.is_empty());
548 }
549}