| // Copyright 2020 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. |
| |
| //! # fuchsia-inspect |
| //! |
| //! Components in Fuchsia may expose structured information about themselves conforming to the |
| //! [Inspect API][inspect]. This crate is the core library for writing inspect data in Rust |
| //! components. |
| //! |
| //! For a comprehensive guide on how to start using inspect, please refer to the |
| //! [codelab]. |
| //! |
| //! ## Library concepts |
| //! |
| //! There's two types of inspect values: nodes and properties. These have the following |
| //! characteristics: |
| //! |
| //! - A Node may have any number of key/value pairs called Properties. |
| //! - A Node may have any number of children, which are also Nodes. |
| //! - Properties and nodes are created under a parent node. Inspect is already initialized with a |
| //! root node. |
| //! - The key for a value in a Node is always a UTF-8 string, the value may be one of the |
| //! supported types (a node or a property of any type). |
| //! - Nodes and properties have strict ownership semantics. Whenever a node or property is |
| //! created, it is written to the backing [VMO][inspect-vmo] and whenever it is dropped it is |
| //! removed from the VMO. |
| //! - Inspection is best effort, if an error occurs, no panic will happen and nodes and properties |
| //! might become No-Ops. For example, when the VMO becomes full, any further creation of a |
| //! property or a node will result in no changes in the VMO and a silent failure. However, |
| //! mutation of existing properties in the VMO will continue to work. |
| //! - All nodes and properties are thread safe. |
| //! |
| //! ### Creating vs Recording |
| //! |
| //! There are two functions each for initializing nodes and properties: |
| //! |
| //! - `create_*`: returns the created node/property and it's up to the caller to handle its |
| //! lifetime. |
| //! - `record_*`: creates the node/property but doesn't return it and ties its lifetime to |
| //! the node where the function was called. |
| //! |
| //! ### Lazy value support |
| //! |
| //! Lazy (or dynamic) values are values that are created on demand, this is, whenever they are read. |
| //! Unlike regular nodes, they don't take any space on the VMO until a reader comes and requests |
| //! its data. |
| //! |
| //! There's two ways of creating lazy values: |
| //! |
| //! - **Lazy node**: creates a child node of root with the given name. The callback returns a |
| //! future for an [`Inspector`][inspector] whose root node is spliced into the parent node when |
| //! read. |
| //! - **Lazy values**: works like the previous one, except that all properties and nodes under the |
| //! future root node node are added directly as children of the parent node. |
| //! |
| //! ## Quickstart |
| //! |
| //! Add the following to your component main: |
| //! |
| //! ```rust |
| //! use fuchsia_inspect::component; |
| //! use fuchsia_component::server::ServiceFs; |
| //! |
| //! let mut fs = ServiceFs::new(); |
| //! component::inspector().root().serve(&mut fs)?; |
| //! |
| //! // Now you can create nodes and properties anywhere! |
| //! let child = component::inspector().root().create_child("foo"); |
| //! child.record_uint("bar", 42); |
| //! ``` |
| //! |
| //! [inspect]: https://fuchsia.dev/fuchsia-src/development/diagnostics/inspect |
| //! [codelab]: https://fuchsia.dev/fuchsia-src/development/diagnostics/inspect/codelab |
| //! [inspect-vmo]: https://fuchsia.dev/fuchsia-src/reference/diagnostics/inspect/vmo-format |
| //! [inspector]: Inspector |
| |
| use { |
| crate::{ |
| format::{ |
| block::{ArrayFormat, LinkNodeDisposition, PropertyFormat}, |
| constants, |
| }, |
| heap::Heap, |
| state::State, |
| }, |
| anyhow, |
| derivative::Derivative, |
| diagnostics_hierarchy::testing::DiagnosticsHierarchyGetter, |
| fidl::endpoints::DiscoverableService, |
| fidl_fuchsia_inspect::TreeMarker, |
| fidl_fuchsia_io::{DirectoryMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE}, |
| fuchsia_component::server::{ServiceFs, ServiceObjTrait}, |
| fuchsia_zircon::{self as zx, HandleBased}, |
| futures::{future::BoxFuture, prelude::*}, |
| lazy_static::lazy_static, |
| mapped_vmo::Mapping, |
| parking_lot::Mutex, |
| paste, |
| std::{ |
| borrow::Cow, |
| cmp::max, |
| default::Default, |
| fmt::Debug, |
| sync::{ |
| atomic::{AtomicUsize, Ordering}, |
| Arc, Weak, |
| }, |
| }, |
| tracing::error, |
| vfs::{ |
| directory::entry::DirectoryEntry, execution_scope::ExecutionScope, path::Path, |
| pseudo_directory, service as pseudo_fs_service, |
| }, |
| }; |
| |
| #[cfg(test)] |
| use crate::format::block::Block; |
| |
| pub use diagnostics_hierarchy::{ |
| DiagnosticsHierarchy, ExponentialHistogramParams, LinearHistogramParams, |
| }; |
| pub use testing::{assert_inspect_tree, tree_assertion}; |
| |
| pub use {crate::error::Error, crate::state::Stats}; |
| |
| pub mod component; |
| mod error; |
| pub mod format; |
| pub mod health; |
| pub mod heap; |
| pub mod reader; |
| pub mod service; |
| mod state; |
| mod utils; |
| |
| pub mod testing { |
| pub use diagnostics_hierarchy::{ |
| assert_data_tree as assert_inspect_tree, |
| testing::{ |
| AnyProperty, DiagnosticsHierarchyGetter, HistogramAssertion, NonZeroUintProperty, |
| PropertyAssertion, TreeAssertion, |
| }, |
| tree_assertion, |
| }; |
| } |
| |
| /// Directiory within the outgoing directory of a component where the diagnostics service should be |
| /// added. |
| pub const DIAGNOSTICS_DIR: &str = "diagnostics"; |
| |
| lazy_static! { |
| // Suffix used for unique names. |
| static ref UNIQUE_NAME_SUFFIX: AtomicUsize = AtomicUsize::new(0); |
| } |
| |
| /// Root of the Inspect API. Through this API, further nodes can be created and inspect can be |
| /// served. |
| #[derive(Clone)] |
| pub struct Inspector { |
| /// The root node. |
| root_node: Arc<Node>, |
| |
| /// The VMO backing the inspector |
| pub(in crate) vmo: Option<Arc<zx::Vmo>>, |
| } |
| |
| impl DiagnosticsHierarchyGetter<String> for Inspector { |
| fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy> { |
| let hierarchy = futures::executor::block_on(async move { reader::read(self).await }) |
| .expect("failed to get hierarchy"); |
| Cow::Owned(hierarchy) |
| } |
| } |
| |
| /// Holds a list of inspect types that won't change. |
| #[derive(Derivative)] |
| #[derivative(Debug, PartialEq, Eq)] |
| struct ValueList { |
| #[derivative(PartialEq = "ignore")] |
| #[derivative(Debug = "ignore")] |
| values: Mutex<Option<InspectTypeList>>, |
| } |
| |
| impl Default for ValueList { |
| fn default() -> Self { |
| ValueList::new() |
| } |
| } |
| |
| type InspectTypeList = Vec<Box<dyn InspectType>>; |
| |
| impl ValueList { |
| /// Creates a new empty value list. |
| pub fn new() -> Self { |
| Self { values: Mutex::new(None) } |
| } |
| |
| /// Stores an inspect type that won't change. |
| pub fn record(&self, value: impl InspectType + 'static) { |
| let boxed_value = Box::new(value); |
| let mut values_lock = self.values.lock(); |
| if let Some(ref mut values) = *values_lock { |
| values.push(boxed_value); |
| } else { |
| *values_lock = Some(vec![boxed_value]); |
| } |
| } |
| } |
| |
| impl Inspector { |
| /// Initializes a new Inspect VMO object with the |
| /// [`defalt maximum size`][constants::DEFAULT_VMO_SIZE_BYTES]. |
| pub fn new() -> Self { |
| Inspector::new_with_size(constants::DEFAULT_VMO_SIZE_BYTES) |
| } |
| |
| /// Returns statistics about the current inspect state. |
| pub fn stats(&self) -> Option<Stats> { |
| self.root_node |
| .inner |
| .inner_ref() |
| .and_then(|inner_ref| inner_ref.state.try_lock().ok().map(|state| state.stats())) |
| } |
| |
| /// True if the Inspector was created successfully (it's not No-Op) |
| pub fn is_valid(&self) -> bool { |
| self.vmo.is_some() && self.root_node.is_valid() |
| } |
| |
| /// Initializes a new Inspect VMO object with the given maximum size. If the |
| /// given size is less than 4K, it will be made 4K which is the minimum size |
| /// the VMO should have. |
| pub fn new_with_size(max_size: usize) -> Self { |
| match Inspector::new_root(max_size) { |
| Ok((vmo, root_node)) => { |
| Inspector { vmo: Some(Arc::new(vmo)), root_node: Arc::new(root_node) } |
| } |
| Err(e) => { |
| error!("Failed to create root node. Error: {:?}", e); |
| Inspector::new_no_op() |
| } |
| } |
| } |
| |
| /// Returns a duplicate of the underlying VMO for this Inspector. |
| /// |
| /// The duplicated VMO will be read-only, and is suitable to send to clients over FIDL. |
| pub fn duplicate_vmo(&self) -> Option<zx::Vmo> { |
| self.vmo |
| .as_ref() |
| .map(|vmo| { |
| vmo.duplicate_handle(zx::Rights::BASIC | zx::Rights::READ | zx::Rights::MAP).ok() |
| }) |
| .unwrap_or(None) |
| } |
| |
| /// Returns a VMO holding a copy of the data in this inspector. |
| /// |
| /// The copied VMO will be read-only. |
| pub fn copy_vmo(&self) -> Option<zx::Vmo> { |
| self.copy_vmo_data().and_then(|data| { |
| if let Ok(vmo) = zx::Vmo::create(data.len() as u64) { |
| vmo.write(&data, 0).ok().map(|_| vmo) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| /// Returns a copy of the bytes stored in the VMO for this inspector. |
| /// |
| /// The output will be truncated to only those bytes that are needed to accurately read the |
| /// stored data. |
| pub fn copy_vmo_data(&self) -> Option<Vec<u8>> { |
| self.root_node.inner.inner_ref().map(|inner_ref| inner_ref.state.copy_vmo_bytes()) |
| } |
| |
| /// Spawns a server for handling `fuchsia.inspect.Tree` requests in the outgoing diagnostics |
| /// directory. |
| pub fn serve<'a, ServiceObjTy: ServiceObjTrait>( |
| &self, |
| service_fs: &mut ServiceFs<ServiceObjTy>, |
| ) -> Result<(), Error> { |
| let (proxy, server) = fidl::endpoints::create_proxy::<DirectoryMarker>() |
| .map_err(|e| Error::Fidl(e.into()))?; |
| let inspector_clone = self.clone(); |
| let dir = pseudo_directory! { |
| TreeMarker::SERVICE_NAME => pseudo_fs_service::host(move |stream| { |
| let inspector_clone_clone = inspector_clone.clone(); |
| async move { |
| service::handle_request_stream( |
| inspector_clone_clone, service::TreeServerSettings::default(), stream |
| ) |
| .await |
| .unwrap_or_else(|e| error!("failed to run server: {:?}", e)); |
| } |
| .boxed() |
| }), |
| }; |
| |
| let server_end = server.into_channel().into(); |
| let scope = ExecutionScope::new(); |
| dir.open(scope, OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE, 0, Path::empty(), server_end); |
| service_fs.add_remote(DIAGNOSTICS_DIR, proxy); |
| |
| Ok(()) |
| } |
| |
| /// Returns the root node of the inspect hierarchy. |
| pub fn root(&self) -> &Node { |
| &self.root_node |
| } |
| |
| fn state(&self) -> Option<State> { |
| self.root().inner.inner_ref().map(|inner_ref| inner_ref.state.clone()) |
| } |
| |
| /// Creates a new No-Op inspector |
| fn new_no_op() -> Self { |
| Inspector { vmo: None, root_node: Arc::new(Node::new_no_op()) } |
| } |
| |
| /// Allocates a new VMO and initializes it. |
| fn new_root(max_size: usize) -> Result<(zx::Vmo, Node), Error> { |
| let mut size = max(constants::MINIMUM_VMO_SIZE_BYTES, max_size); |
| // If the size is not a multiple of 4096, round up. |
| if size % constants::MINIMUM_VMO_SIZE_BYTES != 0 { |
| size = |
| (1 + size / constants::MINIMUM_VMO_SIZE_BYTES) * constants::MINIMUM_VMO_SIZE_BYTES; |
| } |
| let (mapping, vmo) = Mapping::allocate_with_name(size, "InspectHeap") |
| .map_err(|status| Error::AllocateVmo(status))?; |
| let heap = Heap::new(Arc::new(mapping)).map_err(|e| Error::CreateHeap(Box::new(e)))?; |
| let state = State::create(heap).map_err(|e| Error::CreateState(Box::new(e)))?; |
| Ok((vmo, Node::new_root(state))) |
| } |
| |
| /// Creates an no-op inspector from the given Vmo. If the VMO is corrupted, reading can fail. |
| fn no_op_from_vmo(vmo: Arc<zx::Vmo>) -> Inspector { |
| Inspector { vmo: Some(vmo), root_node: Arc::new(Node::new_no_op()) } |
| } |
| } |
| |
| /// Trait implemented by all inspect types. |
| pub trait InspectType: Send + Sync {} |
| |
| /// Trait implemented by all inspect types. It provides constructor functions that are not |
| /// intended for use outside the crate. |
| trait InspectTypeInternal { |
| fn new(state: State, block_index: u32) -> Self; |
| fn new_no_op() -> Self; |
| fn is_valid(&self) -> bool; |
| } |
| |
| /// An inner type of all inspect nodes and properties. Each variant implies a |
| /// different relationship with the underlying inspect VMO. |
| #[derive(Debug, Derivative)] |
| #[derivative(Default)] |
| enum Inner<T: InnerType> { |
| /// The node or property is not attached to the inspect VMO. |
| #[derivative(Default)] |
| None, |
| |
| /// The node or property is attached to the inspect VMO, iff its strong |
| /// reference is still alive. |
| Weak(Weak<InnerRef<T>>), |
| |
| /// The node or property is attached to the inspect VMO. |
| Strong(Arc<InnerRef<T>>), |
| } |
| |
| impl<T: InnerType> Inner<T> { |
| /// Creates a new Inner with the desired block index within the inspect VMO |
| fn new(state: State, block_index: u32) -> Self { |
| Self::Strong(Arc::new(InnerRef { state, block_index, data: T::Data::default() })) |
| } |
| |
| /// Returns true if the number of strong references to this node or property |
| /// is greater than 0. |
| fn is_valid(&self) -> bool { |
| match self { |
| Self::None => false, |
| Self::Weak(weak_ref) => weak_ref.strong_count() > 0, |
| Self::Strong(_) => true, |
| } |
| } |
| |
| /// Returns a `Some(Arc<InnerRef>)` iff the node or property is currently |
| /// attached to inspect, or `None` otherwise. Weak pointers are upgraded |
| /// if possible, but their lifetime as strong references are expected to be |
| /// short. |
| fn inner_ref(&self) -> Option<Arc<InnerRef<T>>> { |
| match self { |
| Self::None => None, |
| Self::Weak(weak_ref) => weak_ref.upgrade(), |
| Self::Strong(inner_ref) => Some(Arc::clone(inner_ref)), |
| } |
| } |
| |
| /// Make a weak reference. |
| fn clone_weak(&self) -> Self { |
| match self { |
| Self::None => Self::None, |
| Self::Weak(weak_ref) => Self::Weak(weak_ref.clone()), |
| Self::Strong(inner_ref) => Self::Weak(Arc::downgrade(inner_ref)), |
| } |
| } |
| } |
| |
| /// Inspect API types implement Eq,PartialEq returning true all the time so that |
| /// structs embedding inspect types can derive these traits as well. |
| /// IMPORTANT: Do not rely on these traits implementations for real comparisons |
| /// or validation tests, instead leverage the reader. |
| impl<T: InnerType> PartialEq for Inner<T> { |
| fn eq(&self, _other: &Self) -> bool { |
| true |
| } |
| } |
| |
| impl<T: InnerType> Eq for Inner<T> {} |
| |
| /// A type that is owned by inspect nodes and properties, sharing ownership of |
| /// the inspect VMO heap, and with numerical pointers to the location in the |
| /// heap in which it resides. |
| #[derive(Debug)] |
| struct InnerRef<T: InnerType> { |
| /// Index of the block in the VMO. |
| block_index: u32, |
| |
| /// Reference to the VMO heap. |
| state: State, |
| |
| /// Associated data for this type. |
| data: T::Data, |
| } |
| |
| impl<T: InnerType> Drop for InnerRef<T> { |
| /// InnerRef has a manual drop impl, to guarantee a single deallocation in |
| /// the case of multiple strong references. |
| fn drop(&mut self) { |
| T::free(&self.state, self.block_index).unwrap(); |
| } |
| } |
| |
| /// De-allocation behavior and associated data for an inner type. |
| trait InnerType { |
| /// Associated data stored on the InnerRef |
| type Data: Default + Debug; |
| |
| /// De-allocation behavior for when the InnerRef gets dropped |
| fn free(state: &State, block_index: u32) -> Result<(), Error>; |
| } |
| |
| #[derive(Default, Debug)] |
| 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 == 0 { |
| return Ok(()); |
| } |
| let mut state_lock = state.try_lock()?; |
| state_lock.free_value(block_index).map_err(|err| Error::free("node", block_index, err)) |
| } |
| } |
| |
| #[derive(Default, Debug)] |
| struct InnerValueType; |
| |
| impl InnerType for InnerValueType { |
| type Data = (); |
| fn free(state: &State, block_index: u32) -> Result<(), Error> { |
| let mut state_lock = state.try_lock()?; |
| state_lock.free_value(block_index).map_err(|err| Error::free("value", block_index, err)) |
| } |
| } |
| |
| #[derive(Default, Debug)] |
| struct InnerPropertyType; |
| |
| impl InnerType for InnerPropertyType { |
| type Data = (); |
| fn free(state: &State, block_index: u32) -> Result<(), Error> { |
| let mut state_lock = state.try_lock()?; |
| state_lock |
| .free_property(block_index) |
| .map_err(|err| Error::free("property", block_index, err)) |
| } |
| } |
| |
| #[derive(Default, Debug)] |
| struct InnerLazyNodeType; |
| |
| impl InnerType for InnerLazyNodeType { |
| type Data = (); |
| fn free(state: &State, block_index: u32) -> Result<(), Error> { |
| let mut state_lock = state.try_lock()?; |
| state_lock |
| .free_lazy_node(block_index) |
| .map_err(|err| Error::free("lazy node", block_index, err)) |
| } |
| } |
| |
| // Utility for generating the implementation of all inspect types (including the struct): |
| // - All Inspect Types (*Property, Node) can be No-Op. This macro generates the |
| // appropiate internal constructors. |
| // - All Inspect Types derive PartialEq, Eq. This generates the dummy implementation |
| // for the wrapped type. |
| macro_rules! inspect_type_impl { |
| ($(#[$attr:meta])* struct $name:ident, $type:ident) => { |
| paste::paste! { |
| $(#[$attr])* |
| /// |
| /// 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 $name { |
| inner: Inner<$type>, |
| } |
| |
| #[cfg(test)] |
| impl $name { |
| /// Returns the [`Block`][Block] associated with this value. |
| pub 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() |
| }) |
| } |
| |
| /// Returns the index of the value's block in the VMO. |
| pub fn block_index(&self) -> u32 { |
| self.inner.inner_ref().unwrap().block_index |
| } |
| } |
| |
| impl InspectType for $name {} |
| |
| impl InspectTypeInternal for $name { |
| fn new(state: State, block_index: u32) -> Self { |
| Self { |
| inner: Inner::new(state, block_index), |
| } |
| } |
| |
| fn is_valid(&self) -> bool { |
| self.inner.is_valid() |
| } |
| |
| fn new_no_op() -> Self { |
| Self { inner: Inner::None } |
| } |
| } |
| } |
| } |
| } |
| |
| // Utility for generating functions to create a numeric property. |
| // `name`: identifier for the name (example: double) |
| // `name_cap`: identifier for the name capitalized (example: Double) |
| // `type`: the type of the numeric property (example: f64) |
| macro_rules! create_numeric_property_fn { |
| ($name:ident, $name_cap:ident, $type:ident) => { |
| paste::paste! { |
| #[doc = "Creates a new `" $name_cap "` with the given `name` and `value`."] |
| #[must_use] |
| pub fn [<create_ $name >](&self, name: impl AsRef<str>, value: $type) |
| -> [<$name_cap Property>] { |
| self.inner.inner_ref().and_then(|inner_ref| { |
| inner_ref.state |
| .try_lock() |
| .and_then(|mut state| { |
| state.[<create_ $name _metric>]( |
| name.as_ref(), value, inner_ref.block_index) |
| }) |
| .map(|block| { |
| [<$name_cap Property>]::new(inner_ref.state.clone(), block.index()) |
| }) |
| .ok() |
| }) |
| .unwrap_or([<$name_cap Property>]::new_no_op()) |
| } |
| |
| #[doc = "Records a new `" $name_cap "` with the given `name` and `value`."] |
| pub fn [<record_ $name >](&self, name: impl AsRef<str>, value: $type) { |
| let property = self.[<create_ $name>](name, value); |
| self.record(property); |
| } |
| } |
| }; |
| } |
| |
| // Utility for generating functions to create an array property. |
| // `name`: identifier for the name (example: double) |
| // `name_cap`: identifier for the name capitalized (example: Double) |
| // `type`: the type of the numeric property (example: f64) |
| macro_rules! create_array_property_fn { |
| ($name:ident, $name_cap:ident, $type:ident) => { |
| paste::paste! { |
| #[doc = "Creates a new `" $name_cap "ArrayProperty` with the given `name` and `slots`."] |
| #[must_use] |
| pub fn [<create_ $name _array>](&self, name: impl AsRef<str>, slots: usize) |
| -> [<$name_cap ArrayProperty>] { |
| self.[<create_ $name _array_internal>](name, slots, ArrayFormat::Default) |
| } |
| |
| fn [<create_ $name _array_internal>]( |
| &self, name: impl AsRef<str>, slots: usize, format: ArrayFormat) |
| -> [<$name_cap ArrayProperty>] { |
| self.inner.inner_ref().and_then(|inner_ref| { |
| inner_ref.state |
| .try_lock() |
| .and_then(|mut state| { |
| state.[<create_ $name _array>]( |
| name.as_ref(), slots, format, inner_ref.block_index) |
| }) |
| .map(|block| { |
| [<$name_cap ArrayProperty>]::new(inner_ref.state.clone(), block.index()) |
| }) |
| .ok() |
| }) |
| .unwrap_or([<$name_cap ArrayProperty>]::new_no_op()) |
| } |
| } |
| }; |
| } |
| |
| // Utility for generating functions to create a linear histogram property. |
| // `name`: identifier for the name (example: double) |
| // `name_cap`: identifier for the name capitalized (example: Double) |
| // `type`: the type of the numeric property (example: f64) |
| macro_rules! create_linear_histogram_property_fn { |
| ($name:ident, $name_cap:ident, $type:ident) => { |
| paste::paste! { |
| #[doc = "Creates a new `" $name_cap |
| "LinearHistogramProperty` with the given `name` and `params`."] |
| #[must_use] |
| pub fn [<create_ $name _linear_histogram>]( |
| &self, name: impl AsRef<str>, params: LinearHistogramParams<$type>) |
| -> [<$name_cap LinearHistogramProperty>] { |
| let slots = params.buckets + constants::LINEAR_HISTOGRAM_EXTRA_SLOTS; |
| let array = self.[<create_ $name _array_internal>]( |
| name, slots, ArrayFormat::LinearHistogram); |
| array.set(0, params.floor); |
| array.set(1, params.step_size); |
| [<$name_cap LinearHistogramProperty>] { |
| floor: params.floor, |
| step_size: params.step_size, |
| slots, |
| array |
| } |
| } |
| } |
| }; |
| } |
| |
| // Utility for generating functions to create an exponential histogram property. |
| // `name`: identifier for the name (example: double) |
| // `name_cap`: identifier for the name capitalized (example: Double) |
| // `type`: the type of the numeric property (example: f64) |
| macro_rules! create_exponential_histogram_property_fn { |
| ($name:ident, $name_cap:ident, $type:ident) => { |
| paste::paste! { |
| #[doc = "Creates a new `" $name_cap |
| "ExponentialHistogramProperty` with the given `name` and `params`."] |
| #[must_use] |
| pub fn [<create_ $name _exponential_histogram>]( |
| &self, name: impl AsRef<str>, params: ExponentialHistogramParams<$type>) |
| -> [<$name_cap ExponentialHistogramProperty>] { |
| let slots = params.buckets + constants::EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS; |
| let array = self.[<create_ $name _array_internal>]( |
| name, slots, ArrayFormat::ExponentialHistogram); |
| array.set(0, params.floor); |
| array.set(1, params.initial_step); |
| array.set(2, params.step_multiplier); |
| [<$name_cap ExponentialHistogramProperty>] { |
| floor: params.floor, |
| initial_step: params.initial_step, |
| step_multiplier: params.step_multiplier, |
| slots, |
| array |
| } |
| } |
| } |
| }; |
| } |
| |
| /// Utility for generating functions to create lazy nodes. |
| /// `fn_suffix`: identifier for the fn name. |
| /// `disposition`: identifier for the type of LinkNodeDisposition. |
| macro_rules! create_lazy_property_fn { |
| ($fn_suffix:ident, $disposition:ident) => { |
| paste::paste! { |
| #[must_use] |
| #[doc = "Creates a new lazy " $fn_suffix " link with the given `name` and `callback`."] |
| pub fn [<create_lazy_ $fn_suffix>]<F>(&self, name: impl AsRef<str>, 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.as_ref(), |
| inner_ref.block_index, |
| LinkNodeDisposition::$disposition, |
| callback, |
| )) |
| .map(|block| LazyNode::new(inner_ref.state.clone(), block.index())) |
| .ok() |
| |
| }) |
| .unwrap_or(LazyNode::new_no_op()) |
| } |
| |
| #[doc = "Records a new lazy " $fn_suffix " link with the given `name` and `callback`."] |
| pub fn [<record_lazy_ $fn_suffix>]<F>( |
| &self, name: impl AsRef<str>, callback: F) |
| where F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static { |
| let property = self.[<create_lazy_ $fn_suffix>](name, callback); |
| self.record(property); |
| } |
| } |
| } |
| } |
| |
| inspect_type_impl!( |
| /// Inspect Node data type. |
| struct Node, |
| InnerNodeType |
| ); |
| |
| inspect_type_impl!( |
| /// Inspect Lazy Node data type. |
| struct LazyNode, |
| InnerLazyNodeType |
| ); |
| |
| impl Node { |
| /// Creates a new root node. |
| pub(in crate) fn new_root(state: State) -> Node { |
| Node::new(state, 0) |
| } |
| |
| /// 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() } |
| } |
| |
| /// Creates and keeps track of a child with the given `name`. |
| pub fn record_child<F>(&self, name: impl AsRef<str>, initialize: F) |
| where |
| F: FnOnce(&mut Node), |
| { |
| let mut child = self.create_child(name); |
| initialize(&mut child); |
| self.record(child); |
| } |
| |
| /// Add a child to this node. |
| #[must_use] |
| pub fn create_child(&self, name: impl AsRef<str>) -> Node { |
| self.inner |
| .inner_ref() |
| .and_then(|inner_ref| { |
| inner_ref |
| .state |
| .try_lock() |
| .and_then(|mut state| state.create_node(name.as_ref(), inner_ref.block_index)) |
| .map(|block| Node::new(inner_ref.state.clone(), block.index())) |
| .ok() |
| }) |
| .unwrap_or(Node::new_no_op()) |
| } |
| |
| /// 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)); |
| } |
| |
| // Add a lazy node property to this node: |
| // - create_lazy_node: adds a lazy child to this node. This node will be |
| // populated by the given callback on demand. |
| // - create_lazy_values: adds a lazy child to this node. The lazy node |
| // children and properties are added to this node on demand. Name is only |
| // used in the event that a reader does not obtain the values. |
| create_lazy_property_fn!(child, Child); |
| create_lazy_property_fn!(values, Inline); |
| |
| // Add a numeric property to this node: create_int, create_double, |
| // create_uint. |
| create_numeric_property_fn!(int, Int, i64); |
| create_numeric_property_fn!(uint, Uint, u64); |
| create_numeric_property_fn!(double, Double, f64); |
| |
| // Add an array property to this node: create_int_array, create_double_array, |
| // create_uint_array. |
| create_array_property_fn!(int, Int, i64); |
| create_array_property_fn!(uint, Uint, u64); |
| create_array_property_fn!(double, Double, f64); |
| |
| // Add a linear histogram property to this node: create_int_linear_histogram, |
| // create_uint_linear_histogram, create_double_linear_histogram. |
| create_linear_histogram_property_fn!(int, Int, i64); |
| create_linear_histogram_property_fn!(uint, Uint, u64); |
| create_linear_histogram_property_fn!(double, Double, f64); |
| |
| // Add an exponential histogram property to this node: create_int_exponential_histogram, |
| // create_uint_exponential_histogram, create_double_exponential_histogram. |
| create_exponential_histogram_property_fn!(int, Int, i64); |
| create_exponential_histogram_property_fn!(uint, Uint, u64); |
| create_exponential_histogram_property_fn!(double, Double, f64); |
| |
| /// Creates a lazy node from the given VMO. |
| #[must_use] |
| pub fn create_lazy_child_from_vmo(&self, name: impl AsRef<str>, vmo: Arc<zx::Vmo>) -> LazyNode { |
| self.create_lazy_child(name.as_ref(), 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(&self, name: impl AsRef<str>, vmo: Arc<zx::Vmo>) { |
| self.record_lazy_child(name.as_ref(), 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(&self, name: impl AsRef<str>, 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.as_ref(), |
| 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(&self, name: impl AsRef<str>, 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(&self, name: impl AsRef<str>, 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.as_ref(), |
| 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(&self, name: impl AsRef<str>, 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(&self, name: impl AsRef<str>, value: bool) -> BoolProperty { |
| self.inner |
| .inner_ref() |
| .and_then(|inner_ref| { |
| inner_ref |
| .state |
| .try_lock() |
| .and_then(|mut state| { |
| state.create_bool(name.as_ref(), 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(&self, name: impl AsRef<str>, value: bool) { |
| let property = self.create_bool(name, value); |
| self.record(property); |
| } |
| } |
| |
| /// Trait implemented by properties. |
| pub trait Property<'t> { |
| /// The type of the property. |
| type Type; |
| |
| /// Set the property value to |value|. |
| fn set(&'t self, value: Self::Type); |
| } |
| |
| /// Trait implemented by numeric properties providing common operations. |
| pub trait NumericProperty { |
| /// The type the property is handling. |
| type Type; |
| |
| /// Add the given |value| to the property current value. |
| fn add(&self, value: Self::Type); |
| |
| /// Subtract the given |value| from the property current value. |
| fn subtract(&self, value: Self::Type); |
| |
| /// Return the current value of the property for testing. |
| /// NOTE: This is a temporary feature to aid unit test of Inspect clients. |
| /// It will be replaced by a more comprehensive Read API implementation. |
| fn get(&self) -> Result<Self::Type, Error>; |
| } |
| |
| // Utility for generating numeric property functions (example: set, add, subtract) |
| // `fn_name`: the name of the function to generate (example: set) |
| // `type`: the type of the argument of the function to generate (example: f64) |
| // `name`: the readble name of the type of the function (example: double) |
| macro_rules! numeric_property_fn { |
| ($fn_name:ident, $type:ident, $name:ident) => { |
| paste::paste! { |
| // Docs here come from the trait docs. |
| fn $fn_name(&self, value: $type) { |
| if let Some(ref inner_ref) = self.inner.inner_ref() { |
| inner_ref.state.try_lock() |
| .and_then(|state| { |
| state.[<$fn_name _ $name _metric>](inner_ref.block_index, value) |
| }) |
| .unwrap_or_else(|e| { |
| error!("Failed to {} property. Error: {:?}", stringify!($fn_name), e); |
| }); |
| } |
| } |
| } |
| }; |
| } |
| |
| // Utility for generating a numeric property datatype impl |
| // `name`: the readble name of the type of the function (example: double) |
| // `name_cap`: the capitalized readble name of the type of the function (example: Double) |
| // `type`: the type of the argument of the function to generate (example: f64) |
| macro_rules! numeric_property { |
| ($name:ident, $name_cap:ident, $type:ident) => { |
| paste::paste! { |
| inspect_type_impl!( |
| /// Inspect API Numeric Property data type. |
| struct [<$name_cap Property>], |
| InnerValueType |
| ); |
| |
| impl<'t> Property<'t> for [<$name_cap Property>] { |
| type Type = $type; |
| |
| numeric_property_fn!(set, $type, $name); |
| } |
| |
| impl NumericProperty for [<$name_cap Property>] { |
| type Type = $type; |
| numeric_property_fn!(add, $type, $name); |
| numeric_property_fn!(subtract, $type, $name); |
| |
| fn get(&self) -> Result<$type, Error> { |
| if let Some(ref inner_ref) = self.inner.inner_ref() { |
| inner_ref.state |
| .try_lock() |
| .and_then(|state| state.[<get_ $name _metric>](inner_ref.block_index)) |
| } else { |
| Err(Error::NoOp("Property")) |
| } |
| } |
| } |
| } |
| }; |
| } |
| |
| numeric_property!(int, Int, i64); |
| numeric_property!(uint, Uint, u64); |
| numeric_property!(double, Double, f64); |
| |
| // Utility for generating a byte/string property datatype impl |
| // `name`: the readable name of the type of the function (example: String) |
| // `type`: the type of the argument of the function to generate (example: str) |
| // `bytes`: an optional method to get the bytes of the property |
| macro_rules! property { |
| ($name:ident, $type:ty $(, $bytes:ident)?) => { |
| paste::paste! { |
| inspect_type_impl!( |
| /// Inspect API Property data type. |
| struct [<$name Property>], |
| InnerPropertyType |
| ); |
| |
| impl<'t> Property<'t> for [<$name Property>] { |
| type Type = &'t $type; |
| |
| fn set(&'t self, value: &'t $type) { |
| if let Some(ref inner_ref) = self.inner.inner_ref() { |
| inner_ref.state |
| .try_lock() |
| .and_then(|mut state| { |
| state.set_property(inner_ref.block_index, value$(.$bytes())?) |
| }) |
| .unwrap_or_else(|e| error!("Failed to set property. Error: {:?}", e)); |
| } |
| } |
| |
| } |
| } |
| }; |
| } |
| |
| property!(String, str, as_bytes); |
| property!(Bytes, [u8]); |
| |
| inspect_type_impl!( |
| /// Inspect API Bool Property data type. |
| struct BoolProperty, |
| InnerValueType |
| ); |
| |
| impl<'t> Property<'t> for BoolProperty { |
| type Type = bool; |
| |
| fn set(&self, value: bool) { |
| if let Some(ref inner_ref) = self.inner.inner_ref() { |
| inner_ref |
| .state |
| .try_lock() |
| .and_then(|state| state.set_bool(inner_ref.block_index, value)) |
| .unwrap_or_else(|e| { |
| error!("Failed to set property. Error: {:?}", e); |
| }); |
| } |
| } |
| } |
| |
| /// Trait implemented by all array properties providing common operations on arrays. |
| pub trait ArrayProperty { |
| /// The type of the array entries. |
| type Type; |
| |
| /// Sets the array value to `value` at the given `index`. |
| fn set(&self, index: usize, value: Self::Type); |
| |
| /// Adds the given `value` to the property current value at the given `index`. |
| fn add(&self, index: usize, value: Self::Type); |
| |
| /// Subtracts the given `value` to the property current value at the given `index`. |
| fn subtract(&self, index: usize, value: Self::Type); |
| |
| /// Sets all slots of the array to 0. |
| fn clear(&self); |
| } |
| |
| // Utility for generating array property functions (example: set, add, subtract) |
| // `fn_name`: the name of the function to generate (example: set) |
| // `type`: the type of the argument of the function to generate (example: f64) |
| // `name`: the readble name of the type of the function (example: double) |
| macro_rules! array_property_fn { |
| ($fn_name:ident, $type:ident, $name:ident) => { |
| paste::paste! { |
| // Docs here come from the trait docs. |
| fn $fn_name(&self, index: usize, value: $type) { |
| if let Some(ref inner_ref) = self.inner.inner_ref() { |
| inner_ref.state |
| .try_lock() |
| .and_then(|mut state| { |
| state.[<$fn_name _array_ $name _slot>]( |
| inner_ref.block_index, index, value) |
| }) |
| .unwrap_or_else(|e| { |
| error!("Failed to {} property. Error: {:?}", stringify!($fn_name), e); |
| }); |
| } |
| } |
| } |
| }; |
| } |
| |
| // Utility for generating a numeric array datatype impl |
| // `name`: the readble name of the type of the function (example: double) |
| // `type`: the type of the argument of the function to generate (example: f64) |
| macro_rules! array_property { |
| ($name:ident, $name_cap:ident, $type:ident) => { |
| paste::paste! { |
| inspect_type_impl!( |
| /// Inspect API Array Property data type. |
| struct [<$name_cap ArrayProperty>], |
| InnerValueType |
| ); |
| |
| impl [<$name_cap ArrayProperty>] { |
| } |
| |
| impl ArrayProperty for [<$name_cap ArrayProperty>] { |
| type Type = $type; |
| |
| array_property_fn!(set, $type, $name); |
| array_property_fn!(add, $type, $name); |
| array_property_fn!(subtract, $type, $name); |
| |
| fn clear(&self) { |
| if let Some(ref inner_ref) = self.inner.inner_ref() { |
| inner_ref.state |
| .try_lock() |
| .and_then(|mut state| state.clear_array(inner_ref.block_index, 0)) |
| .unwrap_or_else(|e| { |
| error!("Failed to clear property. Error: {:?}", e); |
| }); |
| } |
| } |
| } |
| } |
| }; |
| } |
| |
| array_property!(int, Int, i64); |
| array_property!(uint, Uint, u64); |
| array_property!(double, Double, f64); |
| |
| /// Trait implemented by all hitogram properties providing common operations. |
| pub trait HistogramProperty { |
| /// The type of each value added to the histogram. |
| type Type; |
| |
| /// Inserts the given `value` in the histogram. |
| fn insert(&self, value: Self::Type); |
| |
| /// Inserts the given `value` in the histogram `count` times. |
| fn insert_multiple(&self, value: Self::Type, count: usize); |
| |
| /// Clears all buckets of the histogram. |
| fn clear(&self); |
| } |
| |
| macro_rules! histogram_property { |
| ($histogram_type:ident, $name_cap:ident, $type:ident, $clear_start_index:expr) => { |
| paste::paste! { |
| impl HistogramProperty for [<$name_cap $histogram_type HistogramProperty>] { |
| type Type = $type; |
| |
| fn insert(&self, value: $type) { |
| self.insert_multiple(value, 1); |
| } |
| |
| fn insert_multiple(&self, value: $type, count: usize) { |
| self.array.add(self.get_index(value), count as $type); |
| } |
| |
| fn clear(&self) { |
| if let Some(ref inner_ref) = self.array.inner.inner_ref() { |
| // Ensure we don't delete the array slots that contain histogram metadata. |
| inner_ref.state |
| .try_lock() |
| .and_then(|mut state| { |
| state.clear_array(inner_ref.block_index, $clear_start_index) |
| }) |
| .unwrap_or_else(|e| { |
| error!("Failed to {} property. Error: {:?}", stringify!($fn_name), e); |
| }); |
| } |
| } |
| } |
| } |
| }; |
| } |
| |
| macro_rules! linear_histogram_property { |
| ($name_cap:ident, $type:ident) => { |
| paste::paste! { |
| #[derive(Debug)] |
| #[doc = "A linear histogram property for " $type " values."] |
| pub struct [<$name_cap LinearHistogramProperty>] { |
| array: [<$name_cap ArrayProperty>], |
| floor: $type, |
| slots: usize, |
| step_size: $type, |
| } |
| |
| impl [<$name_cap LinearHistogramProperty>] { |
| fn get_index(&self, value: $type) -> usize { |
| let mut current_floor = self.floor; |
| // Start in the underflow index. |
| let mut index = constants::LINEAR_HISTOGRAM_EXTRA_SLOTS - 2; |
| while value >= current_floor && index < self.slots - 1 { |
| current_floor += self.step_size; |
| index += 1; |
| } |
| index as usize |
| } |
| |
| #[cfg(test)] |
| fn get_block(&self) -> Option<Block<Arc<Mapping>>> { |
| self.array.get_block() |
| } |
| } |
| |
| histogram_property!( |
| Linear, $name_cap, $type, |
| // -2 = the overflow and underflow slots which still need to be cleared. |
| constants::LINEAR_HISTOGRAM_EXTRA_SLOTS - 2); |
| } |
| }; |
| } |
| |
| macro_rules! exponential_histogram_property { |
| ($name_cap:ident, $type:ident) => { |
| paste::paste! { |
| #[derive(Debug)] |
| #[doc = "An exponential histogram property for " $type " values."] |
| pub struct [<$name_cap ExponentialHistogramProperty>] { |
| array: [<$name_cap ArrayProperty>], |
| floor: $type, |
| initial_step: $type, |
| step_multiplier: $type, |
| slots: usize, |
| } |
| |
| impl [<$name_cap ExponentialHistogramProperty>] { |
| fn get_index(&self, value: $type) -> usize { |
| let mut current_floor = self.floor; |
| let mut offset = self.initial_step; |
| // Start in the underflow index. |
| let mut index = constants::EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS - 2; |
| while value >= current_floor && index < self.slots - 1 { |
| current_floor = self.floor + offset; |
| offset *= self.step_multiplier; |
| index += 1; |
| } |
| index as usize |
| } |
| |
| #[cfg(test)] |
| fn get_block(&self) -> Option<Block<Arc<Mapping>>> { |
| self.array.get_block() |
| } |
| } |
| |
| histogram_property!( |
| Exponential, $name_cap, $type, |
| // -2 = the overflow and underflow slots which still need to be cleared. |
| constants::EXPONENTIAL_HISTOGRAM_EXTRA_SLOTS - 2); |
| } |
| }; |
| } |
| |
| linear_histogram_property!(Double, f64); |
| linear_histogram_property!(Int, i64); |
| linear_histogram_property!(Uint, u64); |
| exponential_histogram_property!(Double, f64); |
| exponential_histogram_property!(Int, i64); |
| exponential_histogram_property!(Uint, u64); |
| |
| /// Generates a unique name that can be used in inspect nodes and properties that will be prefixed |
| /// by the given `prefix`. |
| pub fn unique_name(prefix: &str) -> String { |
| let suffix = UNIQUE_NAME_SUFFIX.fetch_add(1, Ordering::Relaxed); |
| format!("{}{}", prefix, suffix) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| assert_inspect_tree, |
| format::{block::LinkNodeDisposition, block_type::BlockType, constants}, |
| heap::Heap, |
| reader, |
| }, |
| anyhow::{bail, format_err}, |
| fdio, |
| fidl::endpoints::DiscoverableService, |
| fidl_fuchsia_sys::ComponentControllerEvent, |
| fuchsia_async as fasync, |
| fuchsia_component::client, |
| fuchsia_component::server::ServiceObj, |
| fuchsia_zircon::AsHandleRef, |
| glob::glob, |
| mapped_vmo::Mapping, |
| std::ffi::CString, |
| }; |
| |
| const TEST_COMPONENT_CMX: &str = "inspect_test_component.cmx"; |
| const TEST_COMPONENT_URL: &str = |
| "fuchsia-pkg://fuchsia.com/fuchsia-inspect-tests#meta/inspect_test_component.cmx"; |
| |
| #[test] |
| fn inspector_new() { |
| let test_object = Inspector::new(); |
| assert_eq!( |
| test_object.vmo.as_ref().unwrap().get_size().unwrap(), |
| constants::DEFAULT_VMO_SIZE_BYTES as u64 |
| ); |
| } |
| |
| #[test] |
| fn inspector_duplicate_vmo() { |
| let test_object = Inspector::new(); |
| assert_eq!( |
| test_object.vmo.as_ref().unwrap().get_size().unwrap(), |
| constants::DEFAULT_VMO_SIZE_BYTES as u64 |
| ); |
| assert_eq!( |
| test_object.duplicate_vmo().unwrap().get_size().unwrap(), |
| constants::DEFAULT_VMO_SIZE_BYTES as u64 |
| ); |
| } |
| |
| #[test] |
| fn inspector_copy_data() { |
| let test_object = Inspector::new(); |
| |
| assert_eq!( |
| test_object.vmo.as_ref().unwrap().get_size().unwrap(), |
| constants::DEFAULT_VMO_SIZE_BYTES as u64 |
| ); |
| // The copy will be a single page, since that is all that is used. |
| assert_eq!(test_object.copy_vmo_data().unwrap().len(), 4096); |
| } |
| |
| #[test] |
| fn no_op() { |
| let inspector = Inspector::new_with_size(4096); |
| // Make the VMO full. |
| let nodes = (0..127).map(|_| inspector.root().create_child("test")).collect::<Vec<Node>>(); |
| |
| assert!(nodes.iter().all(|node| node.is_valid())); |
| let no_op_node = inspector.root().create_child("no-op-child"); |
| assert!(!no_op_node.is_valid()); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn new_no_op() -> Result<(), Error> { |
| let mut fs: ServiceFs<ServiceObj<'_, ()>> = ServiceFs::new(); |
| |
| let inspector = Inspector::new_no_op(); |
| assert!(!inspector.is_valid()); |
| assert!(!inspector.root().is_valid()); |
| |
| // Ensure serve doesn't crash on a No-Op inspector |
| inspector.serve(&mut fs) |
| } |
| |
| #[test] |
| fn inspector_new_with_size() { |
| let test_object = Inspector::new_with_size(8192); |
| assert_eq!(test_object.vmo.as_ref().unwrap().get_size().unwrap(), 8192); |
| assert_eq!( |
| CString::new("InspectHeap").unwrap(), |
| test_object.vmo.as_ref().unwrap().get_name().expect("Has name") |
| ); |
| |
| // If size is not a multiple of 4096, it'll be rounded up. |
| let test_object = Inspector::new_with_size(10000); |
| assert_eq!(test_object.vmo.unwrap().get_size().unwrap(), 12288); |
| |
| // If size is less than the minimum size, the minimum will be set. |
| let test_object = Inspector::new_with_size(2000); |
| assert_eq!(test_object.vmo.unwrap().get_size().unwrap(), 4096); |
| } |
| |
| #[test] |
| fn inspector_new_root() { |
| // Note, the small size we request should be rounded up to a full 4kB page. |
| let (vmo, root_node) = Inspector::new_root(100).unwrap(); |
| assert_eq!(vmo.get_size().unwrap(), 4096); |
| let inner = root_node.inner.inner_ref().unwrap(); |
| assert_eq!(inner.block_index, 0); |
| assert_eq!(CString::new("InspectHeap").unwrap(), vmo.get_name().expect("Has name")); |
| } |
| |
| #[test] |
| fn node() { |
| // Create and use a default value. |
| let default = Node::default(); |
| default.record_int("a", 0); |
| |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| 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); |
| } |
| |
| #[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()); |
| } |
| |
| #[test] |
| fn node_clone_weak() { |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| 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"); |
| } |
| |
| #[test] |
| fn double_property() { |
| // Create and use a default value. |
| let default = DoubleProperty::default(); |
| default.add(1.0); |
| |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| let root = Node::new_root(state); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let property = node.create_double("property", 1.0); |
| let property_block = property.get_block().unwrap(); |
| assert_eq!(property_block.block_type(), BlockType::DoubleValue); |
| assert_eq!(property_block.double_value().unwrap(), 1.0); |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| |
| property.set(2.0); |
| assert_eq!(property_block.double_value().unwrap(), 2.0); |
| assert_eq!(property.get().unwrap(), 2.0); |
| |
| property.subtract(5.5); |
| assert_eq!(property_block.double_value().unwrap(), -3.5); |
| |
| property.add(8.1); |
| assert_eq!(property_block.double_value().unwrap(), 4.6); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn int_property() { |
| // Create and use a default value. |
| let default = IntProperty::default(); |
| default.add(1); |
| |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| let root = Node::new_root(state); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let property = node.create_int("property", 1); |
| let property_block = property.get_block().unwrap(); |
| assert_eq!(property_block.block_type(), BlockType::IntValue); |
| assert_eq!(property_block.int_value().unwrap(), 1); |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| |
| property.set(2); |
| assert_eq!(property_block.int_value().unwrap(), 2); |
| assert_eq!(property.get().unwrap(), 2); |
| |
| property.subtract(5); |
| assert_eq!(property_block.int_value().unwrap(), -3); |
| |
| property.add(8); |
| assert_eq!(property_block.int_value().unwrap(), 5); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn uint_property() { |
| // Create and use a default value. |
| let default = UintProperty::default(); |
| default.add(1); |
| |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| let root = Node::new_root(state); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let property = node.create_uint("property", 1); |
| let property_block = property.get_block().unwrap(); |
| assert_eq!(property_block.block_type(), BlockType::UintValue); |
| assert_eq!(property_block.uint_value().unwrap(), 1); |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| |
| property.set(5); |
| assert_eq!(property_block.uint_value().unwrap(), 5); |
| assert_eq!(property.get().unwrap(), 5); |
| |
| property.subtract(3); |
| assert_eq!(property_block.uint_value().unwrap(), 2); |
| |
| property.add(8); |
| assert_eq!(property_block.uint_value().unwrap(), 10); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn bool_property() { |
| // Create and use a default value. |
| let default = BoolProperty::default(); |
| default.set(true); |
| |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| let root = Node::new_root(state); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let property = node.create_bool("property", true); |
| let property_block = property.get_block().unwrap(); |
| assert_eq!(property_block.block_type(), BlockType::BoolValue); |
| assert_eq!(property_block.bool_value().unwrap(), true); |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| |
| property.set(false); |
| assert_eq!(property_block.bool_value().unwrap(), false); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn string_property() { |
| // Create and use a default value. |
| let default = StringProperty::default(); |
| default.set("test"); |
| |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| let root = Node::new_root(state); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let property = node.create_string("property", "test"); |
| let property_block = property.get_block().unwrap(); |
| assert_eq!(property_block.block_type(), BlockType::BufferValue); |
| assert_eq!(property_block.property_total_length().unwrap(), 4); |
| assert_eq!(property_block.property_format().unwrap(), PropertyFormat::String); |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| |
| property.set("test-set"); |
| assert_eq!(property_block.property_total_length().unwrap(), 8); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn bytes_property() { |
| // Create and use a default value. |
| let default = BytesProperty::default(); |
| default.set(&[0u8, 3u8]); |
| |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| let root = Node::new_root(state); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let property = node.create_bytes("property", b"test"); |
| let property_block = property.get_block().unwrap(); |
| assert_eq!(property_block.block_type(), BlockType::BufferValue); |
| assert_eq!(property_block.property_total_length().unwrap(), 4); |
| assert_eq!(property_block.property_format().unwrap(), PropertyFormat::Bytes); |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| |
| property.set(b"test-set"); |
| assert_eq!(property_block.property_total_length().unwrap(), 8); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn test_array() { |
| // Create and use a default value. |
| let default = DoubleArrayProperty::default(); |
| default.add(1, 1.0); |
| let default = IntArrayProperty::default(); |
| default.add(1, 1); |
| let default = UintArrayProperty::default(); |
| default.add(1, 1); |
| |
| let inspector = Inspector::new(); |
| let root = inspector.root(); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let array = node.create_double_array("array_property", 5); |
| let array_block = array.get_block().unwrap(); |
| |
| array.set(0, 5.0); |
| assert_eq!(array_block.array_get_double_slot(0).unwrap(), 5.0); |
| |
| array.add(0, 5.3); |
| assert_eq!(array_block.array_get_double_slot(0).unwrap(), 10.3); |
| |
| array.subtract(0, 3.4); |
| assert_eq!(array_block.array_get_double_slot(0).unwrap(), 6.9); |
| |
| array.set(1, 2.5); |
| array.set(3, -3.1); |
| |
| for (i, value) in [6.9, 2.5, 0.0, -3.1, 0.0].iter().enumerate() { |
| assert_eq!(array_block.array_get_double_slot(i).unwrap(), *value); |
| } |
| |
| array.clear(); |
| for i in 0..5 { |
| assert_eq!(0.0, array_block.array_get_double_slot(i).unwrap()); |
| } |
| |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn linear_histograms() { |
| let inspector = Inspector::new(); |
| let root = inspector.root(); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let int_histogram = node.create_int_linear_histogram( |
| "int-histogram", |
| LinearHistogramParams { floor: 10, step_size: 5, buckets: 5 }, |
| ); |
| int_histogram.insert_multiple(-1, 2); // underflow |
| int_histogram.insert(25); |
| int_histogram.insert(500); // overflow |
| let block = int_histogram.get_block().unwrap(); |
| for (i, value) in [10, 5, 2, 0, 0, 0, 1, 0, 1].iter().enumerate() { |
| assert_eq!(block.array_get_int_slot(i).unwrap(), *value); |
| } |
| |
| let uint_histogram = node.create_uint_linear_histogram( |
| "uint-histogram", |
| LinearHistogramParams { floor: 10, step_size: 5, buckets: 5 }, |
| ); |
| uint_histogram.insert_multiple(0, 2); // underflow |
| uint_histogram.insert(25); |
| uint_histogram.insert(500); // overflow |
| let block = uint_histogram.get_block().unwrap(); |
| for (i, value) in [10, 5, 2, 0, 0, 0, 1, 0, 1].iter().enumerate() { |
| assert_eq!(block.array_get_uint_slot(i).unwrap(), *value); |
| } |
| |
| uint_histogram.clear(); |
| for (i, value) in [10, 5, 0, 0, 0, 0, 0, 0, 0].iter().enumerate() { |
| assert_eq!(*value, block.array_get_uint_slot(i).unwrap()); |
| } |
| |
| let double_histogram = node.create_double_linear_histogram( |
| "double-histogram", |
| LinearHistogramParams { floor: 10.0, step_size: 5.0, buckets: 5 }, |
| ); |
| double_histogram.insert_multiple(0.0, 2); // underflow |
| double_histogram.insert(25.3); |
| double_histogram.insert(500.0); // overflow |
| let block = double_histogram.get_block().unwrap(); |
| for (i, value) in [10.0, 5.0, 2.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0].iter().enumerate() { |
| assert_eq!(block.array_get_double_slot(i).unwrap(), *value); |
| } |
| |
| assert_eq!(node_block.child_count().unwrap(), 3); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn exponential_histograms() { |
| let inspector = Inspector::new(); |
| let root = inspector.root(); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let int_histogram = node.create_int_exponential_histogram( |
| "int-histogram", |
| ExponentialHistogramParams { |
| floor: 1, |
| initial_step: 1, |
| step_multiplier: 2, |
| buckets: 4, |
| }, |
| ); |
| int_histogram.insert_multiple(-1, 2); // underflow |
| int_histogram.insert(8); |
| int_histogram.insert(500); // overflow |
| let block = int_histogram.get_block().unwrap(); |
| for (i, value) in [1, 1, 2, 2, 0, 0, 0, 1, 1].iter().enumerate() { |
| assert_eq!(block.array_get_int_slot(i).unwrap(), *value); |
| } |
| |
| let uint_histogram = node.create_uint_exponential_histogram( |
| "uint-histogram", |
| ExponentialHistogramParams { |
| floor: 1, |
| initial_step: 1, |
| step_multiplier: 2, |
| buckets: 4, |
| }, |
| ); |
| uint_histogram.insert_multiple(0, 2); // underflow |
| uint_histogram.insert(8); |
| uint_histogram.insert(500); // overflow |
| let block = uint_histogram.get_block().unwrap(); |
| for (i, value) in [1, 1, 2, 2, 0, 0, 0, 1, 1].iter().enumerate() { |
| assert_eq!(block.array_get_uint_slot(i).unwrap(), *value); |
| } |
| |
| uint_histogram.clear(); |
| for (i, value) in [1, 1, 2, 0, 0, 0, 0, 0, 0].iter().enumerate() { |
| assert_eq!(*value, block.array_get_uint_slot(i).unwrap()); |
| } |
| |
| let double_histogram = node.create_double_exponential_histogram( |
| "double-histogram", |
| ExponentialHistogramParams { |
| floor: 1.0, |
| initial_step: 1.0, |
| step_multiplier: 2.0, |
| buckets: 4, |
| }, |
| ); |
| double_histogram.insert_multiple(0.0, 2); // underflow |
| double_histogram.insert(8.3); |
| double_histogram.insert(500.0); // overflow |
| let block = double_histogram.get_block().unwrap(); |
| for (i, value) in [1.0, 1.0, 2.0, 2.0, 0.0, 0.0, 0.0, 1.0, 1.0].iter().enumerate() { |
| assert_eq!(block.array_get_double_slot(i).unwrap(), *value); |
| } |
| |
| assert_eq!(node_block.child_count().unwrap(), 3); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn owned_method_argument_properties() { |
| let mapping = Arc::new(Mapping::allocate(4096).unwrap().0); |
| let state = get_state(mapping.clone()); |
| let root = Node::new_root(state); |
| let node = root.create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let _string_property = |
| node.create_string(String::from("string_property"), String::from("test")); |
| let _bytes_property = |
| node.create_bytes(String::from("bytes_property"), vec![0, 1, 2, 3]); |
| let _double_property = node.create_double(String::from("double_property"), 1.0); |
| let _int_property = node.create_int(String::from("int_property"), 1); |
| let _uint_property = node.create_uint(String::from("uint_property"), 1); |
| assert_eq!(node_block.child_count().unwrap(), 5); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[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") |
| ); |
| } |
| |
| fn get_state(mapping: Arc<Mapping>) -> State { |
| let heap = Heap::new(mapping).unwrap(); |
| State::create(heap).expect("create state") |
| } |
| |
| #[test] |
| fn exp_histogram_insert() { |
| let inspector = Inspector::new(); |
| let root = inspector.root(); |
| let hist = root.create_int_exponential_histogram( |
| "test", |
| ExponentialHistogramParams { |
| floor: 0, |
| initial_step: 2, |
| step_multiplier: 4, |
| buckets: 4, |
| }, |
| ); |
| for i in -200..200 { |
| hist.insert(i); |
| } |
| let block = hist.get_block().unwrap(); |
| assert_eq!(block.array_get_int_slot(0).unwrap(), 0); |
| assert_eq!(block.array_get_int_slot(1).unwrap(), 2); |
| assert_eq!(block.array_get_int_slot(2).unwrap(), 4); |
| |
| // Buckets |
| let i = 3; |
| assert_eq!(block.array_get_int_slot(i).unwrap(), 200); |
| assert_eq!(block.array_get_int_slot(i + 1).unwrap(), 2); |
| assert_eq!(block.array_get_int_slot(i + 2).unwrap(), 6); |
| assert_eq!(block.array_get_int_slot(i + 3).unwrap(), 24); |
| assert_eq!(block.array_get_int_slot(i + 4).unwrap(), 96); |
| assert_eq!(block.array_get_int_slot(i + 5).unwrap(), 72); |
| } |
| |
| #[test] |
| fn lazy_values() { |
| let inspector = Inspector::new(); |
| let node = inspector.root().create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let lazy_node = |
| node.create_lazy_values("lazy", || async move { Ok(Inspector::new()) }.boxed()); |
| let lazy_node_block = lazy_node.get_block().unwrap(); |
| assert_eq!(lazy_node_block.block_type(), BlockType::LinkValue); |
| assert_eq!( |
| lazy_node_block.link_node_disposition().unwrap(), |
| LinkNodeDisposition::Inline |
| ); |
| assert_eq!(lazy_node_block.link_content_index().unwrap(), 5); |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[test] |
| fn lazy_node() { |
| let inspector = Inspector::new(); |
| let node = inspector.root().create_child("node"); |
| let node_block = node.get_block().unwrap(); |
| { |
| let lazy_node = |
| node.create_lazy_child("lazy", || async move { Ok(Inspector::new()) }.boxed()); |
| let lazy_node_block = lazy_node.get_block().unwrap(); |
| assert_eq!(lazy_node_block.block_type(), BlockType::LinkValue); |
| assert_eq!( |
| lazy_node_block.link_node_disposition().unwrap(), |
| LinkNodeDisposition::Child |
| ); |
| assert_eq!(lazy_node_block.link_content_index().unwrap(), 5); |
| assert_eq!(node_block.child_count().unwrap(), 1); |
| } |
| assert_eq!(node_block.child_count().unwrap(), 0); |
| } |
| |
| #[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_inspect_tree!(inspector, root: { |
| test: 3u64, |
| lazy: { |
| test2: 4u64, |
| } |
| }); |
| } |
| |
| #[test] |
| fn value_list_record() { |
| let inspector = Inspector::new(); |
| let child = inspector.root().create_child("test"); |
| let value_list = ValueList::new(); |
| assert!(value_list.values.lock().is_none()); |
| value_list.record(child); |
| assert_eq!(value_list.values.lock().as_ref().unwrap().len(), 1); |
| } |
| |
| #[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.14); |
| assert_inspect_tree!(inspector, root: { |
| a: 1u64, |
| b: 2u64, |
| child: { |
| c: 3.14, |
| } |
| }); |
| } |
| // `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_inspect_tree!(inspector, root: { |
| b: 2u64, |
| }); |
| } |
| |
| #[test] |
| fn record_child() { |
| let inspector = Inspector::new(); |
| inspector.root().record_child("test", |node| { |
| node.record_int("a", 1); |
| }); |
| assert_inspect_tree!(inspector, root: { |
| test: { |
| a: 1i64, |
| } |
| }) |
| } |
| |
| #[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.14); |
| assert_inspect_tree!(inspector, root: { main: { |
| a: 1u64, |
| b: 2u64, |
| c: 3u64, |
| child: { |
| c: 3.14, |
| } |
| }}); |
| } |
| // `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_inspect_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_inspect_tree!(inspector, root: { }); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn connect_to_service() -> Result<(), anyhow::Error> { |
| let mut service_fs = ServiceFs::new(); |
| let env = service_fs.create_nested_environment("test")?; |
| let mut app = client::launch(&env.launcher(), TEST_COMPONENT_URL.to_string(), None)?; |
| |
| fasync::Task::spawn(service_fs.collect()).detach(); |
| |
| let mut component_stream = app.controller().take_event_stream(); |
| match component_stream |
| .next() |
| .await |
| .expect("component event stream ended before termination event")? |
| { |
| ComponentControllerEvent::OnTerminated { return_code, termination_reason } => { |
| bail!( |
| "Component terminated unexpectedly. Code: {}. Reason: {:?}", |
| return_code, |
| termination_reason |
| ); |
| } |
| ComponentControllerEvent::OnDirectoryReady {} => { |
| let pattern = format!( |
| "/hub/r/test/*/c/{}/*/out/diagnostics/{}", |
| TEST_COMPONENT_CMX, |
| TreeMarker::SERVICE_NAME |
| ); |
| let path = glob(&pattern)?.next().unwrap().expect("failed to parse glob"); |
| let (tree, server_end) = |
| fidl::endpoints::create_proxy::<TreeMarker>().expect("failed to create proxy"); |
| fdio::service_connect( |
| &path.to_string_lossy().to_string(), |
| server_end.into_channel(), |
| ) |
| .expect("failed to connect to service"); |
| |
| let hierarchy = reader::read(&tree).await?; |
| assert_inspect_tree!(hierarchy, root: { |
| int: 3i64, |
| "lazy-node": { |
| a: "test", |
| child: { |
| double: 3.14, |
| }, |
| } |
| }); |
| app.kill().map_err(|e| format_err!("failed to kill component: {}", e)) |
| } |
| } |
| } |
| |
| #[test] |
| fn unique_name() { |
| let inspector = Inspector::new(); |
| |
| let name_1 = super::unique_name("a"); |
| assert_eq!(name_1, "a0"); |
| inspector.root().record_uint(name_1, 1); |
| |
| let name_2 = super::unique_name("a"); |
| assert_eq!(name_2, "a1"); |
| inspector.root().record_uint(name_2, 1); |
| |
| assert_inspect_tree!(inspector, root: { |
| a0: 1u64, |
| a1: 1u64, |
| }); |
| } |
| } |