blob: 6eb85f8d48b6794f3a480fd4ffd3e8e6686714be [file] [log] [blame]
// 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()))
}
}