blob: 32f929e78f870db1ef729d342105ff8e28e3df1f [file] [log] [blame]
// Copyright 2022 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 {
fuchsia_inspect::Property,
fuchsia_sync::Mutex,
std::{
borrow::Cow,
collections::{hash_map::Entry, HashMap},
fmt::{Debug, Error, Formatter},
ops::Deref,
sync::Arc,
},
};
/// Container to store diagnostic content in inspect.
pub struct RootDiagnosticNode {
inspect: Arc<fuchsia_inspect::Node>,
count: std::sync::atomic::AtomicU64,
}
impl RootDiagnosticNode {
/// Create a new |RootDiagnosticNode| rooted at |inspect|.
pub fn new(inspect: fuchsia_inspect::Node) -> Self {
Self { inspect: Arc::new(inspect), count: std::sync::atomic::AtomicU64::new(0) }
}
/// Create a |DiagnosticNode| that records contents to inspect when dropped.
pub(crate) fn persistent_child(&self) -> DiagnosticNode {
let name =
format!("id-{:?}", self.count.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
DiagnosticNode::new_set_persistence(name, self.inspect.clone(), true)
}
/// Create a |DiagnosticNode| that erases contents from inspect when dropped.
pub(crate) fn child(&self) -> DiagnosticNode {
let name =
format!("id-{:?}", self.count.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
DiagnosticNode::new(name, self.inspect.clone())
}
}
/// A hierarchical container for diagnostic context.
///
/// |DiagnosticNode| contains a name, any properties saved to it, and a reference to
/// it's parent, if any.
/// When printed with Debug, prints all context for ancestor roots.
/// The hierarchy of |DiagnosticNodes| is also output to inspect.
pub(crate) struct DiagnosticNode {
inner: Arc<DiagnosticNodeInner>,
}
#[derive(Clone)]
enum Parent {
Root(Arc<fuchsia_inspect::Node>),
Node(Arc<DiagnosticNodeInner>),
}
struct DiagnosticNodeInner {
name: Cow<'static, str>,
properties: Mutex<HashMap<&'static str, (Cow<'static, str>, fuchsia_inspect::StringProperty)>>,
parent: Parent,
inspect: fuchsia_inspect::Node,
persistent: bool,
}
impl DiagnosticNode {
/// Create a new root |DiagnosticNode| using the given inspect node.
pub(crate) fn new(
name: impl Into<Cow<'static, str>>,
inspect_parent: Arc<fuchsia_inspect::Node>,
) -> Self {
Self::new_set_persistence(name, inspect_parent, false)
}
fn new_set_persistence(
name: impl Into<Cow<'static, str>>,
inspect_parent: Arc<fuchsia_inspect::Node>,
persistent: bool,
) -> Self {
let cow_name = name.into();
Self {
inner: Arc::new(DiagnosticNodeInner {
inspect: inspect_parent.create_child(cow_name.deref()),
parent: Parent::Root(inspect_parent),
name: cow_name,
properties: Mutex::new(HashMap::new()),
persistent,
}),
}
}
/// Create a |DiagnosticNode| as a child of &self.
pub(crate) fn child(&self, name: impl Into<Cow<'static, str>>) -> Self {
let cow_name = name.into();
Self {
inner: Arc::new(DiagnosticNodeInner {
inspect: self.inner.inspect.create_child(cow_name.deref()),
name: cow_name,
properties: Mutex::new(HashMap::new()),
parent: Parent::Node(self.inner.clone()),
persistent: self.inner.persistent,
}),
}
}
/// Set a property. If the key is already in use, overrides the value.
pub(crate) fn set_property(&self, key: &'static str, value: impl Into<Cow<'static, str>>) {
let mut prop_lock = self.inner.properties.lock();
let cow_val = value.into();
match prop_lock.entry(key) {
Entry::Occupied(mut entry) => {
let val = entry.get_mut();
val.1.set(cow_val.deref());
val.0 = cow_val;
}
Entry::Vacant(entry) => {
let prop = self.inner.inspect.create_string(key, cow_val.deref());
entry.insert((cow_val, prop));
}
}
}
/// Mark a property as true.
pub(crate) fn set_flag(&self, key: &'static str) {
self.set_property(key, "true")
}
fn ancestors_and_self(&self) -> Vec<Arc<DiagnosticNodeInner>> {
let mut ancestors = vec![self.inner.clone()];
let mut next_parent = self.inner.parent.clone();
while let Parent::Node(parent) = next_parent.clone() {
next_parent = parent.parent.clone();
ancestors.push(parent);
}
ancestors.reverse();
ancestors
}
}
impl std::ops::Drop for DiagnosticNodeInner {
fn drop(&mut self) {
if self.persistent {
let parent_inspect = match &self.parent {
Parent::Node(inner) => &inner.inspect,
Parent::Root(inspect) => &*inspect,
};
let inspect = std::mem::take(&mut self.inspect);
for (_, val) in self.properties.lock().drain() {
inspect.record(val.1);
}
inspect.record_bool("_dropped", true);
parent_inspect.record(inspect);
}
}
}
impl Debug for DiagnosticNode {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
let ancestors = self.ancestors_and_self();
f.debug_list().entries(ancestors).finish()
}
}
impl Debug for DiagnosticNodeInner {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
let mut debug_struct = f.debug_struct(self.name.deref());
for (key, value) in self.properties.lock().iter() {
debug_struct.field(key, &value.0.deref());
}
debug_struct.finish()
}
}
#[cfg(test)]
mod test {
use {super::*, diagnostics_assertions::assert_data_tree, fuchsia_inspect::Inspector};
#[fuchsia::test]
fn inspect_lifetimes() {
let inspector = Inspector::default();
let root_node = DiagnosticNode::new("root", Arc::new(inspector.root().clone_weak()));
assert_data_tree!(
inspector,
root: {
root: {}
}
);
root_node.set_property("property", "value");
assert_data_tree!(
inspector,
root: {
root: {
property: "value",
}
}
);
let child_node = root_node.child("child");
assert_data_tree!(
inspector,
root: {
root: {
property: "value",
child: {}
}
}
);
drop(child_node);
assert_data_tree!(
inspector,
root: {
root: {
property: "value",
}
}
);
drop(root_node);
assert_data_tree!(
inspector,
root: {}
);
}
#[fuchsia::test]
fn debug_fmt_hierarchy() {
let inspector = Inspector::default();
let root_node = DiagnosticNode::new("root", Arc::new(inspector.root().clone_weak()));
assert!(format!("{:?}", root_node).contains("root"));
let child_node = root_node.child("child");
// child should display parent too.
assert!(format!("{:?}", child_node).contains("child"));
assert!(format!("{:?}", child_node).contains("root"));
// grandchild should display all ancestors.
let grandchild = child_node.child("grand");
assert!(format!("{:?}", grandchild).contains("grand"));
assert!(format!("{:?}", grandchild).contains("child"));
assert!(format!("{:?}", grandchild).contains("root"));
// descendants still print ancestors even if they are dropped
drop(root_node);
drop(child_node);
assert!(format!("{:?}", grandchild).contains("grand"));
assert!(format!("{:?}", grandchild).contains("child"));
assert!(format!("{:?}", grandchild).contains("root"));
}
#[fuchsia::test]
fn debug_fmt_properties() {
let inspector = Inspector::default();
let root_node = DiagnosticNode::new("root", Arc::new(inspector.root().clone_weak()));
assert!(format!("{:?}", root_node).contains("root"));
root_node.set_property("property", "value");
assert!(format!("{:?}", root_node).contains("root"));
assert!(format!("{:?}", root_node).contains("property"));
assert!(format!("{:?}", root_node).contains("value"));
}
}