use crate::writer::private::InspectTypeInternal;
use crate::writer::{Error, Heap, Node, State};
use diagnostics_hierarchy::{DiagnosticsHierarchy, DiagnosticsHierarchyGetter};
use inspect_format::{constants, BlockContainer, Container};
use std::borrow::Cow;
use std::cmp::max;
use std::fmt;
use std::sync::Arc;
use tracing::error;
#[cfg(target_os = "fuchsia")]
use zx::{self as zx, AsHandleRef, HandleBased};
#[derive(Clone)]
pub struct Inspector {
root_node: Arc<Node>,
#[allow(dead_code)] storage: Option<Arc<<Container as BlockContainer>::ShareableData>>,
}
impl fmt::Debug for Inspector {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let tree = self.get_diagnostics_hierarchy();
if fmt.alternate() {
write!(fmt, "{:#?}", tree)
} else {
write!(fmt, "{:?}", tree)
}
}
}
impl DiagnosticsHierarchyGetter<String> for Inspector {
fn get_diagnostics_hierarchy(&self) -> Cow<'_, DiagnosticsHierarchy> {
let hierarchy = futures::executor::block_on(async move { crate::reader::read(self).await })
.expect("failed to get hierarchy");
Cow::Owned(hierarchy)
}
}
#[cfg(target_os = "fuchsia")]
impl Inspector {
pub fn duplicate_vmo(&self) -> Option<zx::Vmo> {
self.storage.as_ref().and_then(|vmo| {
vmo.duplicate_handle(
zx::Rights::BASIC | zx::Rights::READ | zx::Rights::MAP | zx::Rights::GET_PROPERTY,
)
.ok()
})
}
pub fn duplicate_vmo_with_rights(&self, rights: zx::Rights) -> Option<zx::Vmo> {
self.storage.as_ref().and_then(|vmo| vmo.duplicate_handle(rights).ok())
}
pub fn frozen_vmo_copy(&self) -> Option<zx::Vmo> {
self.state()?.try_lock().ok().and_then(|mut state| state.frozen_vmo_copy().ok()).flatten()
}
pub fn copy_vmo(&self) -> Option<zx::Vmo> {
self.copy_vmo_data().and_then(|data| {
if let Ok(vmo) = zx::Vmo::create(data.len() as u64) {
vmo.write(&data, 0).ok().map(|_| vmo)
} else {
None
}
})
}
pub(crate) fn get_storage_handle(&self) -> Option<Arc<zx::Vmo>> {
self.storage.clone()
}
#[cfg(test)]
pub fn is_frozen(&self) -> Result<(), u64> {
use inspect_format::Block;
let vmo = self.storage.as_ref().unwrap();
let mut buffer: [u8; 16] = [0; 16];
vmo.read(&mut buffer, 0).unwrap();
let block = Block::new(&buffer, inspect_format::BlockIndex::EMPTY);
if block.header_generation_count().unwrap() == constants::VMO_FROZEN {
Ok(())
} else {
Err(block.header_generation_count().unwrap())
}
}
}
#[cfg(not(target_os = "fuchsia"))]
impl Inspector {
pub(crate) fn duplicate_vmo(&self) -> Option<<Container as BlockContainer>::Data> {
self.copy_vmo_data()
}
pub(crate) fn get_storage_handle(&self) -> Option<Vec<u8>> {
self.copy_vmo_data()
}
}
impl Default for Inspector {
fn default() -> Self {
Inspector::new(InspectorConfig::default())
}
}
impl Inspector {
pub fn new(conf: InspectorConfig) -> Self {
conf.build()
}
pub fn copy_vmo_data(&self) -> Option<Vec<u8>> {
self.root_node.inner.inner_ref().and_then(|inner_ref| inner_ref.state.copy_vmo_bytes())
}
pub fn max_size(&self) -> Option<usize> {
self.state()?.try_lock().ok().map(|state| state.stats().maximum_size)
}
pub fn is_valid(&self) -> bool {
self.root_node.is_valid()
}
pub fn root(&self) -> &Node {
&self.root_node
}
pub fn atomic_update<F, R>(&self, update_fn: F) -> R
where
F: FnOnce(&Node) -> R,
{
self.root().atomic_update(update_fn)
}
pub(crate) fn state(&self) -> Option<State> {
self.root().inner.inner_ref().map(|inner_ref| inner_ref.state.clone())
}
}
pub struct InspectorConfig {
is_no_op: bool,
size: usize,
storage: Option<Arc<<Container as BlockContainer>::ShareableData>>,
}
impl Default for InspectorConfig {
fn default() -> Self {
Self { is_no_op: false, size: constants::DEFAULT_VMO_SIZE_BYTES, storage: None }
}
}
impl InspectorConfig {
pub fn no_op(mut self) -> Self {
self.is_no_op = true;
self
}
pub fn size(mut self, max_size: usize) -> Self {
self.size = max_size;
self
}
fn create_no_op(self) -> Inspector {
Inspector { storage: self.storage, root_node: Arc::new(Node::new_no_op()) }
}
fn adjusted_buffer_size(max_size: usize) -> usize {
let mut size = max(constants::MINIMUM_VMO_SIZE_BYTES, max_size);
if size % constants::MINIMUM_VMO_SIZE_BYTES != 0 {
size =
(1 + size / constants::MINIMUM_VMO_SIZE_BYTES) * constants::MINIMUM_VMO_SIZE_BYTES;
}
size
}
}
#[cfg(target_os = "fuchsia")]
impl InspectorConfig {
pub fn vmo(mut self, vmo: zx::Vmo) -> Self {
self.storage = Some(Arc::new(vmo));
self.no_op()
}
fn build(self) -> Inspector {
if self.is_no_op {
return self.create_no_op();
}
match Self::new_root(self.size) {
Ok((storage, root_node)) => {
Inspector { storage: Some(storage), root_node: Arc::new(root_node) }
}
Err(e) => {
error!("Failed to create root node. Error: {:?}", e);
self.create_no_op()
}
}
}
fn new_root(
max_size: usize,
) -> Result<(Arc<<Container as BlockContainer>::ShareableData>, Node), Error> {
let size = Self::adjusted_buffer_size(max_size);
let (container, vmo) = Container::read_and_write(size).map_err(Error::AllocateVmo)?;
let name = zx::Name::new("InspectHeap").unwrap();
vmo.set_name(&name).map_err(Error::AllocateVmo)?;
let vmo = Arc::new(vmo);
let heap = Heap::new(container).map_err(|e| Error::CreateHeap(Box::new(e)))?;
let state =
State::create(heap, vmo.clone()).map_err(|e| Error::CreateState(Box::new(e)))?;
Ok((vmo, Node::new_root(state)))
}
}
#[cfg(not(target_os = "fuchsia"))]
impl InspectorConfig {
fn build(self) -> Inspector {
if self.is_no_op {
return self.create_no_op();
}
match Self::new_root(self.size) {
Ok((root_node, storage)) => {
Inspector { storage: Some(storage), root_node: Arc::new(root_node) }
}
Err(e) => {
error!("Failed to create root node. Error: {:?}", e);
self.create_no_op()
}
}
}
fn new_root(
max_size: usize,
) -> Result<(Node, Arc<<Container as BlockContainer>::ShareableData>), Error> {
let size = Self::adjusted_buffer_size(max_size);
let (container, storage) = Container::read_and_write(size).unwrap();
let heap = Heap::new(container).map_err(|e| Error::CreateHeap(Box::new(e)))?;
let state =
State::create(heap, Arc::new(storage)).map_err(|e| Error::CreateState(Box::new(e)))?;
Ok((Node::new_root(state), Arc::new(storage)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assert_update_is_atomic;
use futures::FutureExt;
#[fuchsia::test]
fn debug_impl() {
let inspector = Inspector::default();
inspector.root().record_int("name", 5);
assert_eq!(
format!("{:?}", &inspector),
"DiagnosticsHierarchy { name: \
\"root\", properties: [Int(\"name\", 5)], children: [], missing: [] }"
);
let pretty = r#"DiagnosticsHierarchy {
name: "root",
properties: [
Int(
"name",
5,
),
],
children: [],
missing: [],
}"#;
assert_eq!(format!("{:#?}", &inspector), pretty);
let two = inspector.root().create_child("two");
two.record_lazy_child("two_child", || {
let insp = Inspector::default();
insp.root().record_double("double", 1.0);
async move { Ok(insp) }.boxed()
});
let pretty = r#"DiagnosticsHierarchy {
name: "root",
properties: [
Int(
"name",
5,
),
],
children: [
DiagnosticsHierarchy {
name: "two",
properties: [],
children: [
DiagnosticsHierarchy {
name: "two_child",
properties: [
Double(
"double",
1.0,
),
],
children: [],
missing: [],
},
],
missing: [],
},
],
missing: [],
}"#;
assert_eq!(format!("{:#?}", &inspector), pretty);
}
#[fuchsia::test]
fn inspector_new() {
let test_object = Inspector::default();
assert_eq!(test_object.max_size().unwrap(), constants::DEFAULT_VMO_SIZE_BYTES);
}
#[fuchsia::test]
fn inspector_copy_data() {
let test_object = Inspector::default();
assert_eq!(test_object.max_size().unwrap(), constants::DEFAULT_VMO_SIZE_BYTES);
assert_eq!(test_object.copy_vmo_data().unwrap().len(), 4096);
}
#[fuchsia::test]
fn no_op() {
let inspector = Inspector::new(InspectorConfig::default().size(4096));
let nodes = (0..84)
.map(|i| inspector.root().create_child(format!("test-{}", i)))
.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());
}
#[fuchsia::test]
fn inspector_new_with_size() {
let test_object = Inspector::new(InspectorConfig::default().size(8192));
assert_eq!(test_object.max_size().unwrap(), 8192);
let test_object = Inspector::new(InspectorConfig::default().size(10000));
assert_eq!(test_object.max_size().unwrap(), 12288);
let test_object = Inspector::new(InspectorConfig::default().size(2000));
assert_eq!(test_object.max_size().unwrap(), 4096);
}
#[fuchsia::test]
async fn atomic_update() {
let insp = Inspector::default();
assert_update_is_atomic!(insp, |n| {
n.record_int("", 1);
n.record_int("", 2);
n.record_uint("", 3);
n.record_string("", "abcd");
});
}
}
#[cfg(all(test, target_os = "fuchsia"))]
mod fuchsia_tests {
use super::*;
#[fuchsia::test]
fn inspector_duplicate_vmo() {
let test_object = Inspector::default();
assert_eq!(
test_object.storage.as_ref().unwrap().get_size().unwrap(),
constants::DEFAULT_VMO_SIZE_BYTES as u64
);
assert_eq!(
test_object.duplicate_vmo().unwrap().get_size().unwrap(),
constants::DEFAULT_VMO_SIZE_BYTES as u64
);
}
#[fuchsia::test]
fn inspector_new_root() {
let (vmo, root_node) = InspectorConfig::new_root(100).unwrap();
assert_eq!(vmo.get_size().unwrap(), 4096);
let inner = root_node.inner.inner_ref().unwrap();
assert_eq!(*inner.block_index, 0);
assert_eq!("InspectHeap", vmo.get_name().expect("Has name"));
}
#[fuchsia::test]
fn freeze_vmo_works() {
let inspector = Inspector::default();
let initial = inspector
.state()
.unwrap()
.with_current_header(|header| header.header_generation_count().unwrap());
let vmo = inspector.frozen_vmo_copy();
let is_frozen_result = inspector.is_frozen();
assert!(is_frozen_result.is_err());
assert_eq!(initial + 2, is_frozen_result.err().unwrap());
assert!(is_frozen_result.err().unwrap() % 2 == 0);
let frozen_insp = Inspector::new(InspectorConfig::default().no_op().vmo(vmo.unwrap()));
assert!(frozen_insp.is_frozen().is_ok());
}
#[fuchsia::test]
fn transactions_block_freezing() {
let inspector = Inspector::default();
inspector.atomic_update(|_| assert!(inspector.frozen_vmo_copy().is_none()));
}
#[fuchsia::test]
fn transactions_block_copying() {
let inspector = Inspector::default();
inspector.atomic_update(|_| assert!(inspector.copy_vmo().is_none()));
inspector.atomic_update(|_| assert!(inspector.copy_vmo_data().is_none()));
}
#[fuchsia::test]
fn inspector_new_with_size() {
let test_object = Inspector::new(InspectorConfig::default().size(8192));
assert_eq!(test_object.max_size().unwrap(), 8192);
assert_eq!(
"InspectHeap",
test_object.storage.as_ref().unwrap().get_name().expect("Has name")
);
let test_object = Inspector::new(InspectorConfig::default().size(10000));
assert_eq!(test_object.max_size().unwrap(), 12288);
let test_object = Inspector::new(InspectorConfig::default().size(2000));
assert_eq!(test_object.max_size().unwrap(), 4096);
}
}