Inspect Quick Start

This document is a guide on how to get started with Component Inspection depending on your needs:

I want to learn more about Inspect concepts

Read the README.

I want to know how to use the iquery tool

Read the iquery manual. For detailed examples of usage, see the Codelab.

This document provides a simplified example of iquery below.

I have an existing or new component, and I want to support inspection.

Continue reading this document.

Languages

See below for the quick start guide in your language of choice.

  • {C++}

    Setup

    This section assumes you are writing an asynchronous component and that some part of your component (typically main.cc) looks like this:

    1. async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
    2. auto component_context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
    3. // ...
    4. loop.Run();

    This sets up an async loop, creates a ComponentContext wrapping handles provided by the runtime, and then runs that loop following some other initialization work.

    Add the following include

    1. #include <lib/sys/inspect/cpp/component.h>

    Change your initialization code to look like the following:

    1. async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
    2. auto component_context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
    3. auto inspector =
    4. std::make_unique<sys::ComponentInspector>(component_context.get());
    5. inspect::Node& root_node = inspector->root();
    6. // ...
    7. loop.Run();

    You are now using Inspect! To add some data and see it in action, try adding the following:

    1. // Important: Make sure to hold on to hello_world_property and don't let it go out of scope.
    2. auto hello_world_property = root_node.CreateStringProperty("hello", "world");

    See Viewing Inspect Data below to view what you are now exporting.

    See Supported Data Types for a full list of data types you can try.

    Want to test your Inspect integration? Include lib/inspect/testing/cpp/inspect.h in your unit test for a full set of matchers. See this example of how it is used.

Read on to learn how Inspect is meant to be used in C++.

Dynamic Value Support

The C++ library has two property creators for lazily inflating Inspect trees at read-time: CreateLazyNode and CreateLazyValues.

Both of these methods take a callback returning a promise for an inspect::Inspector, the only difference is how the dynamic values are stored in the tree.

root->CreateLazyNode(name, callback) creates a child node of root with the given name. The callback returns a promise for an inspect::Inspector whose root node is spliced into the parent hierarchy when read. The example below shows that a child called “lazy” exists with the string property “version” and has an additional child that is called “lazy.”

root->CreateLazyValues(name, callback) works like root->CreateLazyNode(name, callback), except all properties and child nodes on the promised root node are added directly as values to the original root. In the second output of this example, the internal lazy nodes do not appear and their values are flattened into properties on root.

  1. root->CreateLazy{Node,Values}("lazy", [] {
  2. Inspector a;
  3. a.GetRoot().CreateString("version", "1.0", &a);
  4. a.GetRoot().CreateLazy{Node,Values}("lazy", [] {
  5. Inspector b;
  6. b.GetRoot().CreateInt("value", 10, &b);
  7. return fit::make_ok_promise(std::move(b));
  8. }, &a);
  9. return fit::make_ok_promise(std::move(a));
  10. });

Output (CreateLazyNode):

  1. root:
  2. lazy:
  3. version = "1.0"
  4. lazy:
  5. val = 10

Output (CreateLazyValues):

  1. root:
  2. val = 10
  3. version = "1.0"

Warning: It is the developer’s responsibility to ensure that names flattened from multiple lazy value nodes do not conflict. If they do, output behavior is undefined.

The return value of CreateLazy{Node,Values} is a LazyNode that owns the passed callback. The callback is never called once the LazyNode is destroyed. If you destroy a LazyNode concurrently with the execution of a callback, the destroy operation is blocked until the callback returns its promise.

If you want to dynamically expose properties on this, you may simply write the following:

  1. class Employee {
  2. public:
  3. Employee(inspect::Node node) : node_(std::move(node)) {
  4. calls_ = node_.CreateInt("calls", 0);
  5. // Create a lazy node that populates values on its parent
  6. // dynamically.
  7. // Note: The callback will never be called after the LazyNode is
  8. // destroyed, so it is safe to capture "this."
  9. lazy_ = node_.CreateLazyValues("lazy", [this] {
  10. // Create a new Inspector and put any data in it you want.
  11. inspect::Inspector inspector;
  12. // Keep track of the number of times this callback is executed.
  13. // This is safe because the callback is executed without locking
  14. // any state in the parent node.
  15. calls_.Add(1);
  16. // ERROR: You cannot modify the LazyNode from the callback. Doing
  17. // so may deadlock!
  18. // lazy_ = ...
  19. // The value is set to the result of calling a method on "this".
  20. inspector.GetRoot().CreateInt("performance_score",
  21. this->CalculatePerformance(), &inspector);
  22. // Callbacks return a fit::promise<Inspector>, so return a result
  23. // promise containing the value we created.
  24. // You can alternatively return a promise that is completed by
  25. // some asynchronous task.
  26. return fit::make_ok_promise(std::move(inspector));
  27. });
  28. }
  29. private:
  30. inspect::Node node_;
  31. inspect::IntProperty calls_;
  32. inspect::LazyNode lazy_;
  33. };

C++ Library Concepts

