Inspect codelab
July 8, 2022 ยท View on GitHub
This document contains the codelab for Inspect in C++, Dart and Rust.
The code is available at:
- //examples/diagnostics/inspect/codelab/cpp.
- //examples/diagnostics/inspect/codelab/rust.
- //examples/diagnostics/inspect/codelab/dart.
This codelab is organized into several parts, each with their own subdirectory. The starting point for the codelab is part 1, and the code for each part contains the solution for the previous parts.
Note: For Rust we also have an ergonomic library with a higher level API: fuchsia-inspect-derive. However, it's recommended to understand the concepts explained in this codelab before using that other library.
When working on this codelab, you may continue adding your solutions to "part_1", or you may skip around by building on the existing solutions.
Prerequisites
Set up your development environment.
This codelab assumes you have completed Getting Started and have:
- A checked out and built Fuchsia tree.
- A device or emulator (
ffx emu) that runs Fuchsia. - A workstation to serve components (
fx serve) to your Fuchsia device or emulator.
To build and run the examples in this codelab, add the following arguments
to your fx set invocation:
Note: Replace core.x64 with your product and board configuration.
-
{C++}
fx set core.x64 \ --with //examples/diagnostics/inspect/codelab/cpp \ --with //examples/diagnostics/inspect/codelab/cpp:tests -
{Rust}
fx set core.x64 \ --with //examples/diagnostics/inspect/codelab/rust \ --with //examples/diagnostics/inspect/codelab/rust:tests -
{Dart}
fx set workstation_eng.x64 --with //examples/diagnostics/inspect/codelab/dart \ --with //examples/diagnostics/inspect/codelab/dart:tests
Part 1: A buggy component
There is a component that serves a protocol called Reverser:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/fidl/reverser.test.fidl" region_tag="reverser_fidl" adjust_indentation="auto" %}
This protocol has a single method, called "Reverse," that simply reverses any string passed to it. An implementation of the protocol is provided, but it has a critical bug. The bug makes clients who attempt to call the Reverse method see that their call hangs indefinitely. It is up to you to fix this bug.
Run the component
There is a client application that will launch the Reverser component and send the rest of its command line arguments as strings to Reverse:
-
See usage
-
{C++}
Depending on the part of the codelab you wish to run, you'd launch the
client_icomponent, whereiis a number in range [1, 5]. For example, to launch the client talking to the reverser from part 2 of the codelab:ffx component run fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_2.cm -
{Rust}
Depending on the part of the codelab you wish to run, you'd launch the
client_icomponent, whereiis a number in range [1, 5]. For example, to launch the client talking to the reverser from part 2 of the codelab:ffx component run fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_2.cm -
{Dart}
fx run fuchsia-pkg://fuchsia.com/inspect-dart-codelab-client#meta/inspect-dart-codelab-client.cmx
-
-
Run part 1 code, and reverse the string "Hello"
-
{C++}
To specify just the single string "Hello" modify the
program.argssection of the common.shard.cml, build and run the following:ffx component run fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cmTo see the command output take a look at the logs:
ffx log --tags inspect_cpp_codelabThis command prints some output containing errors.
-
{Rust}
To specify just the single string "Hello" modify the
program.argssection of the common.shard.cml, build and run the following:ffx component run fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cmTo see the command output take a look at the logs:
ffx log --tags inspect_rust_codelabWe see in the logs that the component got the "Hello" as input, but we don't see the correct reversed output.
-
{Dart}
fx run fuchsia-pkg://fuchsia.com/inspect-dart-codelab-client#meta/inspect-dart-codelab-client.cmx 1 Hello
As you can see in the log the reverser doesn't work properly.
-
-
Try running the client with more arguments:
-
{C++}
Add the string "World" to the
program.argssection of the common.shard.cml:{ program: { args: [ "Hello", "World", ], }, }Build and run the following:
ffx component run --recreate fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm -
{Rust}
Add the string "World" to the
program.argssection of the common.shard.cml:{ program: { args: [ "Hello", "World", ], }, }Build and run the following:
ffx component run --recreate fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm -
{Dart}
fx run fuchsia-pkg://fuchsia.com/inspect-dart-codelab-client#meta/inspect-dart-codelab-client.cmx 1 Hello WorldThis command also prints no outputs.
We can see that the component printed the first input, but we don't see the expected output and also no second input.
-
You are now ready to look through the code to troubleshoot the issue.
Look through the code
Now that you can reproduce the problem, take a look at what the client is doing:
-
{C++}
In the client main:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/client/main.cc" region_tag="reverse_loop" adjust_indentation="auto" %} -
{Rust}
In the client main:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/client/src/main.rs" region_tag="reverse_loop" adjust_indentation="auto" %} -
{Dart}
In the client main:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/client/lib/main.dart" region_tag="reverse_loop" adjust_indentation="auto" %}
In this code snippet, the client calls the Reverse method but never
seems to get a response. There doesn't seem to be an error message
or output.
Take a look at the server code for this part of the codelab. There is a lot of standard component setup:
-
{C++}
In the part 1 main:
-
Logging initialization
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_1/main.cc" region_tag="init_logger" adjust_indentation="auto" %} -
Creating an asynchronous executor
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_1/main.cc" region_tag="async_executor" adjust_indentation="auto" %} -
Serving a public service
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_1/main.cc" region_tag="serve_outgoing" adjust_indentation="auto" %}
-
-
{Rust}
In the part 1 main:
-
Logging initialization
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_1/src/main.rs" region_tag="init_logger" adjust_indentation="auto" %} -
ServiceFs initialization
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_1/src/main.rs" region_tag="servicefs_init" adjust_indentation="auto" %} -
ServiceFs collection
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_1/src/main.rs" region_tag="servicefs_collect" adjust_indentation="auto" %} -
Serving a public service
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_1/src/main.rs" region_tag="serve_service" adjust_indentation="auto" %}
-
-
{Dart}
In the part 1 main:
-
Logging initialization
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_1/lib/main.dart" region_tag="init_logger" adjust_indentation="auto" %} -
Serving a public service
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_1/lib/main.dart" region_tag="serve_service" adjust_indentation="auto" %}
-
See what the reverser definition is:
-
{C++}
In reverser.h:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_1/reverser.h" region_tag="reverser_h" adjust_indentation="auto" %}This class implements the
Reverserprotocol. A helper method calledCreateDefaultHandlerconstructs anInterfaceRequestHandlerthat creates newReversers for incoming requests. -
{Rust}
In reverser.rs:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_1/src/reverser.rs" region_tag="reverser_def" adjust_indentation="auto" %}This struct serves the
Reverserprotocol. TheReverserServerFactory(will make more sense later) constructs aReverserServerwhen a new connection toReverseris established.
-
{Dart}
In reverser.dart:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_1/lib/reverser.dart" region_tag="reverser_impl" adjust_indentation="auto" %}This class implements the
Reverserprotocol. A helper method calledgetDefaultBinderreturns a closure that creates newReversers for incoming requests.
Add Inspect
Now that you know the code structure, you can start to instrument the code with Inspect to find the problem.
Note: Inspect is a powerful instrumentation feature for Fuchsia Components. You can expose structured information about the component's state to diagnose the problem.
You may have previously debugged programs by printing or logging. While this is often effective, asynchronous Components that run persistently often output numerous logs about their internal state over time. This codelab shows how Inspect provides snapshots of your component's current state without needing to dig through logs.
-
Include Inspect dependencies:
-
{C++}
In BUILD.gn:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/BUILD.gn" region_tag="part_1_solution_build_dep" adjust_indentation="auto" %} -
{Rust}
In BUILD.gn in
depsunderrustc_binary("bin"):{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/BUILD.gn" region_tag="part_1_solution_build_dep" adjust_indentation="auto" %} -
{Dart}
In BUILD.gn in
depsunderdart_library("lib")anddart_app("bin"):{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/BUILD.gn" region_tag="part_1_solution_build_dep" adjust_indentation="auto" %}
-
-
Initialize Inspect:
-
{C++}
In main.cc:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/main.cc" region_tag="part_1_include_inspect" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/main.cc" region_tag="part_1_init_inspect" adjust_indentation="auto" %} -
{Rust}
In main.rs:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/main.rs" region_tag="part_1_use_inspect" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/main.rs" region_tag="part_1_serve_inspect" adjust_indentation="auto" %} -
{Dart}
In main.dart:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/main.dart" region_tag="part_1_import_inspect" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/main.dart" region_tag="part_1_init_inspect" adjust_indentation="auto" %}
You are now using Inspect.
-
-
Add a simple "version" property to show which version is running:
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/main.cc" region_tag="part_1_write_version" adjust_indentation="auto" %}This snippet does the following:
-
Obtain the "root" node of the Inspect hierarchy.
The Inspect hierarchy for your component consists of a tree of Nodes, each of which contains any number of properties.
-
Create a new property using
CreateString.This adds a new
StringPropertyon the root. ThisStringPropertyis called "version", and its value is "part2". We're going to set our property to "part1". -
Emplace the new property in the inspector.
The lifetime of a property is tied to an object returned by
Create, and destroying the object causes the property to disappear. The optional third parameter emplaces the new property ininspectorrather than return it. As a result, the new property lives as long as the inspector itself (the entire execution of the component).
-
-
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/main.rs" region_tag="part_1_write_version" adjust_indentation="auto" %}This snippet does the following:
-
Obtain the "root" node of the Inspect hierarchy.
The Inspect hierarchy for your component consists of a tree of Nodes, each of which contains any number of properties.
-
Create a new property using
record_string.This adds a new
StringPropertyon the root. ThisStringPropertyis called "version", and its value is "part2". We're going to set our property to "part1". -
It records it in the root node.
The usual way of creating properties is through
create_*methods on nodes. The lifetime of a property created with these methods is tied to the object returned and destroying the object causes the property to disappear. The library provides convenience methodsrecord_*that perform creation of a property and tie the property lifetime to the node on which the method was called. As a result, the new property lives as long as the node itself (in this case, as long as the root node, so the entire execution of the component).
-
-
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/main.dart" region_tag="part_1_write_version" adjust_indentation="auto" %}This snippet does the following:
-
Obtain the "root" node of the Inspect hierarchy.
The Inspect hierarchy for your component consists of a tree of Nodes, each of which contains any number of properties.
-
Create a new property using
stringProperty(...).setValue(...).This adds a new
StringPropertyon the root. ThisStringPropertyis called "version", and its value is "part2". We're going to set our property to "part1". -
It records it in the root node.
The lifetime of a property is tied to the lifetime of the node where it was created (in this case root, so the lifetime of the component). To delete the property one would have to call
delete()on it.
-
-
Reading Inspect data
Now that you have added Inspect to your component, you can read what it says:
-
Rebuild and update the target system
fx build && fx ota -
Run the client:
-
{C++}
ffx component run --recreate fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm ffx log --tags inspect_cpp_codelab -
{Rust}
ffx component run --recreate fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm ffx log --tags inspect_rust_codelab -
{Dart}
fx run fuchsia-pkg://fuchsia.com/inspect-dart-codelab-client#meta/inspect-dart-codelab-client.cmx 1 Hello
-
-
Use
ffx inspectto view your output:ffx inspect showThis dumps all of the Inspect data for the entire system, which may be a lot of data.
-
Since
ffx inspectsupports glob matching, run:-
{C++}
$ ffx inspect show 'core/ffx-laboratory\:client_part_1/reverser' # or `ffx inspect show --manifest inspect_cpp_codelab` metadata: filename = fuchsia.inspect.Tree component_url = fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/part_1.cm timestamp = 4728864898476 payload: root: version = part1 -
{Rust}
$ ffx inspect show 'core/ffx-laboratory\:client_part_1/reverser' # or `ffx inspect show --manifest inspect_rust_codelab` metadata: filename = fuchsia.inspect.Tree component_url = fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/part_1.cm timestamp = 4728864898476 payload: root: version = part1 -
{Dart}
$ ffx inspect show 'codelab_*/inspect_dart_codelab_part_1.cmx' # or `ffx inspect show --manifest inspect_dart_codelab` metadata: filename = fuchsia.inspect.Tree component_url = fuchsia-pkg://fuchsia.com/inspect_dart_codelab#meta/inspect_dart_codelab_part_1.cmx timestamp = 4728864898476 payload: root: version = part1
-
-
You can also view the output as JSON:
-
{C++}
$ ffx --machine json-pretty inspect show 'core/ffx-laboratory\:client_part_1/reverser' [ { "data_source": "Inspect", "metadata": { "errors": null, "filename": "fuchsia.inspect.Tree", "component_url": "fuchsia-pkg://fuchsia.com/inspect_pp_codelab#meta/part_1.cm", "timestamp": 5031116776282 }, "moniker": "core/ffx-laboratory\\:client_part_5/reverser", "payload": { "root": { "version": "part1", }, "version": 1 } ] -
{Rust}
$ ffx --machine json-pretty inspect show 'core/ffx-laboratory\:client_part_1/reverser' [ { "data_source": "Inspect", "metadata": { "errors": null, "filename": "fuchsia.inspect.Tree", "component_url": "fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/part_1.cm", "timestamp": 5031116776282 }, "moniker": "core/ffx-laboratory\\:client_part_5/reverser", "payload": { "root": { "version": "part1", }, "version": 1 } ] -
{Dart}
$ ffx --machine json-pretty inspect show 'codelab_*/inspect_dart_codelab_part_1.cmx' [ { "data_source": "Inspect", "metadata": { "errors": null, "filename": "fuchsia.inspect.Tree", "component_url": "fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/inspect_dart_codelab_part_1.cmx", "timestamp": 5031116776282 }, "moniker": "codelab_1234/inspect_dart_codelab_part_1.cmx", "payload": { "root": { "version": "part1", }, "version": 1 } ]
-
Instrumenting the code to find the bug
Now that you have initialized Inspect and know how to read data, you are ready to instrument your code and uncover the bug.
The previous output shows you how the component is actually running and that the component is not hanging completely. Otherwise the Inspect read would hang.
Add new information per-connection to observe if the connection is even being handled by your component.
-
Add a new child to your root node to contain statistics about the
reverserservice:-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/main.cc" region_tag="part_1_new_child" adjust_indentation="auto" %} -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/main.rs" region_tag="part_1_new_child" adjust_indentation="auto" %} -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/main.dart" region_tag="part_1_new_child" adjust_indentation="auto" %}
-
-
Update your server to accept this node:
-
{C++}
Update the definition of
CreateDefaultHandlerin reverser.h and [reverser.cc][part1-reverser-cc]:{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/reverser.h" region_tag="part_1_include" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/reverser.cc" region_tag="part_1_update_server" adjust_indentation="auto" %} -
{Rust}
Update
ReverserServerFactory::newto accept this node in reverser.rs:{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/reverser.rs" region_tag="part_1_use" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/reverser.rs" region_tag="part_1_update_reverser" adjust_indentation="auto" %} -
{Dart}
Update the definition of
getDefaultBinderin reverser.dart and [reverser.cc][part1-reverser-cc]:{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/reverser.dart" region_tag="part_1_import" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/reverser.dart" region_tag="part_1_update_reverser" adjust_indentation="auto" %}
-
-
Add a property to keep track of the number of connections:
Note: Nesting related data under a child is a powerful feature of Inspect.
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/reverser.cc" region_tag="part_1_add_connection_count" adjust_indentation="auto" %}Note:
nodeis moved into the handler so that it is not dropped and deleted from the output. -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_3/src/reverser.rs" region_tag="part_1_add_connection_count" adjust_indentation="auto" %}Note:
nodeis moved into the handler so that it is not dropped and deleted from the output.Note:
nodeis kept in ReverserServerFactory so that it is not dropped and deleted from the output together with all the nodes and properties nested under it. If the compiler shows an error about dead code, try renamingnodeto_node, since the node needs to continue existing despite not being read. In the following steps, the example callsself.node, so the compiler will stop complaining! -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/reverser.dart" region_tag="part_1_add_connection_count" adjust_indentation="auto" %}
This snippet demonstrates creating a new
UintProperty(containing a 64 bit unsigned int) calledconnection_countand setting it to 0. In the handler (which runs for each connection), the property is incremented by 1. -
-
Rebuild, re-run your component and then run
ffx inspect:-
{C++}
$ ffx --machine json-pretty inspect show --manifest inspect_cpp_codelab -
{Rust}
$ ffx --machine json-pretty inspect show --manifest inspect_rust_codelab -
{Dart}
$ ffx --machine json-pretty inspect show --manifest inspect_dart_codelab_part_1
You should now see:
... "payload": { "root": { "version": "part1", "reverser_service": { "connection_count": 1, } } } -
The output above demonstrates that the client successfully connected to the service, so the hanging problem must be caused by the Reverser implementation itself. In particular, it will be helpful to know:
-
If the connection is still open while the client is hanging.
-
If the
Reversemethod was called.
Exercise: Create a child node for each connection, and record "request_count" inside the Reverser.
-
Hint: There is a utility function for generating unique names:
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/reverser.cc" region_tag="part_1_connection_child" adjust_indentation="auto" %} -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/reverser.rs" region_tag="part_1_connection_child" adjust_indentation="auto" %} -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/reverser.dart" region_tag="part_1_connection_child" adjust_indentation="auto" %}
This will create unique names starting with "connection".
-
-
{C++}
Hint: You will find it helpful to create a constructor for Reverser that takes
inspect::Node. Part 3 of this codelab explains why this is a useful pattern. -
{Rust}
Hint: You will find it helpful to create a constructor for
ReverserServerthat takesinspect::Nodefor the same reason as we did forReverserServerFactory. -
{Dart}
Hint: You will find it helpful to create a constructor for
ReverserImplthat takesinspect.Node. Part 3 of this codelab explains why this is a useful pattern.
-
Hint: You will need to create a member on Reverser to hold the
request_countproperty. Its type will beinspect::UintProperty. -
Follow up: Does request count give you all of the information you need? Add
response_countas well. -
Advanced: Can you add a count of all requests on all connections? The Reverser objects must share some state. You may find it helpful to refactor arguments to Reverser into a separate struct (See solution in part 2 for this approach).
After completing this exercise and running ffx inspect, you should see something like this:
...
"payload": {
"root": {
"version": "part1",
"reverser_service": {
"connection_count": 1,
"connection0": {
"request_count": 1,
}
}
}
}
The output above shows that the connection is still open and it received one request.
-
{C++}
If you added "response_count" as well, you may have noticed the bug. The
Reversemethod receives acallback, but it is never called with the value ofoutput. -
{Rust}
If you added "response_count" as well, you may have noticed the bug. The
Reversemethod receives aresponder, but it is never called with the value ofresult. -
{Dart}
If you added "response_count" as well, you may have noticed the bug. The
reversemethod receives never returns the value ofresult.
-
Send the response:
-
{C++}
// At the end of Reverser::Reverse {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/reverser.cc" region_tag="part_1_callback" adjust_indentation="auto" %} -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/reverser.rs" region_tag="part_1_respond" adjust_indentation="auto" %} -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/reverser.dart" region_tag="part_1_result" adjust_indentation="auto" %}
-
-
Run the client again:
-
{C++}
ffx component run --recreate fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm Creating component instance: client_part_1 ffx log --tags inspect_cpp_codelab [00039.129068][39163][39165][inspect_cpp_codelab, client] INFO: Input: Hello [00039.194151][39163][39165][inspect_cpp_codelab, client] INFO: Output: olleH [00039.194170][39163][39165][inspect_cpp_codelab, client] INFO: Input: World [00039.194402][39163][39165][inspect_cpp_codelab, client] INFO: Output: dlroW [00039.194407][39163][39165][inspect_cpp_codelab, client] INFO: Done reversing! Please use `ffx component stop` -
{Rust}
ffx component run --recreate fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm Creating component instance: client_part_1 ffx log --tags inspect_rust_codelab [00039.129068][39163][39165][inspect_rust_codelab, client] INFO: Input: Hello [00039.194151][39163][39165][inspect_rust_codelab, client] INFO: Output: olleH [00039.194170][39163][39165][inspect_rust_codelab, client] INFO: Input: World [00039.194402][39163][39165][inspect_rust_codelab, client] INFO: Output: dlroW [00039.194407][39163][39165][inspect_rust_codelab, client] INFO: Done reversing! Please use `ffx component stop` -
{Dart}
fx run fuchsia-pkg://fuchsia.com/inspect-dart-codelab-client#meta/inspect-dart-codelab-client.cmx 1 hello Input: hello Output: olleh Done. Press Ctrl+C to exit
The component continues to run until you execute
ffx component stopfor the Rust/C++ component or until Ctrl+C is pressed for the Dart component. As long as the component runs you can runffx inspectand observe your output. -
This concludes part 1. You may commit your changes so far:
git commit -am "solution to part 1"
Part 2: Diagnosing inter-component problems {#part-2}
Note: All links and examples in this section refer to "part_2" code. If you are following along, you may continue using "part_1."
You received a bug report. The "FizzBuzz" team is saying they are not receiving data from your component.
In addition to serving the Reverser protocol, the component also reaches out to the "FizzBuzz" service and prints the response:
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_1/main.cc" region_tag="fizzbuzz_connect" adjust_indentation="auto" %} -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_1/src/main.rs" region_tag="fizzbuzz_connect" adjust_indentation="auto" %} -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_1/lib/main.dart" region_tag="connect_fizzbuzz" adjust_indentation="auto" %}
If you see the logs, you will see that this log is never printed.
-
{C++}
ffx log --tags inspect_cpp_codelab -
{Rust}
ffx log --tags inspect_rust_codelab -
{Dart}
ffx log --tags inspect_dart_codelab_part_2
You will need to diagnose and solve this problem.
Diagnose the issue with Inspect
-
Run the component to see what is happening:
Note: Replace 2 with 1 if you are continuing from part 1.
-
{C++}
ffx component run fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_2.cm -
{Rust}
ffx component run fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_2.cm -
{Dart}
$ fx run fuchsia-pkg://fuchsia.com/inspect-dart-codelab-client#meta/inspect-dart-codelab-client.cmx 2 hello
Fortunately the FizzBuzz team instrumented their component using Inspect.
-
-
Read the FizzBuzz Inspect data using
ffx inspectas before, you get:"payload": { "root": { "fizzbuzz_service": { "closed_connection_count": 0, "incoming_connection_count": 0, "request_count": 0, ...This output confirms that FizzBuzz is not receiving any connections.
Note: You can also use ffx inspect show "core/ffx-laboratory\:client_part_2/*".
-
Add Inspect to identify the problem:
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_2/main.cc" region_tag="instrument_fizzbuzz" adjust_indentation="auto" %} -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/main.rs" region_tag="instrument_fizzbuzz" adjust_indentation="auto" %} -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_2/lib/main.dart" region_tag="instrument_fizzbuzz" adjust_indentation="auto" %}
-
Exercise: Add Inspect to the FizzBuzz connection to identify the problem
- Hint: Use the snippet above as a starting point, it provides an error handler for the connection attempt.
-
{C++}
Follow up: Can you store the status somewhere? You can convert it to a string using
zx_status_get_string(status).Advanced:
inspectorhas a method calledHealth()that announces overall health status in a special location. Since our service is not healthy unless it can connect to FizzBuzz, can you incorporate this:/* "fuchsia.inspect.Health": { "status": "STARTING_UP" } */ inspector.Health().StartingUp(); /* "fuchsia.inspect.Health": { "status": "OK" } */ inspector.Health().Ok(); /* "fuchsia.inspect.Health": { "status": "UNHEALTHY", "message": "Something went wrong!" } */ inspector.Health().Unhealthy("Something went wrong!"); -
{Rust}
Advanced:
fuchsia_inspect::componenthas a function calledhealth()that returns an object that announces overall health status in a special location (a node child of the root of the inspect tree). Since our service is not healthy unless it can connect to FizzBuzz, can you incorporate this:/* "fuchsia.inspect.Health": { "status": "STARTING_UP" } */ fuchsia_inspect::component::health().set_starting_up(); /* "fuchsia.inspect.Health": { "status": "OK" } */ fuchsia_inspect::component::health().set_ok(); /* "fuchsia.inspect.Health": { "status": "UNHEALTHY", "message": "Something went wrong!" } */ fuchsia_inspect::component::health().set_unhealthy("something went wrong!"); -
{Dart}
Advanced:
fuchsia_inspect::Inspecthas a getter calledhealththat returns an object that announces overall health status in a special location (a node child of the root of the inspect tree). Since our service is not healthy unless it can connect to FizzBuzz, can you incorporate this:/* "fuchsia.inspect.Health": { "status": "STARTING_UP" } */ inspect.Inspect().health.setStartingUp(); /* "fuchsia.inspect.Health": { "status": "OK" } */ inspect.Inspect().health.setOk(); /* "fuchsia.inspect.Health": { "status": "UNHEALTHY", "message": "Something went wrong!" } */ inspect.Inspect().health.setUnhealthy('Something went wrong!');
Once you complete this exercise, you should see that the connection error handler is being called with a "not found" error. Inspect output showed that FizzBuzz is running, so maybe something is misconfigured. Unfortunately not everything uses Inspect (yet!) so look at the logs:
-
{C++}
$ ffx log --filter FizzBuzz ... ... No capability available at path /svc/fuchsia.examples.inspect.FizzBuzz for component /core/ffx-laboratory:client_part_2/reverser, verify the component has the proper `use` declaration. ... -
{Rust}
$ ffx log --filter FizzBuzz ... ... No capability available at path /svc/fuchsia.examples.inspect.FizzBuzz for component /core/ffx-laboratory:client_part_2/reverser, verify the component has the proper `use` declaration. ... -
{Dart}
$ ffx log --filter FizzBuzz ... ... Component fuchsia-pkg://fuchsia.com/inspect_dart_codelab_part_2.cmx is not allowed to connect to fuchsia.examples.inspect.FizzBuzz...
Sandboxing errors are a common pitfall that are sometimes difficult to uncover.
Note: While you could have looked at the logs from the beginning to find the problem, the log output for the system can be extremely verbose. The particular log that you are looking for was a kernel log from the framework, which is additionally difficult to test for.
Looking at part2 meta, you can see it is missing the service:
-
{C++}
Add a
useentry forFizzbuzzto part_2/metause: [ { protocol: "fuchsia.examples.inspect.FizzBuzz" }, ], -
{Rust}
Add a
useentry forFizzbuzzto part_2/metause: [ { protocol: "fuchsia.examples.inspect.FizzBuzz" }, ], -
{Dart}
Find the sandbox meta in part_2/meta
"sandbox": { "services": [ "fuchsia.logger.LogSink" ] }
After you added "fuchsia.examples.inspect.FizzBuzz", rebuild, and run again. You should now see FizzBuzz in the logs and an OK status:
-
{C++}
$ ffx log --tags inspect_cpp_codelab [inspect_cpp_codelab, part2] INFO: main.cc(57): Got FizzBuzz: 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz -
{Rust}
$ ffx log --tags inspect_rust_codelab [inspect_rust_codelab, part2] INFO: main.rs(52): Got FizzBuzz: 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz -
{Dart}
$ ffx log --tags inspect_dart_codelab [inspect_dart_codelab, part2] INFO: main.dart(35): Got FizzBuzz: 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz
This concludes Part 2.
You can now commit your solution:
git commit -am "solution for part 2"
Part 3: Unit Testing for Inspect {#part-3}
Note: All links and examples in this section refer to "part_3" code. If you are following along, you may continue using the part you started with.
All code on Fuchsia should be tested, and this applies to Inspect data as well.
While Inspect data is not required to be tested in general, you need to test Inspect data that is depended upon by other tools such as Triage or Feedback.
Reverser has a basic unit test. Run it:
-
{C++}
The unit tests is located in reverser_unittests.cc.
fx test inspect_cpp_codelab_unittests -
{Rust}
The unit test is located in reverser.rs > mod tests.
fx test inspect_rust_codelab_unittests -
{Dart}
The unit test is located in reverser_test.dart.
fx test inspect_dart_codelab_part_3_unittests
Note: This runs unit tests for all parts of this codelab.
The unit test ensures that Reverser works properly (and doesn't hang!), but it does not check that the Inspect output is as expected.
Note: If you are following along from part_1, you will need to uncomment some lines in the part_1 unit test and pass default values for the Inspect properties to your Reverser.
Passing Nodes into constructors is a form of Dependency Injection, which allows you to pass in test versions of dependencies to check their state.
The code to open a Reverser looks like the following:
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_3/reverser_unittests.cc" region_tag="open_reverser" adjust_indentation="auto" %} // Alternatively binding_set_.AddBinding(std::make_unique<Reverser>(inspect::Node()), ptr.NewRequest()); -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_2/src/reverser.rs" region_tag="open_reverser" adjust_indentation="auto" %} -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_3/test/reverser_test.dart" region_tag="open_reverser" adjust_indentation="auto" %}
A default version of the Inspect Node is passed into the Reverser. This allows the reverser code to run properly in tests, but it does not support asserting on Inspect output.
-
{C++}
Exercise: Change
OpenReverserto take the dependency for Reverser as an argument and use it when constructing Reverser.-
Hint: Create an
inspect::Inspectorin the test function. You can get the root usinginspector.GetRoot(). -
Hint: You will need to create a child on the root to pass in to
OpenReverser.
-
-
{Rust}
Exercise: Change
open_reverserto take the dependency for aReverserServerFactoryas an argument and use it when constructing Reverser.-
Hint: Create a
fuchsia_inspect::Inspectorin the test function. You can get the root usinginspector.root(). -
Note: Do not use
component::inspector()directly in your tests, this creates a static inspector that will be alive in all your tests and can lead to flakes or unexpected behaviors. For unit tests, alwas prefer to use a newfuchsia_inspect::Inspector -
Hint: You will need to create a child on the root to pass in to
ReverserServerFactory::new.
-
-
{Dart}
Exercise: Change
openReverserto take the dependency for aninspect.Nodeas an argument and use it when constructing Reverser.-
Hint: Use
inspect.Inspect.forTestingandFakeVmoHolderto create an Inspect object without fuchsia dependencies to run your test on host. -
Hint: You will need to create a child on the root to pass in to
openReverser.
-
Follow up: Create multiple reverser connections and test them independently.
Following this exercise, your unit test will set real values in an Inspect hierarchy.
Add code to test the output in Inspect:
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_4/reverser_unittests.cc" region_tag="include_testing" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_4/reverser_unittests.cc" region_tag="get_hierarchy" adjust_indentation="auto" %}Note: If you use the LazyNode or LazyValues features, you will need to use inspect::ReadFromInspector and run the returned fpromise::promise to completion. See the solution to this part for an example.
The snippet above reads the underlying virtual memory object (VMO) containing Inspect data and parses it into a readable hierarchy.
You can now read individual properties and children as follows:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_4/reverser_unittests.cc" region_tag="assertions" adjust_indentation="auto" %} -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_4/src/reverser.rs" region_tag="include_testing" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_4/src/reverser.rs" region_tag="test_inspector" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_4/src/reverser.rs" region_tag="assert_tree" adjust_indentation="auto" %} -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_4/test/reverser_test.dart" region_tag="reverser_test" adjust_indentation="auto" %}The
VmoMatcheris a convenient utility for testing inspect integrations. It allows to assert existing properties and children and missing ones, among other features.
The snippets above read a snapshot from the underlying virtual memory object (VMO) containing Inspect data and parses it into a readable hierarchy.
Exercise: Add assertions for the rest of your Inspect data.
This concludes Part 3.
You may commit your changes:
git commit -am "solution to part 3"
Part 4: Integration Testing for Inspect
Note: All links and examples in this section refer to "part_4" code. If you are following along, you may continue using the part you started with.
Integration testing is an important part of the software development workflow for Fuchsia. Integration tests allow you to observe the behavior of your actual component when it runs on the system.
Running integration tests
You can run the integration tests for the codelab as follows:
-
{C++}
$ fx test inspect_cpp_codelab_integration_tests -
{Rust}
$ fx test inspect_rust_codelab_integration_tests -
{Dart}
$ fx test inspect_dart_codelab_part_4_integration_tests
Note: This runs integration tests for all parts of this codelab.
View the code
Look at how the integration test is setup:
-
View the component manifest for the integration test:
-
{C++}
Find the component manifest (cml) in part_4/meta
{ ... use: [ { protocol: "fuchsia.diagnostics.ArchiveAccessor" }, ] }This file uses the protocol
fuchsia.diagnostics.ArchiveAccessorfrom parent. This protocol is available to all tests to enable to read diagnostics about all components under the test realm. -
{Rust}
Find the component manifest (cml) in part_4/meta
{ ... use: [ { protocol: "fuchsia.diagnostics.ArchiveAccessor" }, ] }This file uses the protocol
fuchsia.diagnostics.ArchiveAccessorfrom parent. This protocol is available to all tests to enable to read diagnostics about all components under the test realm. -
{Dart}
Find the component manifest (cmx) in dart/part_4/meta
{ "facets": { "fuchsia.test": { "injected-services": { "fuchsia.diagnostics.ArchiveAccessor": "fuchsia-pkg://fuchsia.com/archivist-for-embedding#meta/archivist-for-embedding.cmx" } } }, "program": { "binary": "test/integration_part_4" }, "sandbox": { "services": [ "fuchsia.logger.LogSink", "fuchsia.sys.Loader", "fuchsia.sys.Environment" ... ] } }The important parts of this file are:
-
Injected services: The
fuchsia.testfacet includes configuration for tests. In this file, thefuchsia.diagnostics.ArchiveAccessorservice is injected and points to a component calledarchivist-for-embedding.cmx. The Archivist collects information from all components in your test environment and provides a reading interface. You can use this information to look at your Inspect output. -
Sandbox services: Integration tests need to start other components in the test environment and wire them up. For this you need
fuchsia.sys.Loaderandfuchsia.sys.Environment.
-
-
-
Look at the integration test itself. The individual test cases are fairly straightforward:
-
{C++}
Locate the integration test in part4/tests/integration_test.cc.
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_4/tests/integration_test.cc" region_tag="integration_test" adjust_indentation="auto" %}StartComponentAndConnectis responsible for creating a new test environment and starting the codelab component inside of it. Theinclude_fizzbuzz_serviceoption instructs the method to optionally include FizzBuzz. This feature tests that your Inspect output is as expected in case it fails to connect to FizzBuzz as in Part 2. -
{Rust}
Locate the integration test in part4/tests/integration_test.rs.
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_4/tests/integration_test.rs" region_tag="integration_test" adjust_indentation="auto" %}IntegrationTest::startis responsible for creating a new test environment and starting the codelab component inside of it. Theinclude_fizzbuzzoption instructs the method to optionally launch the FizzBuzz component. This feature tests that your Inspect output is as expected in case it fails to connect to FizzBuzz as in Part 2. -
{Dart}
Locate the integration test in part_4/test/integration_test.dart.
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_4/test/integration_test.dart" region_tag="integration_test" adjust_indentation="auto" %}env.create()is responsible for creating a new test environment.startComponentAndConnectlaunches the reverser component and optionally launches the FizzBuzz component. This feature tests that the Inspect output is as expected in case it fails to connect to FizzBuzz as in Part 2.
-
-
Add the following method to your test fixture to read from the ArchiveAccessor service:
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_5/tests/integration_test.cc" region_tag="include_json" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_5/tests/integration_test.cc" region_tag="get_inspect" adjust_indentation="auto" %} -
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_5/tests/integration_test.rs" region_tag="include_test_stuff" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_5/tests/integration_test.rs" region_tag="get_inspect" adjust_indentation="auto" %} -
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_5/test/integration_test.dart" region_tag="include_test_stuff" adjust_indentation="auto" %} {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_5/test/integration_test.dart" region_tag="get_inspect" adjust_indentation="auto" %}
-
-
Exercise. Use the returned data in your tests and add assertions to the returned data:
-
{C++}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_5/tests/integration_test.cc" region_tag="parse_result" adjust_indentation="auto" %}Add assertions on the returned JSON data.
-
Hint: It may help to print the JSON output to view the schema.
-
Hint: You can read values by path as follows:
-
Hint: You can
EXPECT_EQby passing in the expected value as a rapidjson::Value:rapidjson::Value("OK").
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/cpp/part_5/tests/integration_test.cc" region_tag="hint_get_value" adjust_indentation="auto" %} -
-
{Rust}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/rust/part_5/tests/integration_test.rs" region_tag="result_hierarchy" adjust_indentation="auto" %}Add assertions on the returned
DiagnosticsHierarchy.- Hint: It may help to print the JSON output to view the schema.
-
{Dart}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/codelab/dart/part_5/test/integration_test.dart" region_tag="result_hierarchy" adjust_indentation="auto" %}Add assertions on the returned Map data.
- Hint: It may help to print the JSON output to view the schema.
-
Your integration test will now ensure your inspect output is correct.
This concludes Part 4.
You may commit your solution:
git commit -am "solution to part 4"
Part 5: Feedback Selectors
This section is under construction.
-
TODO: Writing a feedback selector and adding tests to your integration test.
-
TODO: Selectors for Feedback and other pipelines