blob: ea8dfa7cd9392d2d598de86fd6c28bede4f16580 [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.
//! Provides convenience macros for writing to an inspect bounded list (bounded log)
//!
//! ## Example
//!
//! ```rust
//! let inspector = Inspector::new();
//! let list_node = inspector.root().create_child("list_node");
//! let list_node = BoundedListNode::new(list_node, 10);
//! inspect_log!(list_node, k1: "1".to_string(), meaning_of_life: 42u64, k3: 3i64, k4: 4f64);
//! // Inspect now has:
//! // root: {
//! // list_node: {
//! // "0": { "@time": <timestamp>, k1: "1", meaning_of_life: 42, k3: 3, k4: 4 }
//! // }
//! // }
//! ```
mod impls;
mod wrappers;
pub use wrappers::{InspectBytes, InspectList, InspectListClosure};
use fuchsia_inspect::{Node, StringReference};
use lazy_static::lazy_static;
lazy_static! {
pub static ref TIME_KEY: StringReference<'static> = "@time".into();
}
/// Trait for writing to a node in bounded lists.
pub trait WriteInspect {
/// Write a *single* value (property or child node) to |node| with the specified |key|.
/// If multiple properties need to be written, consider creating a single child
/// node with those properties.
///
/// If the same key is used to write values multiple times, then there will be multiple
/// values with the same name in the underlying VMO.
fn write_inspect(&self, writer: &Node, key: &str);
}
/// Macro to log a new entry to a bounded list node with the specified key-value pairs. Each value
/// must be a type that implements `WriteInspect`. This macro automatically injects a timestamp
/// to each entry.
///
/// Example:
///
/// ```
/// let bounded_list_node = ...;
/// inspect_log!(bounded_list_node, {}); // log only a timestamped entry
/// inspect_log!(bounded_list_node, k1: "1", k2: 2i64, k3: "3"); // non-block form
/// inspect_log!(bounded_list_node, { // block form (only difference is syntactic)
/// ba: "dum",
/// tss: "tss",
/// });
/// inspect_log!(bounded_list_node, { // logging nested data structure
/// k1: {
/// subkey1: "subval1",
/// subkey2: 2,
/// }
/// });
/// ```
///
///# Panics
///
/// inspect_log! uses [`NodeExt::record_time`] which panics if the caller did not set up a
/// fuchsia_async executor
#[macro_export]
macro_rules! inspect_log {
($bounded_list_node:expr, $($args:tt)+) => {{
use $crate::{inspect_insert, log::TIME_KEY, nodes::NodeExt};
// Hack to allow the client to pass in a MutexGuard temporary for $bounded_list_node expr,
// since the temporary lives until the end of the match expression. Example usage:
// ```
// let list_node: Mutex<BoundedListNode> = ...;
// inspect_log!(list_node.lock(), ...);
// ```
match $bounded_list_node.create_entry() {
node => {
node.atomic_update(|this_node| {
this_node.record_time(&*TIME_KEY);
inspect_insert!(@internal_inspect_log this_node, $($args)+);
});
}
}
}};
}
/// Macro to insert items using a Node. Each value must be a type that implements
/// `WriteInspect`.
///
/// Example:
///
/// ```
/// let node = ...; // fuchsia_inspect::Node
/// inspect_insert!(node, k1: "1", k2: 2i64, k3: "3");
/// ```
#[macro_export]
macro_rules! inspect_insert {
(@internal $node_writer:expr,) => {};
// Insert tree
(@internal $node_writer:expr, var $key:ident: { $($sub:tt)+ }) => {{
let child_writer = $node_writer.create_child($key);
inspect_insert!(@internal child_writer, $($sub)+);
$node_writer.record(child_writer);
}};
(@internal $node_writer:expr, var $key:ident: { $($sub:tt)+ }, $($rest:tt)*) => {{
inspect_insert!(@internal $node_writer, var $key: { $($sub)+ });
inspect_insert!(@internal $node_writer, $($rest)*);
}};
// Insert properties and metrics
(@internal $node_writer:expr, var $key:ident: $val:expr) => {{
$val.write_inspect(&$node_writer, $key);
}};
(@internal $node_writer:expr, var $key:ident: $val:expr, $($rest:tt)*) => {{
inspect_insert!(@internal $node_writer, var $key: $val);
inspect_insert!(@internal $node_writer, $($rest)*);
}};
// Insert optional value
(@internal $node_writer:expr, var $key:ident?: $val:expr) => {{
match $val {
Some(val) => inspect_insert!(@internal $node_writer, var $key: val),
None => (),
}
}};
(@internal $node_writer:expr, var $key:ident?: $val:expr, $($rest:tt)*) => {{
inspect_insert!(@internal $node_writer, var $key?: $val);
inspect_insert!(@internal $node_writer, $($rest)*);
}};
// Key identifier format
(@internal $node_writer:expr, $key:ident: $($rest:tt)+) => {{
let key = stringify!($key);
inspect_insert!(@internal $node_writer, var key: $($rest)+);
}};
(@internal $node_writer:expr, $key:ident?: $($rest:tt)+) => {{
let key = stringify!($key);
inspect_insert!(@internal $node_writer, var key?: $($rest)+);
}};
// Entry point: from inspect_log! (mainly to allow empty event)
(@internal_inspect_log $node_writer:expr, { $($args:tt)* }) => {{
// User may specify an empty event, so `WriteInspect` may not always
// be used.
#[allow(unused_imports)]
use $crate::log::WriteInspect;
inspect_insert!(@internal $node_writer, $($args)*);
}};
(@internal_inspect_log $node_writer:expr, $($args:tt)+) => {{
use $crate::log::WriteInspect;
inspect_insert!(@internal $node_writer, $($args)+);
}};
// Entry point: block syntax
($node_writer:expr, { $($args:tt)+ }) => {{
use $crate::log::WriteInspect;
inspect_insert!(@internal $node_writer, $($args)+);
}};
// Entry point: non-block syntax
($node_writer:expr, $($args:tt)+) => {{
use $crate::log::WriteInspect;
inspect_insert!(@internal $node_writer, $($args)+);
}};
}
/// Convenience macro to construct a closure that implements WriteInspect, so it can be
/// used in `inspect_log!` and `inspect_insert!`.
///
/// Note that this macro constructs a *move* closure underneath, unlike `inspect_log!` and
/// `inspect_insert!` where variables are only borrowed.
///
/// Example 1:
///
/// ```
/// let bounded_list_node = ...;
/// let obj = make_inspect_loggable!(k1: "1", k2: 2i64, k3: "3");
/// inspect_log!(bounded_list_node, some_key: obj);
/// ```
///
/// Example 2
///
/// ```
/// let bounded_list_node = ...;
/// let point = Some((10, 50));
/// inspect_log!(bounded_list_node, point?: point.map(|(x, y)| make_inspect_loggable!({
/// x: x,
/// y: y,
/// })))
/// ```
#[macro_export]
macro_rules! make_inspect_loggable {
($($args:tt)+) => {{
use $crate::inspect_insert;
use fuchsia_inspect::Node;
struct WriteInspectClosure<F>(F);
impl<F> WriteInspect for WriteInspectClosure<F> where F: Fn(&Node, &str) {
fn write_inspect(&self, writer: &Node, key: &str) {
self.0(writer, key);
}
}
let f = WriteInspectClosure(move |writer: &Node, key: &str| {
let child = writer.create_child(key);
inspect_insert!(child, $($args)+);
writer.record(child);
});
f
}};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nodes::BoundedListNode;
use fuchsia_async as fasync;
use fuchsia_inspect::{assert_data_tree, Inspector};
use parking_lot::Mutex;
#[fuchsia::test]
fn test_inspect_log_basic() {
let executor = fasync::TestExecutor::new_with_fake_time().unwrap();
executor.set_fake_time(fasync::Time::from_nanos(0));
let (inspector, mut node) = inspector_and_list_node();
// Logging string and full-size numeric type
inspect_log!(node, k1: "1".to_string(), meaning_of_life: 42u64, k3: 3i64, k4: 4f64);
// Logging smaller numeric types (which should be converted to bigger types)
executor.set_fake_time(fasync::Time::from_nanos(5));
inspect_log!(node, small_uint: 1u8, small_int: 2i8, float: 3f32);
// Logging reference types + using bracket format
executor.set_fake_time(fasync::Time::from_nanos(10));
inspect_log!(node, {
s: "str",
uint: &13u8,
});
// Logging empty event
executor.set_fake_time(fasync::Time::from_nanos(15));
inspect_log!(node, {});
assert_data_tree!(inspector, root: {
list_node: {
"0": { "@time": 0i64, k1: "1", meaning_of_life: 42u64, k3: 3i64, k4: 4f64 },
"1": { "@time": 5i64, small_uint: 1u64, small_int: 2i64, float: 3f64 },
"2": { "@time": 10i64, s: "str", uint: 13u64 },
"3": { "@time": 15i64 },
}
});
}
#[fuchsia::test]
fn test_inspect_log_nested() {
let executor = fasync::TestExecutor::new_with_fake_time().unwrap();
executor.set_fake_time(fasync::Time::from_nanos(12345));
let (inspector, mut node) = inspector_and_list_node();
inspect_log!(node, {
k1: {
sub1: "subval1",
sub2: {
subsub1: "subsubval1",
},
sub3: 3u64,
},
k2: if true { 10u64 } else { 20 }
});
assert_data_tree!(inspector, root: {
list_node: {
"0": {
"@time": 12345i64,
k1: {
sub1: "subval1",
sub2: {
subsub1: "subsubval1",
},
sub3: 3u64,
},
k2: 10u64
}
}
});
}
#[fuchsia::test]
fn test_inspect_log_var_key_syntax() {
let executor = fasync::TestExecutor::new_with_fake_time().unwrap();
executor.set_fake_time(fasync::Time::from_nanos(12345));
let (inspector, mut node) = inspector_and_list_node();
let key = "@@@";
inspect_log!(node, var key: "!!!");
assert_data_tree!(inspector, root: {
list_node: {
"0": {
"@time": 12345i64,
"@@@": "!!!"
}
}
});
}
#[fuchsia::test]
fn test_inspect_log_parsing() {
let _executor = fasync::TestExecutor::new();
// if this test compiles, it's considered as succeeded
let (_inspector, mut node) = inspector_and_list_node();
// Non-block version, no trailing comma
inspect_log!(node, k1: "v1", k2: "v2");
// Non-block version, trailing comma
inspect_log!(node, k1: "v1", k2: "v2",);
// Block version, no trailing comma
inspect_log!(node, {
k1: "v1",
k2: "v2"
});
// Block version, trailing comma
inspect_log!(node, {
k1: "v1",
k2: "v2",
});
}
#[fuchsia::test]
fn test_inspect_log_allows_mutex_guard_temporary() {
// if this test compiles, it's considered as succeeded
let _executor = fasync::TestExecutor::new();
let (_inspector, node) = inspector_and_list_node();
let node = Mutex::new(node);
inspect_log!(node.lock(), k1: "v1");
}
#[fuchsia::test]
fn test_inspect_log_macro_does_not_move_value() {
// if this test compiles, it's considered as succeeded
let _executor = fasync::TestExecutor::new();
let (_inspector, mut node) = inspector_and_list_node();
let s = String::from("s");
inspect_log!(node, s: s);
// Should not cause compiler error since value is not moved
println!("{}", s);
}
#[fuchsia::test]
fn test_log_option() {
let executor = fasync::TestExecutor::new_with_fake_time().unwrap();
executor.set_fake_time(fasync::Time::from_nanos(12345));
let (inspector, mut node) = inspector_and_list_node();
inspect_log!(node, some?: Some("a"));
executor.set_fake_time(fasync::Time::from_nanos(24680));
inspect_log!(node, none?: None as Option<String>);
assert_data_tree!(inspector, root: {
list_node: {
"0": { "@time": 12345i64, some: "a" },
"1": { "@time": 24680i64 },
}
});
}
#[fuchsia::test]
fn test_log_inspect_bytes() {
let executor = fasync::TestExecutor::new_with_fake_time().unwrap();
executor.set_fake_time(fasync::Time::from_nanos(12345));
let (inspector, mut node) = inspector_and_list_node();
let bytes = [11u8, 22, 33];
inspect_log!(node, bytes: InspectBytes(&bytes));
inspect_log!(node, bytes: InspectBytes(&bytes[..]));
inspect_log!(node, bytes: InspectBytes(bytes));
assert_data_tree!(inspector, root: {
list_node: {
"0": { "@time": 12345i64, bytes: vec![11u8, 22, 33] },
"1": { "@time": 12345i64, bytes: vec![11u8, 22, 33] },
"2": { "@time": 12345i64, bytes: vec![11u8, 22, 33] },
}
});
}
#[fuchsia::test]
fn test_log_inspect_list() {
let executor = fasync::TestExecutor::new_with_fake_time().unwrap();
executor.set_fake_time(fasync::Time::from_nanos(12345));
let (inspector, mut node) = inspector_and_list_node();
let list = [11u8, 22, 33];
inspect_log!(node, list: InspectList(&list));
assert_data_tree!(inspector, root: {
list_node: {
"0": {
"@time": 12345i64,
list: {
"0": 11u64,
"1": 22u64,
"2": 33u64,
}
}
}
});
}
#[fuchsia::test]
fn test_log_inspect_list_closure() {
let executor = fasync::TestExecutor::new_with_fake_time().unwrap();
executor.set_fake_time(fasync::Time::from_nanos(12345));
let (inspector, mut node) = inspector_and_list_node();
let list = [13u32, 17, 29];
let list_mapped = InspectListClosure(&list, |node_writer, key, item| {
inspect_insert!(node_writer, var key: item * 2);
});
inspect_log!(node, list: list_mapped);
assert_data_tree!(inspector, root: {
list_node: {
"0": {
"@time": 12345i64,
list: {
"0": 26u64,
"1": 34u64,
"2": 58u64,
}
}
}
});
}
#[fuchsia::test]
fn test_inspect_insert_parsing() {
// if this test compiles, it's considered as succeeded
let _executor = fasync::TestExecutor::new();
let (_inspector, mut node) = inspector_and_list_node();
let node_writer = node.create_entry();
// Non-block version, no trailing comma
inspect_insert!(node_writer, k1: "v1".to_string(), k2: if true { 10u64 } else { 20 });
// Non-block version, trailing comma
inspect_insert!(node_writer, k1: 1i64, k2: 2f64,);
// Block version, no trailing comma
inspect_insert!(node_writer, {
k1: 1u8,
k2: 2i8
});
// Block version, trailing comma
inspect_insert!(node_writer, {
k1: &1u64,
k2?: Some("v2"),
});
}
#[fuchsia::test]
fn test_make_inspect_loggable() {
let executor = fasync::TestExecutor::new_with_fake_time().unwrap();
executor.set_fake_time(fasync::Time::from_nanos(12345));
let (inspector, mut node) = inspector_and_list_node();
let obj = make_inspect_loggable!(k1: "1", k2: 2i64, k3: "3");
inspect_log!(node, some_key: obj);
let point = Some((10i64, 50i64));
inspect_log!(node, point?: point.map(|(x, y)| make_inspect_loggable!({
x: x,
y: y,
})));
assert_data_tree!(inspector, root: {
list_node: {
"0": {
"@time": 12345i64,
some_key: { k1: "1", k2: 2i64, k3: "3" },
},
"1": {
"@time": 12345i64,
point: { x: 10i64, y: 50i64 },
},
}
});
}
fn inspector_and_list_node() -> (Inspector, BoundedListNode) {
let inspector = Inspector::new();
let list_node = inspector.root().create_child("list_node");
let list_node = BoundedListNode::new(list_node, 10);
(inspector, list_node)
}
}