Now that you have a root_node you may start building your hierarchy. This section describes some important concepts and patterns to help you get started.

  • A Node may have any number of key/value pairs called Properties.
  • The key for a Value is always a UTF-8 string, the value may be one of the supported types below.
  • A Node may have any number of children, which are also Nodes.

    The code above gives you access to a single node named “root”. hello_world_property is a Property that contains a string value (aptly called a StringProperty).

  • Values and Nodes are created under a parent Node.

    Class Node has creator methods for every type of supported value. hello_world_property was created using CreateStringProperty. You could create a child under the root node by calling root_node.CreateChild("child name"). Note that names must always be UTF-8 strings.

  • Values and Nodes have strict ownership semantics.

    hello_world_property owns the Property. When it is destroyed (goes out of scope) the underlying Property is deleted and no longer present in your component’s Inspect output. This is true for child Nodes as well.

    If you are creating a value that doesn’t need to be modified, use a ValueList to keep them alive until they are no longer needed.

  • Inspection is best-effort.

    Due to space limitations, the Inspect library may be unable to satisfy a Create request. This error is not surfaced to your code: you will receive a Node/Property object for which the methods are no-ops.

  • Pattern: Pass in child Nodes to child objects.

    It is useful to add an inspect::Node argument to the constructors for your own classes. The parent object, which should own its own inspect::Node, may then pass in the result of CreateChild(...) to its children when they are constructed:

    1. class Child {
    2. public:
    3. Child(inspect::Node my_node) : my_node_(std::move(my_node)) {
    4. // Create a string that doesn't change, and emplace it in the ValueList
    5. my_node_.CreateString("version", "1.0", &values_);
    6. // Create metrics and properties on my_node_.
    7. }
    8. private:
    9. inspect::Node my_node_;
    10. inspect::StringProperty some_property_;
    11. inspect::ValueList values_;
    12. // ... more properties and metrics
    13. };
    14. class Parent {
    15. public:
    16. // ...
    17. void AddChild() {
    18. // Note: inspect::UniqueName returns a globally unique name with the specified prefix.
    19. children_.emplace_back(my_node_.CreateChild(inspect::UniqueName("child-")));
    20. }
    21. private:
    22. std::vector<Child> children_;
    23. inspect::Node my_node_;
    24. };
  • {Rust}

    Setup

    This section assumes you are writing an asynchronous component and that some part of your component (typically main.rs) looks similar to this:

    1. #[fasync::run_singlethreaded]
    2. async fn main() -> Result<(), Error> {
    3. ...
    4. let mut fs = ServiceFs::new();
    5. ...
    6. Ok(())
    7. }

    Add the following to your initialization code:

    1. // This creates the root of an inspect tree.
    2. let inspector = component::inspector();
    3. // This serves the inspect Tree to the default path for reading at the standard
    4. // location "/diagnostics/fuchsia.inspect.Tree".
    5. inspector.serve(&mut fs)?;
    6. // This will give you a reference to the root node of the inspect tree.
    7. let root = inspector.root();

    Don’t forget to use fuchsia_inspect::component;!

    Now you can use inspect! For example try the following:

    1. let hello_world_property = root.create_string("hello", "world!");

    See this example for further learning of other types offered by the API.

    To test your inspect code, you can use assert_data_tree:

    1. let inspector = component::inspector();
    2. let root = inspector.root();
    3. let child = root.create_child("child1");
    4. child.record_double("some_property_name", 1.0);
    5. child.record_string("another_property", "example");
    6. let children = inspector.create_child("children");
    7. assert_data_tree!(inspector, root: {
    8. child1: {
    9. some_property_name: 1.0,
    10. another_property: "example",
    11. children: {},
    12. }
    13. });

    Learn more about testing inspect.

    See the docs to learn about other methods offered by the Rust API.

    See Viewing Inspect Data below to view what you are now exporting.

    See Supported Data Types for a full list of data types you can try.

    Rust Library Concepts

    Refer to C++ Library Concepts, as similar concepts apply in Rust.

    The Rust library provides two ways of managing nodes and properties: creation and recording.

    With the create_* methods, the ownership of the property or node object to the caller. When the returned object is dropped, it is removed. For example:

    1. {
    2. let property = root.create_int("name", 1);
    3. }

    In this example, the property went out of scope so a drop on the property is called. Readers won’t see this property.

    With the record_* methods, the lifetime of the node the method is called on is entangled with the resulting property. When the node the method was called is deleted, the recorded property is deleted.

    1. {
    2. let node = root.create_child("name");
    3. {
    4. node.record_uint(2); // no return
    5. }
    6. // The uint property will still be visible to readers.
    7. }

    In this example, neither the name node nor the uint property is visible to readers after node is dropped.

    Dynamic Value Support

    Refer to C++ Dynamic Value Support, as similar concepts apply in Rust.

    Example:

    1. root.create_lazy_{child,values}("lazy", [] {
    2. async move {
    3. let inspector = Inspector::new();
    4. inspector.root().record_string("version", "1.0");
    5. inspector.root().record_lazy_{node,values}("lazy", [] {
    6. let inspector = Inspector::new();
    7. inspector.root().record_int("value", 10);
    8. // `_value`'s drop is called when the function returns, so it will be removed.
    9. // For these situations `record_` is provided.
    10. let _value = inspector.root().create_int("gone", 2);
    11. Ok(inspector)
    12. });
    13. Ok(inspector)
    14. }
    15. .boxed()
    16. });
    17. Output (create_lazy_node):
    18. root:
    19. lazy:
    20. version = "1.0"
    21. lazy:
    22. val = 10
    23. Output (create_lazy_values):
    24. root:
    25. val = 10
    26. version = "1.0"
  • {Dart}

    This example obtains and adds several data types and nested children to the root Inspect node.

    BUILD.gn:

    1. flutter_app("inspect_mod") {
    2. [...]
    3. deps = [
    4. [...]
    5. "//topaz/public/dart/fuchsia_inspect",
    6. [...]
    7. ]
    8. [...]

    ```dart {highlight=”lines:6”} import ‘package:fuchsia_inspect/inspect.dart’ as inspect; […] class RootIntentHandler extends IntentHandler { @override void handleIntent(Intent intent) {

    1. var inspectNode = inspect.Inspect().root;
    2. runApp(InspectExampleApp(inspectNode));

    } }

    1. `inspect_example_app.dart`:
    2. ```dart {highlight="lines:4,7-10,16"}
    3. import 'package:fuchsia_inspect/inspect.dart' as inspect;
    4. class InspectExampleApp extends StatelessWidget {
    5. final inspect.Node _inspectNode;
    6. InspectExampleApp(this._inspectNode) {
    7. _inspectNode.stringProperty('greeting').setValue('Hello World');
    8. _inspectNode.doubleProperty('double down')..setValue(1.23)..add(2);
    9. _inspectNode.intProperty('interesting')..setValue(123)..subtract(5);
    10. _inspectNode.byteDataProperty('bytes').setValue(ByteData(4)..setUint32(0, 0x01020304));
    11. }
    12. @override
    13. Widget build(BuildContext context) {
    14. return MaterialApp(
    15. home: _InspectHomePage(
    16. inspectNode: _inspectNode.child('home-page')),
    17. [...]
    18. }

    You can call delete() on a Node or Property when you’re done with it. Deleting a node deletes everything under it.

    delete() can also be triggered by a Future completing or broadcast Stream closing:

    1. var answerFuture = _answerFinder.getTheAnswer();
    2. var wait = _inspectNode.stringProperty('waiting')..setValue('for a hint');
    3. answerFuture.whenComplete(wait.delete);
    4. stream.listen((_) {}, onDone: node.delete);
    5. // FIDL proxies contain a future that completes when the connection closes:
    6. final _proxy = my_fidl_import.MyServiceProxy();
    7. _proxy.ctrl.whenClosed.whenComplete(node.delete);

Viewing Inspect Data

You can use the [iquery] tool to view the Inspect data you exported from your component by looking through the Hub.

This section assumes you have SSH access to your running Fuchsia system and that you started running your component. We will use the name my_component.cmx as a placeholder for the name of your component.

Find your Inspect endpoint

Try the following:

  1. # This prints all component selectors (monikers in v2 or realm paths in v1) available.
  2. $ iquery list
  3. # This prints all files containing inspect under /hub (this won't show v2 components)
  4. $ iquery list-files /hub
  5. # This filters the above list to only print your component.
  6. $ iquery list-files /hub | grep my_component.cmx
  7. $ iquery list | grep my_component.cmx # TODO(fxbug.dev/45458): allow to pass a manifest filter

Under the listed directories you will see some paths including “system_objects.” This Inspect data is placed there by the Component Runtime itself.

Your component’s endpoint will be listed as <path>/my_component.cmx/<id>/out/diagnostics/root.inspect.

Note: If you followed Dynamic Value Support above, “root.inspect” will be missing.

Read your Inspect data

Use the moniker that list printed above, and run:

  1. $ iquery show 'realm/my_component.cmx'

You can also spcify a node/property using selectors:

  1. $ iquery show 'realm/my_component.cmx:root/path/to/some:property'

Navigate to the out/ directory that was printed above, and run:

  1. $ iquery --recursive fuchsia.inspect.Tree
  2. # Or root.inspect if you are exposing a vmo file directly
  3. # (this is not the case if using inspect libs directly)

Or root.inspect instead of fuchsia.inspect.Tree. Note that this is not the case in general (except on Dart). If you are using the standard inspect libs it’ll be the Tree protocol.

This will print out the following if you followed the suggested steps above:

  1. root:
  2. hello = world

Supported Data Types

Type Description Notes
IntProperty A metric containing a signed 64-bit integer. All Languages
UIntProperty A metric containing an unsigned 64-bit integer. Not supported in Dart
DoubleProperty A metric containing a double floating-point number. All Languages
BoolProperty A metric containing a double floating-point number. All Languages
{Int,Double,UInt}Array An array of metric types, includes typed wrappers for various histograms. Same language support as base metric type
StringProperty A property with a UTF-8 string value. All Languages
ByteVectorProperty A property with an arbitrary byte value. All Languages
Node A node under which metrics, properties, and more nodes may be nested. All Languages
LazyNode Instantiates a complete tree of Nodes dynamically. C++, Rust