blob: 43de03a1b560e3c1396418902624b2676335f231 [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.
use {
crate::{
error::Error,
format::{
block::{ArrayFormat, Block, LinkNodeDisposition, PropertyFormat},
block_type::BlockType,
constants,
},
heap::Heap,
utils, Inspector,
},
anyhow,
derivative::Derivative,
futures::future::BoxFuture,
mapped_vmo::Mapping,
num_traits::ToPrimitive,
parking_lot::{Mutex, MutexGuard},
std::{
collections::HashMap,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
},
tracing::error,
};
/// Callback used to fill inspector lazy nodes.
pub type LazyNodeContextFnArc =
Arc<dyn Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send>;
trait SafeOp {
fn safe_sub(&self, other: Self) -> Self;
fn safe_add(&self, other: Self) -> Self;
}
impl SafeOp for u64 {
fn safe_sub(&self, other: u64) -> u64 {
self.checked_sub(other).unwrap_or(0)
}
fn safe_add(&self, other: u64) -> u64 {
self.checked_add(other).unwrap_or(std::u64::MAX)
}
}
impl SafeOp for i64 {
fn safe_sub(&self, other: i64) -> i64 {
self.checked_sub(other).unwrap_or(std::i64::MIN)
}
fn safe_add(&self, other: i64) -> i64 {
self.checked_add(other).unwrap_or(std::i64::MAX)
}
}
impl SafeOp for f64 {
fn safe_sub(&self, other: f64) -> f64 {
self - other
}
fn safe_add(&self, other: f64) -> f64 {
self + other
}
}
macro_rules! locked_state_metric_fns {
($name:ident, $type:ident) => {
paste::paste! {
pub fn [<create_ $name _metric>](
&mut self,
name: &str,
value: $type,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
self.inner_lock.[<create_ $name _metric>](name, value, parent_index)
}
pub fn [<set_ $name _metric>](&self, block_index: u32, value: $type)
-> Result<(), Error> {
self.inner_lock.[<set_ $name _metric>](block_index, value)
}
pub fn [<add_ $name _metric>](&self, block_index: u32, value: $type)
-> Result<(), Error> {
self.inner_lock.[<add_ $name _metric>](block_index, value)
}
pub fn [<subtract_ $name _metric>](&self, block_index: u32, value: $type)
-> Result<(), Error> {
self.inner_lock.[<subtract_ $name _metric>](block_index, value)
}
pub fn [<get_ $name _metric>](&self, block_index: u32) -> Result<$type, Error> {
self.inner_lock.[<get_ $name _metric>](block_index)
}
}
};
}
/// Generate create, set, add and subtract methods for a metric.
macro_rules! metric_fns {
($name:ident, $type:ident) => {
paste::paste! {
fn [<create_ $name _metric>](
&mut self,
name: &str,
value: $type,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
let (block, name_block) = self.allocate_reserved_value(
name, parent_index, constants::MIN_ORDER_SIZE)?;
block.[<become_ $name _value>](value, name_block.index(), parent_index)?;
Ok(block)
}
fn [<set_ $name _metric>](&self, block_index: u32, value: $type)
-> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
block.[<set_ $name _value>](value)?;
Ok(())
}
fn [<add_ $name _metric>](&self, block_index: u32, value: $type)
-> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
let current_value = block.[<$name _value>]()?;
block.[<set_ $name _value>](current_value.safe_add(value))?;
Ok(())
}
fn [<subtract_ $name _metric>](&self, block_index: u32, value: $type)
-> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
let current_value = block.[<$name _value>]()?;
let new_value = current_value.safe_sub(value);
block.[<set_ $name _value>](new_value)?;
Ok(())
}
fn [<get_ $name _metric>](&self, block_index: u32) -> Result<$type, Error> {
let block = self.heap.get_block(block_index)?;
let current_value = block.[<$name _value>]()?;
Ok(current_value)
}
}
};
}
macro_rules! locked_state_array_fns {
($name:ident, $type:ident, $value:ident) => {
paste::paste! {
pub fn [<create_ $name _array>](
&mut self,
name: &str,
slots: usize,
array_format: ArrayFormat,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
self.inner_lock.[<create_ $name _array>](name, slots, array_format, parent_index)
}
pub fn [<set_array_ $name _slot>](
&mut self, block_index: u32, slot_index: usize, value: $type
) -> Result<(), Error> {
self.inner_lock.[<set_array_ $name _slot>](block_index, slot_index, value)
}
pub fn [<add_array_ $name _slot>](
&mut self, block_index: u32, slot_index: usize, value: $type
) -> Result<(), Error> {
self.inner_lock.[<add_array_ $name _slot>](block_index, slot_index, value)
}
pub fn [<subtract_array_ $name _slot>](
&mut self, block_index: u32, slot_index: usize, value: $type
) -> Result<(), Error> {
self.inner_lock.[<subtract_array_ $name _slot>](block_index, slot_index, value)
}
}
};
}
macro_rules! array_fns {
($name:ident, $type:ident, $value:ident) => {
paste::paste! {
pub fn [<create_ $name _array>](
&mut self,
name: &str,
slots: usize,
array_format: ArrayFormat,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
let block_size =
slots as usize * std::mem::size_of::<$type>() + constants::MIN_ORDER_SIZE;
if block_size > constants::MAX_ORDER_SIZE {
return Err(Error::BlockSizeTooBig(block_size))
}
let (block, name_block) = self.allocate_reserved_value(
name, parent_index, block_size)?;
block.become_array_value(
slots, array_format, BlockType::$value, name_block.index(), parent_index)?;
Ok(block)
}
pub fn [<set_array_ $name _slot>](
&mut self, block_index: u32, slot_index: usize, value: $type
) -> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
block.[<array_set_ $name _slot>](slot_index, value)?;
Ok(())
}
pub fn [<add_array_ $name _slot>](
&mut self, block_index: u32, slot_index: usize, value: $type
) -> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
let previous_value = block.[<array_get_ $name _slot>](slot_index)?;
let new_value = previous_value.safe_add(value);
block.[<array_set_ $name _slot>](slot_index, new_value)?;
Ok(())
}
pub fn [<subtract_array_ $name _slot>](
&mut self, block_index: u32, slot_index: usize, value: $type
) -> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
let previous_value = block.[<array_get_ $name _slot>](slot_index)?;
let new_value = previous_value.safe_sub(value);
block.[<array_set_ $name _slot>](slot_index, new_value)?;
Ok(())
}
}
};
}
/// In charge of performing all operations on the VMO as well as managing the lock and unlock
/// behavior.
#[derive(Clone, Debug)]
pub struct State {
/// A reference to the header block in the VMO.
header: Block<Arc<Mapping>>,
/// The inner state that actually performs the operations.
/// This should always be accessed by locking the mutex and then locking the header.
// TODO(fxbug.dev/51298): have a single locking mechanism implemented on top of the vmo header.
inner: Arc<Mutex<InnerState>>,
}
impl State {
/// Create a |State| object wrapping the given Heap. This will cause the
/// heap to be initialized with a header.
pub fn create(mut heap: Heap) -> Result<Self, Error> {
let mut block = heap.allocate_block(16)?;
block.become_header()?;
let inner = Arc::new(Mutex::new(InnerState::new(heap)));
Ok(Self { inner, header: block })
}
/// Locks the state mutex and inspect vmo. The state will be unlocked on drop.
/// This can fail when the header is already locked.
pub fn try_lock<'a>(&'a self) -> Result<LockedStateGuard<'a>, Error> {
let inner_lock = self.inner.lock();
LockedStateGuard::new(&self.header, inner_lock)
}
/// Copies the bytes in the VMO into the returned vector.
pub fn copy_vmo_bytes(&self) -> Vec<u8> {
let state = self.inner.lock();
state.heap.bytes()
}
}
/// Statistics about the current inspect state.
#[derive(Debug, Eq, PartialEq)]
pub struct Stats {
/// Number of lazy links (lazy children and values) that have been added to the state.
pub total_dynamic_children: usize,
/// Maximum size of the vmo backing inspect.
pub maximum_size: usize,
/// Current size of the vmo backing inspect.
pub current_size: usize,
}
pub struct LockedStateGuard<'a> {
header: &'a Block<Arc<Mapping>>,
inner_lock: MutexGuard<'a, InnerState>,
}
impl<'a> LockedStateGuard<'a> {
fn new(
header: &'a Block<Arc<Mapping>>,
inner_lock: MutexGuard<'a, InnerState>,
) -> Result<Self, Error> {
header.lock_header()?;
Ok(Self { header, inner_lock })
}
/// Returns statistics about the current inspect state.
pub fn stats(&self) -> Stats {
Stats {
total_dynamic_children: self.inner_lock.callbacks.len(),
current_size: self.inner_lock.heap.current_size(),
maximum_size: self.inner_lock.heap.maximum_size(),
}
}
/// Returns a reference to the lazy callbacks map.
pub fn callbacks(&self) -> &HashMap<String, LazyNodeContextFnArc> {
&self.inner_lock.callbacks
}
/// Allocate a NODE block with the given |name| and |parent_index|.
pub fn create_node(
&mut self,
name: &str,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
self.inner_lock.create_node(name, parent_index)
}
/// Allocate a LINK block with the given |name| and |parent_index| and keep track
/// of the callback that will fill it.
pub fn create_lazy_node<F>(
&mut self,
name: &str,
parent_index: u32,
disposition: LinkNodeDisposition,
callback: F,
) -> Result<Block<Arc<Mapping>>, Error>
where
F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static,
{
self.inner_lock.create_lazy_node(name, parent_index, disposition, callback)
}
/// Frees a LINK block at the given |index|.
pub fn free_lazy_node(&mut self, index: u32) -> Result<(), Error> {
self.inner_lock.free_lazy_node(index)
}
/// Free a *_VALUE block at the given |index|.
pub fn free_value(&mut self, index: u32) -> Result<(), Error> {
self.inner_lock.free_value(index)
}
/// Allocate a PROPERTY block with the given |name|, |value| and |parent_index|.
pub fn create_property(
&mut self,
name: &str,
value: &[u8],
format: PropertyFormat,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
self.inner_lock.create_property(name, value, format, parent_index)
}
/// Free a PROPERTY block.
pub fn free_property(&mut self, index: u32) -> Result<(), Error> {
self.inner_lock.free_property(index)
}
/// Set the |value| of a String PROPERTY block.
pub fn set_property(&mut self, block_index: u32, value: &[u8]) -> Result<(), Error> {
self.inner_lock.set_property(block_index, value)
}
pub fn create_bool(
&mut self,
name: &str,
value: bool,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
self.inner_lock.create_bool(name, value, parent_index)
}
pub fn set_bool(&self, block_index: u32, value: bool) -> Result<(), Error> {
self.inner_lock.set_bool(block_index, value)
}
locked_state_metric_fns!(int, i64);
locked_state_metric_fns!(uint, u64);
locked_state_metric_fns!(double, f64);
locked_state_array_fns!(int, i64, IntValue);
locked_state_array_fns!(uint, u64, UintValue);
locked_state_array_fns!(double, f64, DoubleValue);
/// Sets all slots of the array at the given index to zero
pub fn clear_array(&mut self, block_index: u32, start_slot_index: usize) -> Result<(), Error> {
self.inner_lock.clear_array(block_index, start_slot_index)
}
#[cfg(test)]
pub fn allocate_link(
&mut self,
name: &str,
content: &str,
disposition: LinkNodeDisposition,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
self.inner_lock.allocate_link(name, content, disposition, parent_index)
}
#[cfg(test)]
pub fn heap(&self) -> &Heap {
&self.inner_lock.heap
}
}
impl Drop for LockedStateGuard<'_> {
fn drop(&mut self) {
self.header.unlock_header().unwrap_or_else(|e| {
error!(?e, "Failed to unlock header");
});
}
}
/// Wraps a heap and implements the Inspect VMO API on top of it at a low level.
#[derive(Derivative)]
#[derivative(Debug)]
struct InnerState {
heap: Heap,
next_unique_link_id: AtomicU64,
#[derivative(Debug = "ignore")]
callbacks: HashMap<String, LazyNodeContextFnArc>,
}
impl InnerState {
/// Creates a new inner state that performs all operations on the heap.
pub fn new(heap: Heap) -> Self {
Self { heap, next_unique_link_id: AtomicU64::new(0), callbacks: HashMap::new() }
}
/// Allocate a NODE block with the given |name| and |parent_index|.
fn create_node(&mut self, name: &str, parent_index: u32) -> Result<Block<Arc<Mapping>>, Error> {
let (block, name_block) =
self.allocate_reserved_value(name, parent_index, constants::MIN_ORDER_SIZE)?;
block.become_node(name_block.index(), parent_index)?;
Ok(block)
}
/// Allocate a LINK block with the given |name| and |parent_index| and keep track
/// of the callback that will fill it.
fn create_lazy_node<F>(
&mut self,
name: &str,
parent_index: u32,
disposition: LinkNodeDisposition,
callback: F,
) -> Result<Block<Arc<Mapping>>, Error>
where
F: Fn() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> + Sync + Send + 'static,
{
let content = self.unique_link_name(name);
let link = self.allocate_link(name, &content, disposition, parent_index)?;
self.callbacks.insert(content, Arc::from(callback));
Ok(link)
}
/// Frees a LINK block at the given |index|.
fn free_lazy_node(&mut self, index: u32) -> Result<(), Error> {
let block = self.heap.get_block(index)?;
let content_block = self.heap.get_block(block.link_content_index()?)?;
let content = self.heap.get_block(content_block.index())?.name_contents()?;
self.delete_value(block)?;
self.heap.free_block(content_block)?;
self.callbacks.remove(&content);
Ok(())
}
fn unique_link_name(&mut self, prefix: &str) -> String {
let id = self.next_unique_link_id.fetch_add(1, Ordering::Relaxed);
format!("{}-{}", prefix, id)
}
pub(in crate) fn allocate_link(
&mut self,
name: &str,
content: &str,
disposition: LinkNodeDisposition,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
let (value_block, name_block) =
self.allocate_reserved_value(name, parent_index, constants::MIN_ORDER_SIZE)?;
let result = self.allocate_name(content).and_then(|content_block| {
value_block.become_link(
name_block.index(),
parent_index,
content_block.index(),
disposition,
)
});
match result {
Ok(()) => Ok(value_block),
Err(err) => {
self.delete_value(value_block)?;
Err(err)
}
}
}
/// Free a *_VALUE block at the given |index|.
fn free_value(&mut self, index: u32) -> Result<(), Error> {
let block = self.heap.get_block(index)?;
self.delete_value(block).unwrap();
Ok(())
}
/// Allocate a PROPERTY block with the given |name|, |value| and |parent_index|.
fn create_property(
&mut self,
name: &str,
value: &[u8],
format: PropertyFormat,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
let (block, name_block) =
self.allocate_reserved_value(name, parent_index, constants::MIN_ORDER_SIZE)?;
block.become_property(name_block.index(), parent_index, format)?;
if let Err(err) = self.inner_set_property_value(&block, &value) {
self.heap.free_block(block).expect("Failed to free block");
self.heap.free_block(name_block).expect("Failed to free name block");
return Err(err);
}
Ok(block)
}
/// Free a PROPERTY block.
fn free_property(&mut self, index: u32) -> Result<(), Error> {
let block = self.heap.get_block(index)?;
self.free_extents(block.property_extent_index()?)?;
self.delete_value(block)?;
Ok(())
}
/// Set the |value| of a String PROPERTY block.
fn set_property(&mut self, block_index: u32, value: &[u8]) -> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
self.inner_set_property_value(&block, value)?;
Ok(())
}
fn create_bool(
&mut self,
name: &str,
value: bool,
parent_index: u32,
) -> Result<Block<Arc<Mapping>>, Error> {
let (block, name_block) =
self.allocate_reserved_value(name, parent_index, constants::MIN_ORDER_SIZE)?;
block.become_bool_value(value, name_block.index(), parent_index)?;
Ok(block)
}
fn set_bool(&self, block_index: u32, value: bool) -> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
block.set_bool_value(value)?;
Ok(())
}
metric_fns!(int, i64);
metric_fns!(uint, u64);
metric_fns!(double, f64);
array_fns!(int, i64, IntValue);
array_fns!(uint, u64, UintValue);
array_fns!(double, f64, DoubleValue);
/// Sets all slots of the array at the given index to zero
fn clear_array(&mut self, block_index: u32, start_slot_index: usize) -> Result<(), Error> {
let block = self.heap.get_block(block_index)?;
block.array_clear(start_slot_index)
}
fn allocate_reserved_value(
&mut self,
name: &str,
parent_index: u32,
block_size: usize,
) -> Result<(Block<Arc<Mapping>>, Block<Arc<Mapping>>), Error> {
let block = self.heap.allocate_block(block_size)?;
let name_block = match self.allocate_name(name) {
Ok(b) => b,
Err(err) => {
self.heap.free_block(block)?;
return Err(err);
}
};
let result = self.heap.get_block(parent_index).and_then(|parent_block| match parent_block
.block_type()
{
BlockType::NodeValue | BlockType::Tombstone => {
parent_block.set_child_count(parent_block.child_count().unwrap() + 1)
}
BlockType::Header => Ok(()),
_ => {
return Err(Error::InvalidBlockType(
parent_index as usize,
parent_block.block_type(),
))
}
});
match result {
Ok(()) => Ok((block, name_block)),
Err(err) => {
self.heap.free_block(name_block)?;
self.heap.free_block(block)?;
Err(err)
}
}
}
fn allocate_name(&mut self, name: &str) -> Result<Block<Arc<Mapping>>, Error> {
let mut bytes = name.as_bytes();
let max_bytes = constants::MAX_ORDER_SIZE - constants::HEADER_SIZE_BYTES;
if bytes.len() > max_bytes {
bytes = &bytes[..max_bytes];
}
let name_block = self.heap.allocate_block(utils::block_size_for_payload(bytes.len()))?;
name_block.become_name(name)?;
Ok(name_block)
}
fn delete_value(&mut self, block: Block<Arc<Mapping>>) -> Result<(), Error> {
// Decrement parent child count.
let parent_index = block.parent_index()?;
if parent_index != constants::HEADER_INDEX {
let parent = self.heap.get_block(parent_index).unwrap();
let child_count = parent.child_count().unwrap() - 1;
if parent.block_type() == BlockType::Tombstone && child_count == 0 {
self.heap.free_block(parent).expect("Failed to free block");
} else {
parent.set_child_count(child_count).unwrap();
}
}
// Free the name block.
let name_index = block.name_index()?;
let name = self.heap.get_block(name_index).unwrap();
self.heap.free_block(name).expect("Failed to free block");
// If the block is a NODE and has children, make it a TOMBSTONE so that
// it's freed when the last of its children is freed. Otherwise, free it.
if block.block_type() == BlockType::NodeValue && block.child_count()? != 0 {
block.become_tombstone()
} else {
self.heap.free_block(block)
}
}
fn inner_set_property_value(
&mut self,
block: &Block<Arc<Mapping>>,
value: &[u8],
) -> Result<(), Error> {
self.free_extents(block.property_extent_index()?)?;
let extent_index = self.write_extents(value)?;
block.set_property_total_length(value.len().to_u32().unwrap())?;
block.set_property_extent_index(extent_index)?;
Ok(())
}
fn free_extents(&mut self, head_extent_index: u32) -> Result<(), Error> {
let mut index = head_extent_index;
while index != constants::HEADER_INDEX {
let block = self.heap.get_block(index).unwrap();
index = block.next_extent()?;
self.heap.free_block(block)?;
}
Ok(())
}
fn write_extents(&mut self, value: &[u8]) -> Result<u32, Error> {
if value.len() == 0 {
// Invalid index
return Ok(constants::HEADER_INDEX);
}
let mut offset = 0;
let total_size = value.len().to_usize().unwrap();
let mut extent_block =
self.heap.allocate_block(utils::block_size_for_payload(total_size - offset))?;
let head_extent_index = extent_block.index();
while offset < total_size {
extent_block.become_extent(0)?;
let bytes_written = extent_block.extent_set_contents(&value[offset..])?;
offset += bytes_written;
if offset < total_size {
let block =
self.heap.allocate_block(utils::block_size_for_payload(total_size - offset))?;
extent_block.set_extent_next_index(block.index())?;
extent_block = block;
}
}
Ok(head_extent_index)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
assert_inspect_tree,
reader::{snapshot::Snapshot, PartialNodeHierarchy},
Inspector,
},
fuchsia_async as fasync,
futures::prelude::*,
std::convert::TryFrom,
};
#[test]
fn test_create() {
let state = get_state(4096);
let snapshot = Snapshot::try_from(state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 9);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_node() {
let core_state = get_state(4096);
let block = {
let mut state = core_state.try_lock().expect("lock state");
// Create a node value and verify its fields
let block = state.create_node("test-node", 0).unwrap();
assert_eq!(block.block_type(), BlockType::NodeValue);
assert_eq!(block.index(), 1);
assert_eq!(block.child_count().unwrap(), 0);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.parent_index().unwrap(), 0);
// Verify name block.
let name_block = state.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 9);
assert_eq!(name_block.name_contents().unwrap(), "test-node");
block
};
// Verify blocks.
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 9);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::NodeValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert!(blocks[3..].iter().all(|b| b.block_type() == BlockType::Free));
{
let mut state = core_state.try_lock().expect("lock state");
let child_block = state.create_node("child1", 1).unwrap();
assert_eq!(block.child_count().unwrap(), 1);
// Create a child of the child and verify child counts.
let child11_block = state.create_node("child1-1", 4).unwrap();
assert_eq!(child11_block.child_count().unwrap(), 0);
assert_eq!(child_block.child_count().unwrap(), 1);
assert_eq!(block.child_count().unwrap(), 1);
assert!(state.free_value(child11_block.index()).is_ok());
assert_eq!(child_block.child_count().unwrap(), 0);
// Add a couple more children to the block and verify count.
let child_block2 = state.create_node("child2", 1).unwrap();
let child_block3 = state.create_node("child3", 1).unwrap();
assert_eq!(block.child_count().unwrap(), 3);
// Free children and verify count.
assert!(state.free_value(child_block.index()).is_ok());
assert!(state.free_value(child_block2.index()).is_ok());
assert!(state.free_value(child_block3.index()).is_ok());
assert_eq!(block.child_count().unwrap(), 0);
// Free node.
assert!(state.free_value(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_int_metric() {
let core_state = get_state(4096);
let block = {
let mut state = core_state.try_lock().expect("lock state");
// Creates with value
let block = state.create_int_metric("test", 3, 0).unwrap();
assert_eq!(block.block_type(), BlockType::IntValue);
assert_eq!(block.index(), 1);
assert_eq!(block.int_value().unwrap(), 3);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.parent_index().unwrap(), 0);
let name_block = state.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 4);
assert_eq!(name_block.name_contents().unwrap(), "test");
block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 10);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::IntValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert!(blocks[3..].iter().all(|b| b.block_type() == BlockType::Free));
{
let mut state = core_state.try_lock().expect("lock state");
assert!(state.add_int_metric(block.index(), 10).is_ok());
assert_eq!(block.int_value().unwrap(), 13);
assert!(state.subtract_int_metric(block.index(), 5).is_ok());
assert_eq!(block.int_value().unwrap(), 8);
assert!(state.set_int_metric(block.index(), -6).is_ok());
assert_eq!(block.int_value().unwrap(), -6);
assert_eq!(state.get_int_metric(block.index()).unwrap(), -6);
assert!(state.subtract_int_metric(block.index(), std::i64::MAX).is_ok());
assert_eq!(block.int_value().unwrap(), std::i64::MIN);
assert!(state.set_int_metric(block.index(), std::i64::MAX).is_ok());
assert!(state.add_int_metric(block.index(), 2).is_ok());
assert_eq!(block.int_value().unwrap(), std::i64::MAX);
// Free metric.
assert!(state.free_value(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_uint_metric() {
let core_state = get_state(4096);
let block = {
let mut state = core_state.try_lock().expect("try lock");
// Creates with value
let block = state.create_uint_metric("test", 3, 0).unwrap();
assert_eq!(block.block_type(), BlockType::UintValue);
assert_eq!(block.index(), 1);
assert_eq!(block.uint_value().unwrap(), 3);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.parent_index().unwrap(), 0);
let name_block = state.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 4);
assert_eq!(name_block.name_contents().unwrap(), "test");
block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 10);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::UintValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert!(blocks[3..].iter().all(|b| b.block_type() == BlockType::Free));
{
let mut state = core_state.try_lock().expect("try lock");
assert!(state.add_uint_metric(block.index(), 10).is_ok());
assert_eq!(block.uint_value().unwrap(), 13);
assert!(state.subtract_uint_metric(block.index(), 5).is_ok());
assert_eq!(block.uint_value().unwrap(), 8);
assert!(state.set_uint_metric(block.index(), 0).is_ok());
assert_eq!(block.uint_value().unwrap(), 0);
assert_eq!(state.get_uint_metric(block.index()).unwrap(), 0);
assert!(state.subtract_uint_metric(block.index(), std::u64::MAX).is_ok());
assert_eq!(block.uint_value().unwrap(), 0);
assert!(state.set_uint_metric(block.index(), 3).is_ok());
assert!(state.add_uint_metric(block.index(), std::u64::MAX).is_ok());
assert_eq!(block.uint_value().unwrap(), std::u64::MAX);
// Free metric.
assert!(state.free_value(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_double_metric() {
let core_state = get_state(4096);
// Creates with value
let block = {
let mut state = core_state.try_lock().expect("lock state");
let block = state.create_double_metric("test", 3.0, 0).unwrap();
assert_eq!(block.block_type(), BlockType::DoubleValue);
assert_eq!(block.index(), 1);
assert_eq!(block.double_value().unwrap(), 3.0);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.parent_index().unwrap(), 0);
let name_block = state.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 4);
assert_eq!(name_block.name_contents().unwrap(), "test");
block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 10);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::DoubleValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert!(blocks[3..].iter().all(|b| b.block_type() == BlockType::Free));
{
let mut state = core_state.try_lock().expect("lock state");
assert!(state.add_double_metric(block.index(), 10.5).is_ok());
assert_eq!(block.double_value().unwrap(), 13.5);
assert!(state.subtract_double_metric(block.index(), 5.1).is_ok());
assert_eq!(block.double_value().unwrap(), 8.4);
assert!(state.set_double_metric(block.index(), -6.0).is_ok());
assert_eq!(block.double_value().unwrap(), -6.0);
assert_eq!(state.get_double_metric(block.index()).unwrap(), -6.0);
// Free metric.
assert!(state.free_value(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_string_property() {
let core_state = get_state(4096);
let block = {
let mut state = core_state.try_lock().expect("lock state");
// Creates with value
let block =
state.create_property("test", b"test-property", PropertyFormat::String, 0).unwrap();
assert_eq!(block.block_type(), BlockType::BufferValue);
assert_eq!(block.index(), 1);
assert_eq!(block.parent_index().unwrap(), 0);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.property_total_length().unwrap(), 13);
assert_eq!(block.property_format().unwrap(), PropertyFormat::String);
let name_block = state.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 4);
assert_eq!(name_block.name_contents().unwrap(), "test");
let extent_block = state.heap().get_block(4).unwrap();
assert_eq!(extent_block.block_type(), BlockType::Extent);
assert_eq!(extent_block.next_extent().unwrap(), 0);
assert_eq!(
String::from_utf8(extent_block.extent_contents().unwrap()).unwrap(),
"test-property\0\0\0\0\0\0\0\0\0\0\0"
);
block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 11);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::BufferValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert_eq!(blocks[3].block_type(), BlockType::Free);
assert_eq!(blocks[4].block_type(), BlockType::Extent);
assert!(blocks[5..].iter().all(|b| b.block_type() == BlockType::Free));
{
let mut state = core_state.try_lock().expect("lock state");
// Free property.
assert!(state.free_property(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_bytevector_property() {
let core_state = get_state(4096);
// Creates with value
let block = {
let mut state = core_state.try_lock().expect("lock state");
let block =
state.create_property("test", b"test-property", PropertyFormat::Bytes, 0).unwrap();
assert_eq!(block.block_type(), BlockType::BufferValue);
assert_eq!(block.index(), 1);
assert_eq!(block.parent_index().unwrap(), 0);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.property_total_length().unwrap(), 13);
assert_eq!(block.property_extent_index().unwrap(), 4);
assert_eq!(block.property_format().unwrap(), PropertyFormat::Bytes);
let name_block = state.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 4);
assert_eq!(name_block.name_contents().unwrap(), "test");
let extent_block = state.heap().get_block(4).unwrap();
assert_eq!(extent_block.block_type(), BlockType::Extent);
assert_eq!(extent_block.next_extent().unwrap(), 0);
assert_eq!(
String::from_utf8(extent_block.extent_contents().unwrap()).unwrap(),
"test-property\0\0\0\0\0\0\0\0\0\0\0"
);
block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 11);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::BufferValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert_eq!(blocks[3].block_type(), BlockType::Free);
assert_eq!(blocks[4].block_type(), BlockType::Extent);
assert!(blocks[5..].iter().all(|b| b.block_type() == BlockType::Free));
// Free property.
{
let mut state = core_state.try_lock().expect("lock state");
assert!(state.free_property(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_bool() {
let core_state = get_state(4096);
let block = {
let mut state = core_state.try_lock().expect("lock state");
// Creates with value
let block = state.create_bool("test", true, 0).unwrap();
assert_eq!(block.block_type(), BlockType::BoolValue);
assert_eq!(block.index(), 1);
assert_eq!(block.bool_value().unwrap(), true);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.parent_index().unwrap(), 0);
let name_block = state.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 4);
assert_eq!(name_block.name_contents().unwrap(), "test");
block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 10);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::BoolValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert!(blocks[3..].iter().all(|b| b.block_type() == BlockType::Free));
// Free metric.
{
let mut state = core_state.try_lock().expect("lock state");
assert!(state.free_value(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_int_array() {
let core_state = get_state(4096);
let block = {
let mut state = core_state.try_lock().expect("lock state");
let block = state.create_int_array("test", 5, ArrayFormat::Default, 0).unwrap();
assert_eq!(block.block_type(), BlockType::ArrayValue);
assert_eq!(block.order(), 2);
assert_eq!(block.index(), 4);
assert_eq!(block.name_index().unwrap(), 1);
assert_eq!(block.parent_index().unwrap(), 0);
assert_eq!(block.array_slots().unwrap(), 5);
assert_eq!(block.array_format().unwrap(), ArrayFormat::Default);
assert_eq!(block.array_entry_type().unwrap(), BlockType::IntValue);
let name_block = state.heap().get_block(1).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 4);
assert_eq!(name_block.name_contents().unwrap(), "test");
for i in 0..5 {
state.set_array_int_slot(block.index(), i, 3 * i as i64).unwrap();
}
for i in 0..5 {
assert_eq!(block.array_get_int_slot(i).unwrap(), 3 * i as i64);
}
block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::Name);
assert_eq!(blocks[2].block_type(), BlockType::Free);
assert_eq!(blocks[3].block_type(), BlockType::ArrayValue);
assert!(blocks[4..].iter().all(|b| b.block_type() == BlockType::Free));
// Free the array.
{
let mut state = core_state.try_lock().expect("lock state");
assert!(state.free_value(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_multi_extent_property() {
let core_state = get_state(10000);
let block = {
let mut state = core_state.try_lock().expect("lock state");
let chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
let data = chars.iter().cycle().take(6000).collect::<String>();
let block =
state.create_property("test", data.as_bytes(), PropertyFormat::String, 0).unwrap();
assert_eq!(block.block_type(), BlockType::BufferValue);
assert_eq!(block.index(), 1);
assert_eq!(block.parent_index().unwrap(), 0);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.property_total_length().unwrap(), 6000);
assert_eq!(block.property_extent_index().unwrap(), 128);
assert_eq!(block.property_format().unwrap(), PropertyFormat::String);
let name_block = state.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 4);
assert_eq!(name_block.name_contents().unwrap(), "test");
let extent_block = state.heap().get_block(128).unwrap();
assert_eq!(extent_block.block_type(), BlockType::Extent);
assert_eq!(extent_block.order(), 7);
assert_eq!(extent_block.next_extent().unwrap(), 256);
assert_eq!(
extent_block.extent_contents().unwrap(),
chars.iter().cycle().take(2040).map(|&c| c as u8).collect::<Vec<u8>>()
);
let extent_block = state.heap().get_block(256).unwrap();
assert_eq!(extent_block.block_type(), BlockType::Extent);
assert_eq!(extent_block.order(), 7);
assert_eq!(extent_block.next_extent().unwrap(), 384);
assert_eq!(
extent_block.extent_contents().unwrap(),
chars.iter().cycle().skip(2040).take(2040).map(|&c| c as u8).collect::<Vec<u8>>()
);
let extent_block = state.heap().get_block(384).unwrap();
assert_eq!(extent_block.block_type(), BlockType::Extent);
assert_eq!(extent_block.order(), 7);
assert_eq!(extent_block.next_extent().unwrap(), 0);
assert_eq!(
extent_block.extent_contents().unwrap()[..1920],
chars.iter().cycle().skip(4080).take(1920).map(|&c| c as u8).collect::<Vec<u8>>()[..]
);
assert_eq!(extent_block.extent_contents().unwrap()[1920..], [0u8; 120][..]);
block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 12);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::BufferValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert!(blocks[3..9].iter().all(|b| b.block_type() == BlockType::Free));
assert_eq!(blocks[9].block_type(), BlockType::Extent);
assert_eq!(blocks[10].block_type(), BlockType::Extent);
assert_eq!(blocks[11].block_type(), BlockType::Extent);
// Free property.
{
let mut state = core_state.try_lock().expect("lock state");
assert!(state.free_property(block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_tombstone() {
let core_state = get_state(4096);
let child_block = {
let mut state = core_state.try_lock().expect("lock state");
// Create a node value and verify its fields
let block = state.create_node("root-node", 0).unwrap();
let child_block = state.create_node("child-node", block.index()).unwrap();
// Node still has children, so will become a tombstone.
assert!(state.free_value(block.index()).is_ok());
child_block
};
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[0].block_type() == BlockType::Header);
assert!(blocks[1].block_type() == BlockType::Tombstone);
assert!(blocks[2].block_type() == BlockType::Free);
assert!(blocks[3].block_type() == BlockType::NodeValue);
assert!(blocks[4].block_type() == BlockType::Free);
assert!(blocks[5].block_type() == BlockType::Name);
assert!(blocks[6..].iter().all(|b| b.block_type() == BlockType::Free));
// Freeing the child, causes all blocks to be freed.
{
let mut state = core_state.try_lock().expect("lock state");
assert!(state.free_value(child_block.index()).is_ok());
}
let snapshot = Snapshot::try_from(core_state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
}
#[test]
fn test_with_header_lock() {
let state = get_state(4096);
// Initial generation count is 0
assert_eq!(state.header.header_generation_count().unwrap(), 0);
// Lock the state
let mut lock_guard = state.try_lock().expect("lock state");
assert!(state.header.check_locked(true).is_ok());
assert_eq!(state.header.header_generation_count().unwrap(), 1);
// Operations on the lock guard do not change the generation counter.
let _ = lock_guard.create_node("test", 0).unwrap();
let _ = lock_guard.create_node("test2", 1).unwrap();
assert_eq!(state.header.header_generation_count().unwrap(), 1);
// Dropping the guard releases the lock.
drop(lock_guard);
assert_eq!(state.header.header_generation_count().unwrap(), 2);
assert!(state.header.check_locked(false).is_ok());
}
#[fasync::run_singlethreaded(test)]
async fn test_link() {
// Intialize state and create a link block.
let state = get_state(4096);
let block = {
let mut state_guard = state.try_lock().expect("lock state");
let block = state_guard
.create_lazy_node("link-name", 0, LinkNodeDisposition::Inline, || {
async move {
let inspector = Inspector::new();
inspector.root().record_uint("a", 1);
Ok(inspector)
}
.boxed()
})
.unwrap();
// Verify the callback was properly saved.
assert!(state_guard.callbacks().get("link-name-0").is_some());
let callback = state_guard.callbacks().get("link-name-0").unwrap();
match callback().await {
Ok(inspector) => {
let hierarchy =
PartialNodeHierarchy::try_from(&*inspector.vmo.unwrap()).unwrap();
assert_inspect_tree!(hierarchy, root: {
a: 1u64,
});
}
Err(_) => assert!(false),
}
// Verify link block.
assert_eq!(block.block_type(), BlockType::LinkValue);
assert_eq!(block.index(), 1);
assert_eq!(block.parent_index().unwrap(), 0);
assert_eq!(block.name_index().unwrap(), 2);
assert_eq!(block.link_content_index().unwrap(), 4);
assert_eq!(block.link_node_disposition().unwrap(), LinkNodeDisposition::Inline);
// Verify link's name block.
let name_block = state_guard.heap().get_block(2).unwrap();
assert_eq!(name_block.block_type(), BlockType::Name);
assert_eq!(name_block.name_length().unwrap(), 9);
assert_eq!(name_block.name_contents().unwrap(), "link-name");
// Verify link's content block.
let content_block = state_guard.heap().get_block(4).unwrap();
assert_eq!(content_block.block_type(), BlockType::Name);
assert_eq!(content_block.name_length().unwrap(), 11);
assert_eq!(content_block.name_contents().unwrap(), "link-name-0");
block
};
// Verfiy all the VMO blocks.
let snapshot = Snapshot::try_from(state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert_eq!(blocks.len(), 10);
assert_eq!(blocks[0].block_type(), BlockType::Header);
assert_eq!(blocks[1].block_type(), BlockType::LinkValue);
assert_eq!(blocks[2].block_type(), BlockType::Name);
assert_eq!(blocks[3].block_type(), BlockType::Name);
assert!(blocks[4..].iter().all(|b| b.block_type() == BlockType::Free));
// Free link
{
let mut state_guard = state.try_lock().expect("lock state");
assert!(state_guard.free_lazy_node(block.index()).is_ok());
// Verify the callback was cleared on free link.
assert!(state_guard.callbacks().get("link-name-0").is_none());
}
let snapshot = Snapshot::try_from(state.copy_vmo_bytes()).unwrap();
let blocks: Vec<Block<&[u8]>> = snapshot.scan().collect();
assert!(blocks[1..].iter().all(|b| b.block_type() == BlockType::Free));
// Verify adding another link generates a different ID regardless of the params.
let mut state_guard = state.try_lock().expect("lock state");
state_guard
.create_lazy_node("link-name", 0, LinkNodeDisposition::Inline, || {
async move { Ok(Inspector::new()) }.boxed()
})
.unwrap();
let content_block = state_guard.heap().get_block(4).unwrap();
assert_eq!(content_block.name_contents().unwrap(), "link-name-1");
}
#[fasync::run_singlethreaded(test)]
async fn stats() {
// Intialize state and create a link block.
let state = get_state(12288);
let mut state_guard = state.try_lock().expect("lock state");
let _block1 = state_guard
.create_lazy_node("link-name", 0, LinkNodeDisposition::Inline, || {
async move {
let inspector = Inspector::new();
inspector.root().record_uint("a", 1);
Ok(inspector)
}
.boxed()
})
.unwrap();
let _block2 = state_guard.create_uint_metric("test", 3, 0).unwrap();
assert_eq!(
state_guard.stats(),
Stats { total_dynamic_children: 1, maximum_size: 12288, current_size: 4096 }
)
}
fn get_state(size: usize) -> State {
let (mapping, _) = Mapping::allocate(size).unwrap();
let heap = Heap::new(Arc::new(mapping)).unwrap();
State::create(heap).unwrap()
}
}