blob: 151276440da4ba980dfdb2efd6ffde1fa1c396d9 [file] [log] [blame]
// Copyright 2021 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 crate::writer::{
private::InspectTypeInternal, BoolProperty, BytesProperty, DoubleArrayProperty,
DoubleExponentialHistogramProperty, DoubleLinearHistogramProperty, DoubleProperty, Error,
Inner, InnerType, InspectType, InspectTypeReparentable, Inspector, IntArrayProperty,
IntExponentialHistogramProperty, IntLinearHistogramProperty, IntProperty, LazyNode, State,
StringArrayProperty, StringProperty, StringReference, UintArrayProperty,
UintExponentialHistogramProperty, UintLinearHistogramProperty, UintProperty, ValueList,
};
use diagnostics_hierarchy::{
ArrayFormat, ExponentialHistogramParams, LinearHistogramParams, LinkNodeDisposition,
};
use fuchsia_zircon as zx;
use futures::{future::BoxFuture, FutureExt};
use inspect_format::{constants, PropertyFormat};
use std::sync::Arc;
#[cfg(test)]
use {inspect_format::Block, mapped_vmo::Mapping};
/// Inspect Node data type.
///
/// NOTE: do not rely on PartialEq implementation for true comparison.
/// Instead leverage the reader.
///
/// NOTE: Operations on a Default value are no-ops.
#[derive(Debug, PartialEq, Eq, Default)]
pub struct Node {
pub(crate) inner: Inner<InnerNodeType>,
}
impl InspectType for Node {}
crate::impl_inspect_type_internal!(Node);
impl Node {
/// Create a weak reference to the original node. All operations on a weak
/// reference have identical semantics to the original node for as long
/// as the original node is live. After that, all operations are no-ops.
pub fn clone_weak(&self) -> Node {
Self { inner: self.inner.clone_weak() }
}
/// Add a child to this node.
#[must_use]
pub fn create_child<'b>(&self, name: impl Into<StringReference<'b>>) -> Node {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| state.create_node(name, inner_ref.block_index))
.map(|block| Node::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(Node::new_no_op())
}
/// Creates and keeps track of a child with the given `name`.
pub fn record_child<'b, F>(&self, name: impl Into<StringReference<'b>>, initialize: F)
where
F: FnOnce(&Node),
{
self.atomic_update(move |n| {
let child = n.create_child(name);
initialize(&child);
n.record(child);
});
}
/// Takes a function to execute as under a single lock of the Inspect VMO. This function
/// receives a reference to the `Node` where this is called.
pub fn atomic_update<F, R>(&self, update_fn: F) -> R
where
F: FnOnce(&Node) -> R,
{
match self.inner.inner_ref() {
None => {
// If the node was a no-op we still execute the `update_fn` even if all operations
// inside it will be no-ops to return `R`.
update_fn(&self)
}
Some(inner_ref) => {
// Silently ignore the error when fail to lock (as in any regular operation).
// All operations performed in the `update_fn` won't update the vmo
// generation count since we'll be holding one lock here.
inner_ref.state.begin_transaction();
let result = update_fn(&self);
inner_ref.state.end_transaction();
result
}
}
}
/// Keeps track of the given property for the lifetime of the node.
pub fn record(&self, property: impl InspectType + 'static) {
self.inner.inner_ref().map(|inner_ref| inner_ref.data.record(property));
}
/// Drop all recorded data from the node.
pub fn clear_recorded(&self) {
self.inner.inner_ref().map(|inner_ref| inner_ref.data.clear());
}
/// Creates a new `IntProperty` with the given `name` and `value`.
#[must_use]
pub fn create_int<'b>(&self, name: impl Into<StringReference<'b>>, value: i64) -> IntProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_int_metric(name, value, inner_ref.block_index)
})
.map(|block| IntProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(IntProperty::new_no_op())
}
/// Records a new `IntProperty` with the given `name` and `value`.
pub fn record_int<'b>(&self, name: impl Into<StringReference<'b>>, value: i64) {
let property = self.create_int(name, value);
self.record(property);
}
/// Creates a new `UintProperty` with the given `name` and `value`.
#[must_use]
pub fn create_uint<'b>(
&self,
name: impl Into<StringReference<'b>>,
value: u64,
) -> UintProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_uint_metric(name, value, inner_ref.block_index)
})
.map(|block| UintProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(UintProperty::new_no_op())
}
/// Records a new `UintProperty` with the given `name` and `value`.
pub fn record_uint<'b>(&self, name: impl Into<StringReference<'b>>, value: u64) {
let property = self.create_uint(name, value);
self.record(property);
}
/// Creates a new `DoubleProperty` with the given `name` and `value`.
#[must_use]
pub fn create_double<'b>(
&self,
name: impl Into<StringReference<'b>>,
value: f64,
) -> DoubleProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_double_metric(name, value, inner_ref.block_index)
})
.map(|block| DoubleProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(DoubleProperty::new_no_op())
}
/// Records a new `DoubleProperty` with the given `name` and `value`.
pub fn record_double<'b>(&self, name: impl Into<StringReference<'b>>, value: f64) {
let property = self.create_double(name, value);
self.record(property);
}
/// Creates a new `StringArrayProperty` with the given `name` and `slots`.
#[must_use]
pub fn create_string_array<'a, 'b>(
&self,
name: impl Into<StringReference<'b>>,
slots: usize,
) -> StringArrayProperty<'a> {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_string_array(name, slots, inner_ref.block_index)
})
.map(|block| StringArrayProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(StringArrayProperty::new_no_op())
}
/// Creates a new `IntArrayProperty` with the given `name` and `slots`.
#[must_use]
pub fn create_int_array<'b>(
&self,
name: impl Into<StringReference<'b>>,
slots: usize,
) -> IntArrayProperty {
self.create_int_array_internal(name, slots, ArrayFormat::Default)
}
#[must_use]
pub(crate) fn create_int_array_internal<'b>(
&self,
name: impl Into<StringReference<'b>>,
slots: usize,
format: ArrayFormat,
) -> IntArrayProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_int_array(name, slots, format, inner_ref.block_index)
})
.map(|block| IntArrayProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(IntArrayProperty::new_no_op())
}
/// Creates a new `UintArrayProperty` with the given `name` and `slots`.
#[must_use]
pub fn create_uint_array<'b>(
&self,
name: impl Into<StringReference<'b>>,
slots: usize,
) -> UintArrayProperty {
self.create_uint_array_internal(name, slots, ArrayFormat::Default)
}
#[must_use]
pub(crate) fn create_uint_array_internal<'b>(
&self,
name: impl Into<StringReference<'b>>,
slots: usize,
format: ArrayFormat,
) -> UintArrayProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_uint_array(name, slots, format, inner_ref.block_index)
})
.map(|block| UintArrayProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(UintArrayProperty::new_no_op())
}
/// Creates a new `DoubleArrayProperty` with the given `name` and `slots`.
#[must_use]
pub fn create_double_array<'b>(
&self,
name: impl Into<StringReference<'b>>,
slots: usize,
) -> DoubleArrayProperty {
self.create_double_array_internal(name, slots, ArrayFormat::Default)
}
#[must_use]
pub(crate) fn create_double_array_internal<'b>(
&self,
name: impl Into<StringReference<'b>>,
slots: usize,
format: ArrayFormat,
) -> DoubleArrayProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_double_array(name, slots, format, inner_ref.block_index)
})
.map(|block| DoubleArrayProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(DoubleArrayProperty::new_no_op())
}
/// Creates a new `IntLinearHistogramProperty` with the given `name` and `params`.
#[must_use]
pub fn create_int_linear_histogram<'b>(
&self,
name: impl Into<StringReference<'b>>,
params: LinearHistogramParams<i64>,
) -> IntLinearHistogramProperty {
IntLinearHistogramProperty::new(name, params, &self)
}
/// Creates a new `UintLinearHistogramProperty` with the given `name` and `params`.
#[must_use]
pub fn create_uint_linear_histogram<'b>(
&self,
name: impl Into<StringReference<'b>>,
params: LinearHistogramParams<u64>,
) -> UintLinearHistogramProperty {
UintLinearHistogramProperty::new(name, params, &self)
}
/// Creates a new `DoubleLinearHistogramProperty` with the given `name` and `params`.
#[must_use]
pub fn create_double_linear_histogram<'b>(
&self,
name: impl Into<StringReference<'b>>,
params: LinearHistogramParams<f64>,
) -> DoubleLinearHistogramProperty {
DoubleLinearHistogramProperty::new(name, params, &self)
}
/// Creates a new `IntExponentialHistogramProperty` with the given `name` and `params`.
#[must_use]
pub fn create_int_exponential_histogram<'b>(
&self,
name: impl Into<StringReference<'b>>,
params: ExponentialHistogramParams<i64>,
) -> IntExponentialHistogramProperty {
IntExponentialHistogramProperty::new(name, params, &self)
}
/// Creates a new `UintExponentialHistogramProperty` with the given `name` and `params`.
#[must_use]
pub fn create_uint_exponential_histogram<'b>(
&self,
name: impl Into<StringReference<'b>>,
params: ExponentialHistogramParams<u64>,
) -> UintExponentialHistogramProperty {
UintExponentialHistogramProperty::new(name, params, &self)
}
/// Creates a new `DoubleExponentialHistogramProperty` with the given `name` and `params`.
#[must_use]
pub fn create_double_exponential_histogram<'b>(
&self,
name: impl Into<StringReference<'b>>,
params: ExponentialHistogramParams<f64>,
) -> DoubleExponentialHistogramProperty {
DoubleExponentialHistogramProperty::new(name, params, &self)
}
/// Creates a new lazy child with the given `name` and `callback`.
#[must_use]
pub fn create_lazy_child<'b, F>(
&self,
name: impl Into<StringReference<'b>>,
callback: F,
) -> LazyNode
where
F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static,
{
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_lazy_node(
name,
inner_ref.block_index,
LinkNodeDisposition::Child,
callback,
)
})
.map(|block| LazyNode::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(LazyNode::new_no_op())
}
/// Records a new lazy child with the given `name` and `callback`.
pub fn record_lazy_child<'b, F>(&self, name: impl Into<StringReference<'b>>, callback: F)
where
F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static,
{
let property = self.create_lazy_child(name, callback);
self.record(property);
}
/// Creates a new inline lazy node with the given `name` and `callback`.
#[must_use]
pub fn create_lazy_values<'b, F>(
&self,
name: impl Into<StringReference<'b>>,
callback: F,
) -> LazyNode
where
F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static,
{
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_lazy_node(
name,
inner_ref.block_index,
LinkNodeDisposition::Inline,
callback,
)
})
.map(|block| LazyNode::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(LazyNode::new_no_op())
}
/// Records a new inline lazy node with the given `name` and `callback`.
pub fn record_lazy_values<'b, F>(&self, name: impl Into<StringReference<'b>>, callback: F)
where
F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static,
{
let property = self.create_lazy_values(name, callback);
self.record(property);
}
/// Creates a lazy node from the given VMO.
#[must_use]
pub fn create_lazy_child_from_vmo<'b>(
&self,
name: impl Into<StringReference<'b>>,
vmo: Arc<zx::Vmo>,
) -> LazyNode {
self.create_lazy_child(name, move || {
let vmo_clone = vmo.clone();
async move { Ok(Inspector::no_op_from_vmo(vmo_clone)) }.boxed()
})
}
/// Records a lazy node from the given VMO.
pub fn record_lazy_child_from_vmo<'b>(
&self,
name: impl Into<StringReference<'b>>,
vmo: Arc<zx::Vmo>,
) {
self.record_lazy_child(name, move || {
let vmo_clone = vmo.clone();
async move { Ok(Inspector::no_op_from_vmo(vmo_clone)) }.boxed()
});
}
/// Add a string property to this node.
#[must_use]
pub fn create_string<'b>(
&self,
name: impl Into<StringReference<'b>>,
value: impl AsRef<str>,
) -> StringProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_property(
name,
value.as_ref().as_bytes(),
PropertyFormat::String,
inner_ref.block_index,
)
})
.map(|block| StringProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(StringProperty::new_no_op())
}
/// Creates and saves a string property for the lifetime of the node.
pub fn record_string<'b>(&self, name: impl Into<StringReference<'b>>, value: impl AsRef<str>) {
let property = self.create_string(name, value);
self.record(property);
}
/// Add a byte vector property to this node.
#[must_use]
pub fn create_bytes<'b>(
&self,
name: impl Into<StringReference<'b>>,
value: impl AsRef<[u8]>,
) -> BytesProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| {
state.create_property(
name,
value.as_ref(),
PropertyFormat::Bytes,
inner_ref.block_index,
)
})
.map(|block| BytesProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(BytesProperty::new_no_op())
}
/// Creates and saves a bytes property for the lifetime of the node.
pub fn record_bytes<'b>(&self, name: impl Into<StringReference<'b>>, value: impl AsRef<[u8]>) {
let property = self.create_bytes(name, value);
self.record(property);
}
/// Add a bool property to this node.
#[must_use]
pub fn create_bool<'b>(
&self,
name: impl Into<StringReference<'b>>,
value: bool,
) -> BoolProperty {
self.inner
.inner_ref()
.and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|mut state| state.create_bool(name, value, inner_ref.block_index))
.map(|block| BoolProperty::new(inner_ref.state.clone(), block.index()))
.ok()
})
.unwrap_or(BoolProperty::new_no_op())
}
/// Creates and saves a bool property for the lifetime of the node.
pub fn record_bool<'b>(&self, name: impl Into<StringReference<'b>>, value: bool) {
let property = self.create_bool(name, value);
self.record(property);
}
/// Takes a child from its parent and adopts it into its own tree.
pub fn adopt<T: InspectTypeReparentable>(&self, child: &T) -> Result<(), Error> {
child.reparent(self)
}
/// Returns the [`Block`][Block] associated with this value.
#[cfg(test)]
pub(crate) fn get_block(&self) -> Option<Block<Arc<Mapping>>> {
self.inner.inner_ref().and_then(|inner_ref| {
inner_ref
.state
.try_lock()
.and_then(|state| state.heap().get_block(inner_ref.block_index))
.ok()
})
}
/// Creates a new root node.
pub(crate) fn new_root(state: State) -> Node {
Node::new(state, constants::ROOT_INDEX)
}
}
#[derive(Default, Debug)]
pub(crate) struct InnerNodeType;
impl InnerType for InnerNodeType {
// Each node has a list of recorded values.
type Data = ValueList;
fn free(state: &State, block_index: u32) -> Result<(), Error> {
if block_index == constants::ROOT_INDEX {
return Ok(());
}
let mut state_lock = state.try_lock()?;
state_lock.free_value(block_index).map_err(|err| Error::free("node", block_index, err))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
assert_json_diff, reader,
writer::{
private::InspectTypeInternal, testing_utils::get_state, ArrayProperty, Error,
NumericProperty,
},
};
use diagnostics_hierarchy::{assert_data_tree, DiagnosticsHierarchy};
use fuchsia_zircon::{AsHandleRef, Peered};
use inspect_format::BlockType;
use std::convert::TryFrom;
#[fuchsia::test]
fn node() {
// Create and use a default value.
let default = Node::default();
default.record_int("a", 0);
let state = get_state(4096);
let root = Node::new_root(state);
let node = root.create_child("node");
let node_block = node.get_block().unwrap();
assert_eq!(node_block.block_type(), BlockType::NodeValue);
assert_eq!(node_block.child_count().unwrap(), 0);
{
let child = node.create_child("child");
let child_block = child.get_block().unwrap();
assert_eq!(child_block.block_type(), BlockType::NodeValue);
assert_eq!(child_block.child_count().unwrap(), 0);
assert_eq!(node_block.child_count().unwrap(), 1);
}
assert_eq!(node_block.child_count().unwrap(), 0);
}
#[fuchsia::test]
async fn lazy_child() {
let inspector = Inspector::new();
let _lazy = inspector.root().create_lazy_child("lazy-1", || {
async move {
let insp = Inspector::new();
insp.root().record_lazy_child("parent", || {
async move {
let insp2 = Inspector::new();
insp2.root().record_int("create-lazy-child", 0);
insp2.root().record_int("create-lazy-child-2", 2);
Ok(insp2)
}
.boxed()
});
Ok(insp)
}
.boxed()
});
inspector.root().record_lazy_child("lazy-2", || {
async move {
let insp = Inspector::new();
insp.root().record_bool("recorded-lazy-child", true);
Ok(insp)
}
.boxed()
});
inspector.root().record_lazy_values("lazy", || {
async move {
let insp = Inspector::new();
insp.root().record_bool("recorded-lazy-values", true);
Ok(insp)
}
.boxed()
});
let result = reader::read(&inspector).await.unwrap();
assert_data_tree!(result, root: {
"lazy-1": {
"parent": {
"create-lazy-child": 0i64,
"create-lazy-child-2": 2i64,
},
},
"lazy-2": {
"recorded-lazy-child": true,
},
"recorded-lazy-values": true,
});
}
#[fuchsia::test]
fn test_adoption() {
let insp = Inspector::new();
let root = insp.root();
let a = root.create_child("a");
let b = root.create_child("b");
let c = b.create_child("c");
assert_data_tree!(insp, root: {
a: {},
b: {
c: {},
},
});
a.adopt(&b).unwrap();
assert_data_tree!(insp, root: {
a: {
b: {
c: {},
},
},
});
assert!(c.adopt(&a).is_err());
assert!(c.adopt(&b).is_err());
assert!(b.adopt(&a).is_err());
assert!(a.adopt(root).is_err());
assert!(a.adopt(&a).is_err());
{
let d = root.create_int("d", 4);
assert_data_tree!(insp, root: {
a: {
b: {
c: {},
},
},
d: 4i64,
});
c.adopt(&d).unwrap();
assert_data_tree!(insp, root: {
a: {
b: {
c: {
d: 4i64,
},
},
},
});
}
assert_data_tree!(insp, root: {
a: {
b: {
c: {},
},
},
});
}
#[fuchsia::test]
fn node_no_op_clone_weak() {
let default = Node::default();
assert!(!default.is_valid());
let weak = default.clone_weak();
assert!(!weak.is_valid());
let _ = weak.create_child("child");
std::mem::drop(default);
let _ = weak.create_uint("age", 1337);
assert!(!weak.is_valid());
}
#[fuchsia::test]
fn node_clone_weak() {
let state = get_state(4096);
let root = Node::new_root(state);
let node = root.create_child("node");
let node_weak = node.clone_weak();
let node_weak_2 = node_weak.clone_weak(); // Weak from another weak
let node_block = node.get_block().unwrap();
assert_eq!(node_block.block_type(), BlockType::NodeValue);
assert_eq!(node_block.child_count().unwrap(), 0);
let node_weak_block = node.get_block().unwrap();
assert_eq!(node_weak_block.block_type(), BlockType::NodeValue);
assert_eq!(node_weak_block.child_count().unwrap(), 0);
let node_weak_2_block = node.get_block().unwrap();
assert_eq!(node_weak_2_block.block_type(), BlockType::NodeValue);
assert_eq!(node_weak_2_block.child_count().unwrap(), 0);
let child_from_strong = node.create_child("child");
let child = node_weak.create_child("child_1");
let child_2 = node_weak_2.create_child("child_2");
std::mem::drop(node_weak_2);
assert_eq!(node_weak_block.child_count().unwrap(), 3);
std::mem::drop(child_from_strong);
assert_eq!(node_weak_block.child_count().unwrap(), 2);
std::mem::drop(child);
assert_eq!(node_weak_block.child_count().unwrap(), 1);
assert!(node_weak.is_valid());
assert!(child_2.is_valid());
std::mem::drop(node);
assert!(!node_weak.is_valid());
let _ = node_weak.create_child("orphan");
let _ = child_2.create_child("orphan");
}
#[fuchsia::test]
fn dummy_partialeq() {
let inspector = Inspector::new();
let root = inspector.root();
// Types should all be equal to another type. This is to enable clients
// with inspect types in their structs be able to derive PartialEq and
// Eq smoothly.
assert_eq!(root, &root.create_child("child1"));
assert_eq!(root.create_int("property1", 1), root.create_int("property2", 2));
assert_eq!(root.create_double("property1", 1.0), root.create_double("property2", 2.0));
assert_eq!(root.create_uint("property1", 1), root.create_uint("property2", 2));
assert_eq!(
root.create_string("property1", "value1"),
root.create_string("property2", "value2")
);
assert_eq!(
root.create_bytes("property1", b"value1"),
root.create_bytes("property2", b"value2")
);
}
#[fuchsia::test]
fn inspector_lazy_from_vmo() {
let inspector = Inspector::new();
inspector.root().record_uint("test", 3);
let embedded_inspector = Inspector::new();
embedded_inspector.root().record_uint("test2", 4);
let vmo = embedded_inspector.duplicate_vmo().unwrap();
inspector.root().record_lazy_child_from_vmo("lazy", Arc::new(vmo));
assert_data_tree!(inspector, root: {
test: 3u64,
lazy: {
test2: 4u64,
}
});
}
#[fuchsia::test]
fn record() {
let inspector = Inspector::new();
let property = inspector.root().create_uint("a", 1);
inspector.root().record_uint("b", 2);
{
let child = inspector.root().create_child("child");
child.record(property);
child.record_double("c", 3.25);
assert_data_tree!(inspector, root: {
a: 1u64,
b: 2u64,
child: {
c: 3.25,
}
});
}
// `child` went out of scope, meaning it was deleted.
// Property `a` should be gone as well, given that it was being tracked by `child`.
assert_data_tree!(inspector, root: {
b: 2u64,
});
inspector.root().clear_recorded();
assert_data_tree!(inspector, root: {});
}
#[fuchsia::test]
fn clear_recorded() {
let inspector = Inspector::new();
let one = inspector.root().create_child("one");
let two = inspector.root().create_child("two");
let one_recorded = one.create_child("one_recorded");
let two_recorded = two.create_child("two_recorded");
one.record(one_recorded);
two.record(two_recorded);
assert_json_diff!(inspector, root: {
one: {
one_recorded: {},
},
two: {
two_recorded: {},
},
});
two.clear_recorded();
assert_json_diff!(inspector, root: {
one: {
one_recorded: {},
},
two: {},
});
one.clear_recorded();
assert_json_diff!(inspector, root: {
one: {},
two: {},
});
}
#[fuchsia::test]
fn record_child() {
let inspector = Inspector::new();
inspector.root().record_child("test", |node| {
node.record_int("a", 1);
});
assert_data_tree!(inspector, root: {
test: {
a: 1i64,
}
})
}
#[fuchsia::test]
fn record_weak() {
let inspector = Inspector::new();
let main = inspector.root().create_child("main");
let main_weak = main.clone_weak();
let property = main_weak.create_uint("a", 1);
// Ensure either the weak or strong reference can be used for recording
main_weak.record_uint("b", 2);
main.record_uint("c", 3);
{
let child = main_weak.create_child("child");
child.record(property);
child.record_double("c", 3.25);
assert_data_tree!(inspector, root: { main: {
a: 1u64,
b: 2u64,
c: 3u64,
child: {
c: 3.25,
}
}});
}
// `child` went out of scope, meaning it was deleted.
// Property `a` should be gone as well, given that it was being tracked by `child`.
assert_data_tree!(inspector, root: { main: {
b: 2u64,
c: 3u64
}});
std::mem::drop(main);
// Recording after dropping a strong reference is a no-op
main_weak.record_double("d", 1.0);
// Verify that dropping a strong reference cleans up the state
assert_data_tree!(inspector, root: { });
}
#[fuchsia::test]
async fn atomic_update_reader() {
let inspector = Inspector::new();
// Spawn a read thread that holds a duplicate handle to the VMO that will be written.
let vmo = inspector.duplicate_vmo().expect("duplicate vmo handle");
let (p1, p2) = zx::EventPair::create().unwrap();
macro_rules! notify_and_wait_reader {
() => {
p1.signal_peer(zx::Signals::NONE, zx::Signals::USER_0).unwrap();
p1.wait_handle(zx::Signals::USER_0, zx::Time::INFINITE).unwrap();
p1.signal_handle(zx::Signals::USER_0, zx::Signals::NONE).unwrap();
};
}
macro_rules! wait_and_notify_writer {
($code:block) => {
p2.wait_handle(zx::Signals::USER_0, zx::Time::INFINITE).unwrap();
p2.signal_handle(zx::Signals::USER_0, zx::Signals::NONE).unwrap();
$code
p2.signal_peer(zx::Signals::NONE, zx::Signals::USER_0).unwrap();
}
}
let thread = std::thread::spawn(move || {
// Before running the atomic update.
wait_and_notify_writer! {{
let hierarchy: DiagnosticsHierarchy<String> =
reader::PartialNodeHierarchy::try_from(&vmo).unwrap().into();
assert_eq!(hierarchy, DiagnosticsHierarchy::new_root());
}};
// After: create_child("child"): Assert that the VMO is in use (locked) and we can't
// read.
wait_and_notify_writer! {{
assert!(reader::PartialNodeHierarchy::try_from(&vmo).is_err());
}};
// After: record_int("a"): Assert that the VMO is in use (locked) and we can't
// read.
wait_and_notify_writer! {{
assert!(reader::PartialNodeHierarchy::try_from(&vmo).is_err());
}};
// After: record_int("b"): Assert that the VMO is in use (locked) and we can't
// read.
wait_and_notify_writer! {{
assert!(reader::PartialNodeHierarchy::try_from(&vmo).is_err());
}};
// After atomic update
wait_and_notify_writer! {{
let hierarchy: DiagnosticsHierarchy<String> =
reader::PartialNodeHierarchy::try_from(&vmo).unwrap().into();
assert_data_tree!(hierarchy, root: {
value: 2i64,
child: {
a: 1i64,
b: 2i64,
}
});
}};
});
// Perform the atomic update
let mut child = Node::default();
notify_and_wait_reader!();
let int_val = inspector.root().create_int("value", 1);
inspector
.root()
.atomic_update(|node| {
// Intentionally make this slow to assert an atomic update in the reader.
child = node.create_child("child");
notify_and_wait_reader!();
child.record_int("a", 1);
notify_and_wait_reader!();
child.record_int("b", 2);
notify_and_wait_reader!();
int_val.add(1);
Ok::<(), Error>(())
})
.expect("successful atomic update");
notify_and_wait_reader!();
// Wait for the reader thread to successfully finish.
let _ = thread.join();
// Ensure that the variable that we mutated internally can be used.
child.record_int("c", 3);
assert_data_tree!(inspector, root: {
value: 2i64,
child: {
a: 1i64,
b: 2i64,
c: 3i64,
}
});
}
#[fuchsia::test]
fn string_arrays_on_record() {
let inspector = Inspector::new();
inspector.root().record_child("child", |node| {
node.record_int("my_int", 1i64);
let arr: crate::StringArrayProperty<'static> =
node.create_string_array("my_string_array", 1);
arr.set(0, "test");
node.record(arr);
});
assert_data_tree!(inspector, root: {
child: {
my_int: 1i64,
my_string_array: vec!["test"]
}
});
}
}