| // Copyright 2019 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. |
| |
| #![feature(async_await)] |
| |
| use { |
| crate::{ |
| format::{ |
| block::{ArrayFormat, PropertyFormat}, |
| constants, |
| }, |
| heap::Heap, |
| state::State, |
| }, |
| failure::{format_err, Error}, |
| fuchsia_component::server::{ServiceFs, ServiceObjTrait}, |
| fuchsia_syslog::macros::*, |
| fuchsia_zircon::{self as zx, HandleBased}, |
| mapped_vmo::Mapping, |
| parking_lot::Mutex, |
| paste, |
| std::{cmp::max, sync::Arc}, |
| }; |
| |
| #[cfg(test)] |
| use crate::format::block::Block; |
| |
| pub mod component; |
| pub mod format; |
| pub mod health; |
| mod heap; |
| pub mod reader; |
| #[macro_use] |
| pub mod testing; |
| mod state; |
| mod utils; |
| |
| /// Root of the Inspect API |
| pub struct Inspector { |
| /// The root node. |
| root_node: Node, |
| |
| /// The VMO backing the inspector |
| pub(in crate) vmo: Option<zx::Vmo>, |
| } |
| |
| /// Root API for inspect. Used to create the VMO, export to ServiceFs, and get |
| /// the root node. |
| impl Inspector { |
| /// Create a new Inspect VMO object with the default maximum size. |
| pub fn new() -> Self { |
| Inspector::new_with_size(constants::DEFAULT_VMO_SIZE_BYTES) |
| } |
| |
| /// 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() |
| } |
| |
| /// Create 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(vmo), root_node }, |
| Err(e) => { |
| fx_log_err!("Failed to create root node. Error: {}", e); |
| Inspector::new_no_op() |
| } |
| } |
| } |
| |
| /// To be used by tests only. Tries to return a ZX handle suitable for passing through FIDL. |
| pub fn vmo_handle_for_test(&self) -> Option<zx::Handle> { |
| self.vmo |
| .as_ref() |
| .map(|bar| { |
| bar.duplicate_handle(zx::Rights::BASIC | zx::Rights::READ | zx::Rights::MAP) |
| .ok() |
| .map(|v| v.into_handle()) |
| }) |
| .unwrap_or(None) |
| } |
| |
| /// Exports the VMO backing this Inspector at the standard location in the |
| /// supplied ServiceFs. |
| pub fn export<ServiceObjTy: ServiceObjTrait>(&self, service_fs: &mut ServiceFs<ServiceObjTy>) { |
| self.vmo |
| .as_ref() |
| .ok_or(format_err!("Cannot expose No-Op Inspector")) |
| .and_then(|vmo| { |
| service_fs.dir("objects").add_vmo_file_at( |
| "root.inspect", |
| vmo.duplicate_handle(zx::Rights::BASIC | zx::Rights::READ | zx::Rights::MAP)?, |
| 0, /* vmo offset */ |
| vmo.get_size()?, |
| ); |
| Ok(()) |
| }) |
| .unwrap_or_else(|e| { |
| fx_log_err!("Failed to expose vmo. Error: {:?}", e); |
| }); |
| } |
| |
| /// Get the root of the VMO object. |
| pub fn root(&self) -> &Node { |
| &self.root_node |
| } |
| |
| /// Creates a new No-Op inspector |
| fn new_no_op() -> Self { |
| Inspector { vmo: None, root_node: 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(size) |
| .map_err(|e| format_err!("failed to allocate vmo zx status={}", e))?; |
| let heap = Heap::new(Arc::new(mapping))?; |
| let state = State::create(heap)?; |
| Ok((vmo, Node::new_root(Arc::new(Mutex::new(state))))) |
| } |
| } |
| |
| /// Trait implemented by all inspect types. It provides constructor functions that are not |
| /// intended for use outside the crate. |
| trait InspectType { |
| type Inner; |
| |
| fn new(state: Arc<Mutex<State>>, block_index: u32) -> Self; |
| fn new_no_op() -> Self; |
| fn is_valid(&self) -> bool; |
| fn unwrap_ref(&self) -> &Self::Inner; |
| } |
| |
| /// Utility for generating the implementation of all inspect types: |
| /// - 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) => { |
| paste::item! { |
| $(#[$attr])* |
| /// NOTE: do not rely on PartialEq implementation for true comparison. |
| /// Instead leverage the reader. |
| #[derive(Debug, PartialEq, Eq)] |
| pub struct $name { |
| inner: Option<[<Inner $name>]>, |
| } |
| |
| #[cfg(test)] |
| impl $name { |
| pub fn get_block(&self) -> Option<Block<Arc<Mapping>>> { |
| self.inner.as_ref().and_then(|inner| { |
| inner.state.lock().heap.get_block(inner.block_index).ok() |
| }) |
| } |
| |
| pub fn block_index(&self) -> u32 { |
| self.inner.as_ref().unwrap().block_index |
| } |
| } |
| |
| impl InspectType for $name { |
| type Inner = [<Inner $name>]; |
| |
| fn new(state: Arc<Mutex<State>>, block_index: u32) -> Self { |
| Self { |
| inner: Some([<Inner $name>] { |
| state, block_index |
| }) |
| } |
| } |
| |
| fn is_valid(&self) -> bool { |
| self.inner.is_some() |
| } |
| |
| fn new_no_op() -> Self { |
| Self { inner: None } |
| } |
| |
| fn unwrap_ref(&self) -> &Self::Inner { |
| self.inner.as_ref().unwrap() |
| } |
| } |
| |
| #[derive(Debug)] |
| struct [<Inner $name>] { |
| /// Index of the block in the VMO. |
| block_index: u32, |
| |
| /// Reference to the VMO heap. |
| state: Arc<Mutex<State>>, |
| } |
| |
| /// 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 PartialEq for [<Inner $name>] { |
| fn eq(&self, _other: &[<Inner $name>]) -> bool { |
| true |
| } |
| } |
| |
| impl Eq for [<Inner $name>] {} |
| } |
| } |
| } |
| |
| /// 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::item! { |
| pub fn [<create_ $name >](&self, name: impl AsRef<str>, value: $type) |
| -> [<$name_cap Property>] { |
| self.inner.as_ref().and_then(|inner| { |
| inner.state |
| .lock() |
| .[<create_ $name _metric>](name.as_ref(), value, inner.block_index) |
| .map(|block| { |
| [<$name_cap Property>]::new(inner.state.clone(), block.index()) |
| }) |
| .ok() |
| }) |
| .unwrap_or([<$name_cap Property>]::new_no_op()) |
| } |
| } |
| }; |
| } |
| |
| /// 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::item! { |
| pub fn [<create_ $name _array>](&self, name: impl AsRef<str>, slots: u8) |
| -> [<$name_cap ArrayProperty>] { |
| self.[<create_ $name _array_internal>](name, slots, ArrayFormat::Default) |
| } |
| |
| fn [<create_ $name _array_internal>](&self, name: impl AsRef<str>, slots: u8, format: ArrayFormat) |
| -> [<$name_cap ArrayProperty>] { |
| self.inner.as_ref().and_then(|inner| { |
| inner.state |
| .lock() |
| .[<create_ $name _array>](name.as_ref(), slots, format, inner.block_index) |
| .map(|block| { |
| [<$name_cap ArrayProperty>]::new(inner.state.clone(), block.index()) |
| }) |
| .ok() |
| }) |
| .unwrap_or([<$name_cap ArrayProperty>]::new_no_op()) |
| } |
| } |
| }; |
| } |
| |
| pub struct LinearHistogramParams<T> { |
| pub floor: T, |
| pub step_size: T, |
| pub buckets: u8, |
| } |
| |
| /// 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::item! { |
| 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 |
| } |
| } |
| } |
| }; |
| } |
| |
| pub struct ExponentialHistogramParams<T> { |
| pub floor: T, |
| pub initial_step: T, |
| pub step_multiplier: T, |
| pub buckets: u8, |
| } |
| |
| /// 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::item! { |
| 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 |
| } |
| } |
| } |
| }; |
| } |
| |
| inspect_type_impl!( |
| /// Inspect API Node data type. |
| struct Node |
| ); |
| |
| impl Node { |
| pub(in crate) fn new_root(state: Arc<Mutex<State>>) -> Node { |
| Node::new(state, 0) |
| } |
| |
| /// Add a child to this node. |
| pub fn create_child(&self, name: impl AsRef<str>) -> Node { |
| self.inner |
| .as_ref() |
| .and_then(|inner| { |
| inner |
| .state |
| .lock() |
| .create_node(name.as_ref(), inner.block_index) |
| .map(|block| Node::new(inner.state.clone(), block.index())) |
| .ok() |
| }) |
| .unwrap_or(Node::new_no_op()) |
| } |
| |
| /// 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); |
| |
| /// Add a string property to this node. |
| pub fn create_string(&self, name: impl AsRef<str>, value: impl AsRef<str>) -> StringProperty { |
| self.inner |
| .as_ref() |
| .and_then(|inner| { |
| inner |
| .state |
| .lock() |
| .create_property( |
| name.as_ref(), |
| value.as_ref().as_bytes(), |
| PropertyFormat::String, |
| inner.block_index, |
| ) |
| .map(|block| StringProperty::new(inner.state.clone(), block.index())) |
| .ok() |
| }) |
| .unwrap_or(StringProperty::new_no_op()) |
| } |
| |
| /// Add a byte vector property to this node. |
| pub fn create_bytes(&self, name: impl AsRef<str>, value: impl AsRef<[u8]>) -> BytesProperty { |
| self.inner |
| .as_ref() |
| .and_then(|inner| { |
| inner |
| .state |
| .lock() |
| .create_property( |
| name.as_ref(), |
| value.as_ref(), |
| PropertyFormat::Bytes, |
| inner.block_index, |
| ) |
| .map(|block| BytesProperty::new(inner.state.clone(), block.index())) |
| .ok() |
| }) |
| .unwrap_or(BytesProperty::new_no_op()) |
| } |
| } |
| |
| impl Drop for Node { |
| fn drop(&mut self) { |
| self.inner.as_ref().map(|inner| { |
| // This is the special "root" node. Do not delete. |
| if inner.block_index == 0 { |
| return; |
| } |
| inner |
| .state |
| .lock() |
| .free_value(inner.block_index) |
| .expect(&format!("Failed to free node index={}", inner.block_index)); |
| }); |
| } |
| } |
| |
| /// Trait implemented by properties. |
| pub trait Property<'t> { |
| type Type; |
| |
| /// Set the property value to |value|. |
| fn set(&'t self, value: Self::Type); |
| } |
| |
| /// Trait implemented by numeric properties. |
| 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::item! { |
| fn $fn_name(&self, value: $type) { |
| if let Some(ref inner) = self.inner { |
| inner.state.lock().[<$fn_name _ $name _metric>](inner.block_index, value) |
| .unwrap_or_else(|e| { |
| fx_log_err!("Failed to {} property. Error: {:?}", stringify!($fn_name), e); |
| }); |
| } |
| } |
| } |
| }; |
| } |
| |
| /// Utility for generting a Drop implementation for numeric and array properties. |
| /// `name`: the name of the struct of the property for which Drop will be implemented. |
| macro_rules! drop_value_impl { |
| ($name:ident) => { |
| impl Drop for $name { |
| fn drop(&mut self) { |
| self.inner.as_ref().map(|inner| { |
| inner |
| .state |
| .lock() |
| .free_value(inner.block_index) |
| .expect(&format!("Failed to free property index={}", inner.block_index)); |
| }); |
| } |
| } |
| }; |
| } |
| |
| /// 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::item! { |
| inspect_type_impl!( |
| /// Inspect API Numeric Property data type. |
| struct [<$name_cap Property>] |
| ); |
| |
| 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) = self.inner { |
| inner.state.lock().[<get_ $name _metric>](inner.block_index) |
| } else { |
| Err(format_err!("Property is No-Op")) |
| } |
| } |
| } |
| |
| drop_value_impl!([<$name_cap 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 expression to get the bytes of the property |
| macro_rules! property { |
| ($name:ident, $type:expr, $bytes:expr) => { |
| paste::item! { |
| inspect_type_impl!( |
| /// Inspect API Property data type. |
| struct [<$name Property>] |
| ); |
| |
| impl<'t> Property<'t> for [<$name Property>] { |
| type Type = &'t $type; |
| |
| fn set(&'t self, value: &'t $type) { |
| if let Some(ref inner) = self.inner { |
| inner.state.lock().set_property(inner.block_index, $bytes) |
| .unwrap_or_else(|e| fx_log_err!("Failed to set property. Error: {:?}", e)); |
| } |
| } |
| |
| } |
| |
| impl Drop for [<$name Property>] { |
| fn drop(&mut self) { |
| self.inner.as_ref().map(|inner| { |
| inner.state |
| .lock() |
| .free_property(inner.block_index) |
| .expect(&format!("Failed to free property index={}", inner.block_index)); |
| }); |
| } |
| } |
| } |
| }; |
| } |
| |
| property!(String, str, value.as_bytes()); |
| property!(Bytes, [u8], value); |
| |
| pub trait ArrayProperty { |
| type Type; |
| |
| /// Set the array value to |value| at the given |index|. |
| fn set(&self, index: usize, value: Self::Type); |
| |
| /// Add the given |value| to the property current value at the given |index|. |
| fn add(&self, index: usize, value: Self::Type); |
| |
| /// Subtract the given |value| to the property current value at the given |index|. |
| fn subtract(&self, index: usize, value: Self::Type); |
| } |
| |
| /// 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::item! { |
| fn $fn_name(&self, index: usize, value: $type) { |
| if let Some(ref inner) = self.inner { |
| inner.state.lock().[<$fn_name _array_ $name _slot>](inner.block_index, index, value) |
| .unwrap_or_else(|e| { |
| fx_log_err!("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::item! { |
| inspect_type_impl!( |
| /// Inspect API Array Property data type. |
| struct [<$name_cap ArrayProperty>] |
| ); |
| |
| 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); |
| } |
| |
| drop_value_impl!([<$name_cap ArrayProperty>]); |
| } |
| }; |
| } |
| |
| array_property!(int, Int, i64); |
| array_property!(uint, Uint, u64); |
| array_property!(double, Double, f64); |
| |
| pub trait HistogramProperty { |
| type Type; |
| |
| fn insert(&self, value: Self::Type); |
| fn insert_multiple(&self, value: Self::Type, count: usize); |
| } |
| |
| macro_rules! histogram_property { |
| ($histogram_type:ident, $name_cap:ident, $type:ident) => { |
| paste::item! { |
| 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); |
| } |
| } |
| } |
| }; |
| } |
| |
| macro_rules! linear_histogram_property { |
| ($name_cap:ident, $type:ident) => { |
| paste::item! { |
| pub struct [<$name_cap LinearHistogramProperty>] { |
| array: [<$name_cap ArrayProperty>], |
| floor: $type, |
| slots: u8, |
| 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); |
| } |
| }; |
| } |
| |
| macro_rules! exponential_histogram_property { |
| ($name_cap:ident, $type:ident) => { |
| paste::item! { |
| pub struct [<$name_cap ExponentialHistogramProperty>] { |
| array: [<$name_cap ArrayProperty>], |
| floor: $type, |
| initial_step: $type, |
| step_multiplier: $type, |
| slots: u8, |
| } |
| |
| impl [<$name_cap ExponentialHistogramProperty>] { |
| fn get_index(&self, value: $type) -> usize { |
| let mut current_floor = self.floor; |
| let mut current_step = 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 += current_step; |
| current_step *= 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); |
| } |
| }; |
| } |
| |
| 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); |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| format::{block_type::BlockType, constants}, |
| heap::Heap, |
| }, |
| fuchsia_async::{self as fasync, futures::StreamExt}, |
| mapped_vmo::Mapping, |
| }; |
| |
| #[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 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::new(); |
| |
| let inspector = Inspector::new_no_op(); |
| assert!(!inspector.is_valid()); |
| assert!(!inspector.root().is_valid()); |
| |
| // Ensure export doesn't crash on a No-Op inspector |
| inspector.export(&mut fs); |
| |
| fs.take_and_serve_directory_handle()?; |
| fasync::spawn(fs.collect()); |
| Ok(()) |
| } |
| |
| #[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); |
| |
| // 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.as_ref().unwrap(); |
| assert_eq!(inner.block_index, 0); |
| } |
| |
| #[test] |
| fn node() { |
| 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 double_property() { |
| 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() { |
| 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() { |
| 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 string_property() { |
| 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::PropertyValue); |
| 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() { |
| 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::PropertyValue); |
| 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() { |
| 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].into_iter().enumerate() { |
| assert_eq!(array_block.array_get_double_slot(i).unwrap(), *value); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| 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() -> Result<(), Error> { |
| 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") |
| ); |
| |
| Ok(()) |
| } |
| |
| fn get_state(mapping: Arc<Mapping>) -> Arc<Mutex<State>> { |
| let heap = Heap::new(mapping).unwrap(); |
| Arc::new(Mutex::new(State::create(heap).unwrap())) |
| } |
| } |