Skip to main content

test_manager_lib/
self_diagnostics.rs

1// Copyright 2022 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 fuchsia_inspect::Property;
6use fuchsia_sync::Mutex;
7use std::borrow::Cow;
8use std::collections::HashMap;
9use std::collections::hash_map::Entry;
10use std::fmt::{Debug, Error, Formatter};
11use std::ops::Deref;
12use std::sync::Arc;
13
14/// Container to store diagnostic content in inspect.
15pub struct RootDiagnosticNode {
16    inspect: Arc<fuchsia_inspect::Node>,
17    count: std::sync::atomic::AtomicU64,
18}
19
20impl RootDiagnosticNode {
21    /// Create a new |RootDiagnosticNode| rooted at |inspect|.
22    pub fn new(inspect: fuchsia_inspect::Node) -> Self {
23        Self { inspect: Arc::new(inspect), count: std::sync::atomic::AtomicU64::new(0) }
24    }
25
26    /// Create a |DiagnosticNode| that records contents to inspect when dropped.
27    #[allow(dead_code)]
28    pub(crate) fn persistent_child(&self) -> DiagnosticNode {
29        let name =
30            format!("id-{:?}", self.count.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
31        DiagnosticNode::new_set_persistence(name, self.inspect.clone(), true)
32    }
33
34    /// Create a |DiagnosticNode| that erases contents from inspect when dropped.
35    pub(crate) fn child(&self) -> DiagnosticNode {
36        let name =
37            format!("id-{:?}", self.count.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
38        DiagnosticNode::new(name, self.inspect.clone())
39    }
40}
41
42/// A hierarchical container for diagnostic context.
43///
44/// |DiagnosticNode| contains a name, any properties saved to it, and a reference to
45/// it's parent, if any.
46/// When printed with Debug, prints all context for ancestor roots.
47/// The hierarchy of |DiagnosticNodes| is also output to inspect.
48pub(crate) struct DiagnosticNode {
49    inner: Arc<DiagnosticNodeInner>,
50}
51
52#[derive(Clone)]
53enum Parent {
54    Root(Arc<fuchsia_inspect::Node>),
55    Node(Arc<DiagnosticNodeInner>),
56}
57
58struct DiagnosticNodeInner {
59    name: Cow<'static, str>,
60    properties: Mutex<HashMap<&'static str, (Cow<'static, str>, fuchsia_inspect::StringProperty)>>,
61    parent: Parent,
62    inspect: fuchsia_inspect::Node,
63    persistent: bool,
64}
65
66impl DiagnosticNode {
67    /// Create a new root |DiagnosticNode| using the given inspect node.
68    pub(crate) fn new(
69        name: impl Into<Cow<'static, str>>,
70        inspect_parent: Arc<fuchsia_inspect::Node>,
71    ) -> Self {
72        Self::new_set_persistence(name, inspect_parent, false)
73    }
74
75    fn new_set_persistence(
76        name: impl Into<Cow<'static, str>>,
77        inspect_parent: Arc<fuchsia_inspect::Node>,
78        persistent: bool,
79    ) -> Self {
80        let cow_name = name.into();
81        Self {
82            inner: Arc::new(DiagnosticNodeInner {
83                inspect: inspect_parent.create_child(cow_name.deref()),
84                parent: Parent::Root(inspect_parent),
85                name: cow_name,
86                properties: Mutex::new(HashMap::new()),
87                persistent,
88            }),
89        }
90    }
91
92    /// Create a |DiagnosticNode| as a child of &self.
93    pub(crate) fn child(&self, name: impl Into<Cow<'static, str>>) -> Self {
94        let cow_name = name.into();
95        Self {
96            inner: Arc::new(DiagnosticNodeInner {
97                inspect: self.inner.inspect.create_child(cow_name.deref()),
98                name: cow_name,
99                properties: Mutex::new(HashMap::new()),
100                parent: Parent::Node(self.inner.clone()),
101                persistent: self.inner.persistent,
102            }),
103        }
104    }
105
106    /// Set a property. If the key is already in use, overrides the value.
107    pub(crate) fn set_property(&self, key: &'static str, value: impl Into<Cow<'static, str>>) {
108        let mut prop_lock = self.inner.properties.lock();
109        let cow_val = value.into();
110        match prop_lock.entry(key) {
111            Entry::Occupied(mut entry) => {
112                let val = entry.get_mut();
113                val.1.set(cow_val.deref());
114                val.0 = cow_val;
115            }
116            Entry::Vacant(entry) => {
117                let prop = self.inner.inspect.create_string(key, cow_val.deref());
118                entry.insert((cow_val, prop));
119            }
120        }
121    }
122
123    /// Mark a property as true.
124    #[allow(dead_code)]
125    pub(crate) fn set_flag(&self, key: &'static str) {
126        self.set_property(key, "true")
127    }
128
129    fn ancestors_and_self(&self) -> Vec<Arc<DiagnosticNodeInner>> {
130        let mut ancestors = vec![self.inner.clone()];
131        let mut next_parent = self.inner.parent.clone();
132        while let Parent::Node(parent) = next_parent.clone() {
133            next_parent = parent.parent.clone();
134            ancestors.push(parent);
135        }
136        ancestors.reverse();
137        ancestors
138    }
139}
140
141impl std::ops::Drop for DiagnosticNodeInner {
142    fn drop(&mut self) {
143        if self.persistent {
144            let parent_inspect = match &self.parent {
145                Parent::Node(inner) => &inner.inspect,
146                Parent::Root(inspect) => &*inspect,
147            };
148            let inspect = std::mem::take(&mut self.inspect);
149            for (_, val) in self.properties.lock().drain() {
150                inspect.record(val.1);
151            }
152            inspect.record_bool("_dropped", true);
153            parent_inspect.record(inspect);
154        }
155    }
156}
157
158impl Debug for DiagnosticNode {
159    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
160        let ancestors = self.ancestors_and_self();
161        f.debug_list().entries(ancestors).finish()
162    }
163}
164
165impl Debug for DiagnosticNodeInner {
166    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
167        let mut debug_struct = f.debug_struct(self.name.deref());
168        for (key, value) in self.properties.lock().iter() {
169            debug_struct.field(key, &value.0.deref());
170        }
171        debug_struct.finish()
172    }
173}
174
175#[cfg(test)]
176mod test {
177    use super::*;
178    use diagnostics_assertions::assert_data_tree;
179    use fuchsia_inspect::Inspector;
180
181    #[fuchsia::test]
182    async fn inspect_lifetimes() {
183        let inspector = Inspector::default();
184        let root_node = DiagnosticNode::new("root", Arc::new(inspector.root().clone_weak()));
185
186        assert_data_tree!(
187            inspector,
188            root: {
189                root: {}
190            }
191        );
192
193        root_node.set_property("property", "value");
194
195        assert_data_tree!(
196            inspector,
197            root: {
198                root: {
199                    property: "value",
200                }
201            }
202        );
203
204        let child_node = root_node.child("child");
205        assert_data_tree!(
206            inspector,
207            root: {
208                root: {
209                    property: "value",
210                    child: {}
211                }
212            }
213        );
214
215        drop(child_node);
216        assert_data_tree!(
217            inspector,
218            root: {
219                root: {
220                    property: "value",
221                }
222            }
223        );
224
225        drop(root_node);
226        assert_data_tree!(
227            inspector,
228            root: {}
229        );
230    }
231
232    #[fuchsia::test]
233    fn debug_fmt_hierarchy() {
234        let inspector = Inspector::default();
235        let root_node = DiagnosticNode::new("root", Arc::new(inspector.root().clone_weak()));
236
237        assert!(format!("{:?}", root_node).contains("root"));
238
239        let child_node = root_node.child("child");
240
241        // child should display parent too.
242        assert!(format!("{:?}", child_node).contains("child"));
243        assert!(format!("{:?}", child_node).contains("root"));
244
245        // grandchild should display all ancestors.
246        let grandchild = child_node.child("grand");
247        assert!(format!("{:?}", grandchild).contains("grand"));
248        assert!(format!("{:?}", grandchild).contains("child"));
249        assert!(format!("{:?}", grandchild).contains("root"));
250
251        // descendants still print ancestors even if they are dropped
252        drop(root_node);
253        drop(child_node);
254        assert!(format!("{:?}", grandchild).contains("grand"));
255        assert!(format!("{:?}", grandchild).contains("child"));
256        assert!(format!("{:?}", grandchild).contains("root"));
257    }
258
259    #[fuchsia::test]
260    fn debug_fmt_properties() {
261        let inspector = Inspector::default();
262        let root_node = DiagnosticNode::new("root", Arc::new(inspector.root().clone_weak()));
263
264        assert!(format!("{:?}", root_node).contains("root"));
265
266        root_node.set_property("property", "value");
267        assert!(format!("{:?}", root_node).contains("root"));
268        assert!(format!("{:?}", root_node).contains("property"));
269        assert!(format!("{:?}", root_node).contains("value"));
270    }
271}