blob: 9dfbf43101490971474d8c7e1ac2bc6305a7d944 [file] [log] [blame] [view]
# Ergonomic Inspect
This guide covers the usage of the
[`fuchsia_inspect_derive`](/src/lib/diagnostics/inspect/derive)
library, and assumes that you are familiar with
[Inspect](/docs/development/diagnostics/inspect)
and have basic experience with the
[`fuchsia_inspect`](/src/lib/diagnostics/inspect/rust) library.
## Overview
The `fuchsia_inspect_derive` library provides ergonomic macros, traits and
smart pointers around the `fuchsia_inspect` library, that makes it easier to
integrate inspect with your Rust code base, by:
- Owning source data and inspect data under the same RAII type
- Being idiomatic. First class support for primitives, common interior
mutability patterns and async.
- Generating repetitive boilerplate code
- Providing a unified way to [attach a type to inspect](#inspect-attaching)
- Supporting gradual integration with existing code bases, both those that
don't yet support inspect, and the ones that are integrated with
`fuchsia_inspect` directly.
- Supporting foreign types that lack inspect integration. See
[`IDebug<T>`](#idebug) for usage and constraints.
At the same time, it preserves the performance and semantics of a manual inspect
integration, by:
- Committing granular inspect tree modifications, where logical leaf nodes are
updated independently.
- Applying static dispatch only, to avoid additional runtime overhead.
- Not using any additional synchronization primitives.
### Caveats
When you integrate your Rust code base with this library, be aware that:
- The library mirrors the internal type hierarchy of the Rust program. Limited
structural modifications such as renaming, flattening and omitting fields are
supported (similar to [Serde][serde-field-attrs]). If the desired inspect tree
structure is vastly different from the type hierarchy, you should consider
using `fuchsia_inspect` directly.
- Some features are not yet supported, requiring you to
[implement `Inspect` manually](#implement-inspect-manually):
- Lazy nodes, histograms and inspect arrays.
- `Option<T>` and other enums.
- Collection types, such as vectors and maps.
- StringReferences
- The library promotes [custom smart pointers](#iowned), which creates another
layer of data wrapping.
## Quick start {#quick-start}
This section shows an example where you take an existing data structure and
apply inspect to that structure. Let's start with a simple example, a Yak:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="quick_start_before_decl" adjust_indentation="auto" %}
```
Then, consider this construction site:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="quick_start_before_init" adjust_indentation="auto" %}
```
Let's make the yak inspectable. In particular:
- Expose the current hair length
- Expose the number of times the Yak has been shaved
- The credit card number should NOT be exposed
Now use `fuchsia_inspect_derive` to make this Yak inspectable:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="quick_start_after_decl" adjust_indentation="auto" %}
```
Now, in your main program (or in a unit test), construct the yak and attach it
to the inspect tree:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="quick_start_after_init" adjust_indentation="auto" %}
```
Now you have integrated a simple program with Inspect. The rest of this guide
describes the types, traits and macros of this library, and how to apply them to
real world programs.
## Derive `Inspect` {#inspect-derive}
`derive(Inspect)` can be added to any named struct, *but each of its fields
must also implement `Inspect`* (except for `inspect_node` and skipped fields).
The library provides implementations of `Inspect` for several types:
- The [`IOwned` smart pointers](#iowned)
- Many common [interior mutability wrappers](#interior-mutability)
- All inspect properties (`UintProperty`, `StringProperty`, etc) except for
arrays and histograms
- Other `Inspect` types. [See the section on nesting](#inspect-nesting).
If you add a type that isn't `Inspect`, you get a compiler error:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/compiler_errors.rs" region_tag="derive_inspect_unwrapped" adjust_indentation="auto" %}
```
### Nested `Inspect` Types {#inspect-nesting}
`Inspect` types can be freely nested, like so:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_nested_decl" adjust_indentation="auto" %}
```
### Fields and Attributes {#inspect-attributes}
All fields, except for skipped fields and `inspect_node`, must implement
`Inspect`, either for `&mut T` or `&T`.
If an `inspect_node` field is present, instances will have its own node in the
inspect tree. It must be a `fuchsia_inspect::Node`:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_node_present_decl" adjust_indentation="auto" %}
```
If `inspect_node` is absent, fields will be attached directly to the parent node
(meaning that the name provided to `with_inspect` will be ignored):
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_node_absent_decl" adjust_indentation="auto" %}
```
If your type needs to add or remove nodes or properties dynamically,
it should own an inspect node. The inspect node is needed when
nodes or properties are added or removed after the initial attachment.
`derive(Inspect)` supports the following field attributes:
- `inspect(skip)`: The field is ignored by inspect.
- `inspect(rename = "foo")`: Use a different name. By default, the field name
is used.
- `inspect(forward)`: Forwards the attachment to an inner `Inspect` type, omitting one
layer of nesting from the inspect hierarchy. All other fields should not have any inspect
attributes. The type must NOT have an `inspect_node` field. Useful for wrapper types.
For example:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_forward_decl" adjust_indentation="auto" %}
```
### Manually Managed Inspect Types
If you are integrating with a code base that uses `fuchsia_inspect` directly,
its types are not be aware of `fuchsia_inspect_derive`. Do not add such
manually managed types as fields to an `Inspect` type directly. Instead,
[implement `Inspect` manually](#implement-inspect-manually) for the type.
Avoid attaching manually outside of the `Inspect` trait,
since attachment in `fuchsia_inspect_derive` occurs after construction.
Attaching in a constructor can silently cause its inspect state to be
absent.
### Attaching to the Inspect Tree {#inspect-attaching}
An inspect type should be attached once, and immediately after instantiation,
using the `with_inspect` extension trait method:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_node_present_init" adjust_indentation="auto" %}
```
If you have a nested `Inspect` structure, you should only attach the top-level
type. The nested types are attached implicitly:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_nested_init" adjust_indentation="auto" %}
```
Note that when a `Yak` is constructed from within a `Stable`, there is no
`with_inspect` call present. Instead, the `Yak` is automatically attached as a
child of the `Stable`. However, you can still attach a `Yak` when it is the
top-level type, such as in the unit tests for `Yak`. This allows you to test any
`Inspect` type in isolation.
You can optionally choose to supply inspect nodes in constructors instead of
explicitly calling `with_inspect` at the construction sites. First, ensure that
the type is NOT nested under another `Inspect` type (as this would cause
duplicate attachments). Sedondly, make sure to document this fact clearly,
so the calling user is aware of your attachment convention.
### Interior mutability {#interior-mutability}
In Rust (and particularly `async` Rust), it is common to use interior
mutability. This library provides `Inspect` implementations for several smart
pointers and locks:
- `std`: `Box`, `Arc`, `Rc`, `RefCell`, `Mutex` and `RwLock`
- Note that `Cell` does NOT work. Instead, upgrade to a `RefCell`.
- `parking_lot`: `Mutex` and `RwLock`
- `futures`: `Mutex`
Generally, interior mutability within a `derive(Inspect)` type just works:
```diff
#[derive(Inspect)]
struct Stable {
- yak: Yak,
+ yak: Arc<Mutex<Yak>>,
- horse: Horse,
+ horse: RefCell<Horse>,
inspect_node: fuchsia_inspect::Node,
}
```
Make sure to put your smart pointers inside your mutability wrapper:
```diff
struct Yak {
- coins: IValue<Rc<RwLock<u32>>>, // Won't compile
+ coins: Rc<RwLock<IValue<u32>>>, // Correct
}
```
If an inner type is behind a lock, attachment will fail if the lock is
acquired by someone else. Hence, always attach immediately after
instantiation.
### Implement `Inspect` Manually {#implement-inspect-manually}
The `derive(Inspect)` derive-macro generates an
`impl Inspect for &mut T { .. }`. Oftentimes, this works fine, but in
some cases you may need to implement `Inspect` manually. Fortunately,
the `Inspect` trait is quite simple:
```rust
trait Inspect {
/// Attach self to the inspect tree
fn iattach(self, parent: &Node, name: AsRef<str>) -> Result<(), AttachError>;
}
```
Do not return an `AttachError` for structural errors in the data.
Instead, report the error using logs or an inspect node.
`AttachError` is reserved for irrecoverable invariant errors that
fail the entire attachment.
## `IOwned` Smart Pointers {#iowned}
Smart pointers may sound scary, but you probably use them everyday already. For
instance, `Arc` and `Box` are smart pointers. They are statically dispatched,
and have first-class support in Rust (through [deref coercion]). This makes them
minimally invasive.
`fuchsia_inspect_derive` comes with a few useful smart pointers that implement
`Inspect` and can be used to wrap primitives, debuggable types, and more. They
all share the same behavior: An `IOwned<T>` smart pointer owns a generic
**source type** `T` and some associated **inspect data**.
Here is a demonstration of the `IOwned` API:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="smart_pointers_ivalue" adjust_indentation="auto" %}
```
An `IOwned<T>` smart pointer should not be instantiated directly, but rather one
of its variants:
### `IValue<T>` {#ivalue}
The `IValue<T>` smart pointer wraps a primitive ([or any type `T: Unit`](#unit)).
For example, an `IValue<f32>` is represented as a `DoubleProperty`, and
an `IValue<i16>` is represented as an `IntProperty`.
An `IValue` of a primitive results in the same structure as using a plain
inspect property directly. So, why would you use an `IValue`? If you only
need to write or increment a value, you can use a plain inspect property. If you
also need to read the value, you should use an `IValue`.
### `IDebug<T>` {#idebug}
The `IDebug<T>` smart pointer wraps a debuggable type, and maintains the debug
representation of `T` as a `StringProperty`. This is useful for:
- Foreign types, where adding an inspect implementation is infeasible
- Debugging, to quickly verify some state about your program
Avoid using debug representations in production code, since they come with
the following issues:
- Debug representations are written on every inspect update, which can result in
unnecessary performance overhead.
- Debug representations can exhaust the space of the inspect VMO, causing
truncation of the entire inspect state.
- Debug representations cannot be integrated with the privacy pipeline: if any
PII is exposed as part of the debug string, the entire field must be
considered PII. Managing your own structured data allows to granularly redact
fields containing PII.
## The `Unit` Trait {#unit}
The `Unit` trait describes the inspect representation of a type, how to
initialize it, and how to update it. It should be implemented for types that act
as a logical leaf node, and does NOT support per-field updates. This library
provides implementations of `Unit` for most primitives. For example, `u8`,
`u16`, `u32` and `u64` are represented as a `UintProperty`.
### Usage in IValue {#unit-usage}
A `Unit` type should be wrapped in an `IValue<T: Unit>` (see above), for a RAII
managed inspectable type. It is NOT recommended to call methods on `Unit`
directly.
### Derive `Unit` {#unit-derive}
Sometimes a logical `Unit` is a composite type. Unit can be derived for a named
struct, as long as its fields also implement `Unit`. For example:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="unit_plain_decl" adjust_indentation="auto" %}
```
`Unit` can be nested, but keep in mind that all fields are still written at
the same time:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="unit_nested_decl" adjust_indentation="auto" %}
```
#### Attributes {#unit-attributes}
`derive(Unit)` supports the following field attributes:
- `inspect(skip)`: The field is ignored by inspect.
- `inspect(rename = "foo")`: Use a different name. By default, the field name
is used.
[deref coercion]:
https://doc.rust-lang.org/1.27.2/book/second-edition/ch15-02-deref.html
[serde-field-attrs]:
https://serde.rs/field-attrs.html