In this codelab, you will learn to use the Inspect (Rust and C++) to publish diagnostic information from your programs, and use Inspect information to debug your programs.
In this codelab, you're going to modify programs to output Inspect data. You will learn:
How to include the Inspect libraries.
How to initialize Inspect in your components.
How to solve real bugs by writing and reading Inspect data.
How to read Inspect data to verify that your program is doing what you want.
What is Inspect?
Inspect allows Fuchsia Components to expose structured, hierarchical information about their current state.
Learn more about Inspect in the Fuchsia Component Inspection documentation.
What benefits does Inspect provide?
Component Inspection supports many use cases, including:
Debugging
View Inspect data from your running component to identify problems. For example, you can learn if your component is currently connected to a dependency.
Monitoring system health
Inspect data provides insight into overall system state. For example, you can learn why your system is not connected to the internet.
Gathering usage or performance statistics
You can read Inspect data from multiple components at the same time to understand system performance. For example, you can see the list of incoming connections to your component along with your component's memory usage.
What kind of information can I store in Inspect?
You determine the structure and content of the data you expose in Inspect. Some examples include:
- The number of open WiFi connections.
- The number of requests that the program has served.
- The number of errors that a parser has encountered.
- The contents of a data structure.
API Reference
What you’ll need
- Basic knowledge of Rust, or C++.
- Access to a Fuchsia source tree you can execute build commands in.
Source code
The code is available at:
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.
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:
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
Part 1: A buggy component
There is a component that serves a protocol called Reverser:
// Implementation of a string reverser.
@discoverable
closed protocol Reverser {
// Returns the input string reversed character-by-character.
strict Reverse(struct {
input string:1024;
}) -> (struct {
response string:1024;
});
};
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
Depending on the part of the codelab you wish to run, you'd launch the
client_i
component, wherei
is a number in range [1, 5]. For example, to launch the client talking to the reverser from part 2 of the codelab:C++
ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_2.cm
Rust
ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_2.cm
Run part 1 code, and reverse the string "Hello"
To specify just the single string "Hello" modify the
program.args
section of the common.shard.cml, build and run the following:C++
ffx component run /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm
To see the command output take a look at the logs:
ffx log --tags inspect_cpp_codelab
This command prints some output containing errors.
Rust
ffx component run /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm
To see the command output take a look at the logs:
ffx log --tags inspect_rust_codelab
We see in the logs that the component got the "Hello" as input, but we don't see the correct reversed output.
As you can see in the log the reverser doesn't work properly.
Try running the client with more arguments:
Add the string "World" to the
program.args
section of the common.shard.cml:{ program: { args: [ "Hello", "World", ], }, }
Build and run the following:
C++
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm ```
Rust
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm ```
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:
// Repeatedly send strings to be reversed to the other component.
for (int i = 1; i < argc; i++) {
FX_LOGS(INFO) << "Input: " << argv[i];
std::string output;
status = zx::make_result(reverser->Reverse(argv[i], &output));
if (status.is_error()) {
FX_LOGS(ERROR) << "Error: Failed to reverse string.";
return status.status_value();
}
FX_LOGS(INFO) << "Output: " << output;
}
Rust
In the client main:
for string in args.strings {
info!("Input: {}", string);
match reverser.reverse(&string).await {
Ok(output) => info!("Output: {}", output),
Err(e) => error!(error = ?e, "Failed to reverse string"),
}
}
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
fuchsia_logging::LogSettingsBuilder builder; builder.WithTags({"inspect_cpp_codelab", "part1"}).BuildAndInitialize();
Creating an asynchronous executor
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
Serving a public service
context->outgoing()->AddPublicService(Reverser::CreateDefaultHandler());
Rust
In the part 1 main:
Logging initialization
#[fuchsia::main(logging_tags = ["inspect_rust_codelab", "part1"])]
ServiceFs initialization
let mut fs = ServiceFs::new();
ServiceFs collection
let running_service_fs = fs.collect::<()>().map(Ok);
Serving a public service
fs.dir("svc").add_fidl_service(move |stream| reverser_factory.spawn_new(stream)); fs.take_and_serve_directory_handle()?;
See what the reverser definition is:
C++
In reverser.h:
class Reverser final : public fuchsia::examples::inspect::Reverser {
public:
// CODELAB: Create a new constructor for Reverser that takes an Inspect node.
// Implementation of Reverser.Reverse().
void Reverse(std::string input, ReverseCallback callback) override;
// Return a request handler for the Reverser protocol that binds incoming requests to new
// Reversers.
static fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> CreateDefaultHandler();
};
This class implements the Reverser
protocol. A helper method called
CreateDefaultHandler
constructs an InterfaceRequestHandler
that
creates new Reverser
s for incoming requests.
Rust
In reverser.rs:
pub struct ReverserServerFactory {}
impl ReverserServerFactory {
// CODELAB: Create a new() constructor that takes an Inspect node.
pub fn new() -> Self {
Self {}
}
pub fn spawn_new(&self, stream: ReverserRequestStream) {
// CODELAB: Add stats about incoming connections.
ReverserServer::new().spawn(stream);
}
}
struct ReverserServer {}
impl ReverserServer {
// CODELAB: Create a new() constructor that takes an Inspect node.
fn new() -> Self {
Self {}
}
pub fn spawn(self, mut stream: ReverserRequestStream) {
fasync::Task::local(async move {
while let Some(request) = stream.try_next().await.expect("serve reverser") {
// CODELAB: Add stats about incoming requests.
let ReverserRequest::Reverse { input, responder: _ } = request;
let _result = input.chars().rev().collect::<String>();
// Yes, this is silly. Just for codelab purposes.
fasync::Timer::new(fasync::MonotonicInstant::after(
zx::MonotonicDuration::from_hours(10),
))
.await
}
})
.detach();
}
}
This struct serves the Reverser
protocol. The ReverserServerFactory
(will make more sense
later) constructs a ReverserServer
when a new connection to Reverser
is established.
Add Inspect
Now that you know the code structure, you can start to instrument the code with Inspect to find 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:
source_set("lib") { sources = [ "reverser.cc", "reverser.h", ] public_deps = [ "//examples/diagnostics/inspect/codelab/fidl:fuchsia.examples.inspect_hlcpp", "//sdk/lib/inspect/component/cpp", ] }
Rust
In BUILD.gn in
deps
underrustc_binary("bin")
:"//src/lib/diagnostics/inspect/runtime/rust", "//src/lib/diagnostics/inspect/rust", "//src/lib/fuchsia",
Initialize Inspect:
C++
In main.cc:
#include <lib/inspect/component/cpp/component.h> inspect::ComponentInspector inspector(loop.dispatcher(), {});
Rust
In main.rs:
use fuchsia_inspect::component; let _inspect_server_task = inspect_runtime::publish( component::inspector(), inspect_runtime::PublishOptions::default(), );
You are now using Inspect.
Add a simple "version" property to show which version is running:
C++
inspector.root().RecordString("version", "part2");
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
StringProperty
on the root. ThisStringProperty
is 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 ininspector
rather than return it. As a result, the new property lives as long as the inspector itself (the entire execution of the component).
Rust
component::inspector().root().record_string("version", "part2");
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
StringProperty
on the root. ThisStringProperty
is 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).
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 /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm
ffx log --tags inspect_cpp_codelab
Rust
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm
ffx log --tags inspect_rust_codelab
Use
ffx inspect
to view your output:ffx inspect show
This dumps all of the Inspect data for the entire system, which may be a lot of data.
Since
ffx inspect
supports glob matching, run:C++
ffx inspect show 'core/ffx-laboratory\:client_part_1/reverser' # or `ffx inspect show --component 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 --component 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
You can also view the output as JSON:
C++
ffx --machine json-pretty inspect show 'core/ffx-laboratory\:client_part_1/reverser'
You should see an output like:
[ { "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'
You should see an output like:
[ { "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 } ]
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
reverser
service:C++
context->outgoing()->AddPublicService( Reverser::CreateDefaultHandler(inspector.root().CreateChild("reverser_service")));
Rust
let reverser_factory = ReverserServerFactory::new(component::inspector().root().create_child("reverser_service"));
Update your server to accept this node:
C++
Update the definition of
CreateDefaultHandler
in reverser.h and reverser.cc:#include <lib/inspect/cpp/inspect.h> fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> Reverser::CreateDefaultHandler( inspect::Node node) {
Rust
Update
ReverserServerFactory::new
to accept this node in reverser.rs:use fuchsia_inspect as inspect; pub struct ReverserServerFactory { node: inspect::Node, // ... } impl ReverserServerFactory { pub fn new(node: inspect::Node) -> Self { // ... Self { node, // ... } } // ... }
Add a property to keep track of the number of connections:
C++
fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> Reverser::CreateDefaultHandler( inspect::Node node) { // ... // Return a handler for incoming FIDL connections to Reverser. // // The returned closure contains a binding set, which is used to bind incoming requests to a // particular implementation of a FIDL interface. This particular binding set is configured to // bind incoming requests to unique_ptr<Reverser>, which means the binding set itself takes // ownership of the created Reversers and frees them when the connection is closed. return [connection_count = node.CreateUint("connection_count", 0), node = std::move(node), // ...
Rust
use { // ... fuchsia_inspect::NumericProperty, // ... }; pub struct ReverserServerFactory { node: inspect::Node, // ... connection_count: inspect::UintProperty, } impl ReverserServerFactory { pub fn new(node: inspect::Node) -> Self { // ... let connection_count = node.create_uint("connection_count", 0); Self { node, // ... connection_count, } } pub fn spawn_new(&self, stream: ReverserRequestStream) { self.connection_count.add(1);
This snippet demonstrates creating a new
UintProperty
(containing a 64 bit unsigned int) calledconnection_count
and 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 --component inspect_cpp_codelab
Rust
ffx --machine json-pretty inspect show --component inspect_rust_codelab
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
Reverse
method 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++
auto child = node.CreateChild(node.UniqueName("connection-"));
Rust
let node = self.node.create_child(inspect::unique_name("connection"));
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 ReverserServer
that takes inspect::Node
for the same reason as we did for ReverserServerFactory
.
Hint: You will need to create a member on Reverser to hold the
request_count
property. Its type will beinspect::UintProperty
.Follow up: Does request count give you all of the information you need? Add
response_count
as 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:
...
"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 Reverse
method receives a callback
, but it is never called with the value of output
.
Rust
If you added "response_count" as well, you may have noticed the bug.
The Reverse
method receives a responder
, but it is never called with the value of result
.
Send the response:
C++
// At the end of Reverser::Reverse callback(std::move(output));
Rust
responder.send(&result).expect("send reverse request response");
Run the client again:
C++
ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm
You should see an output like:
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 /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm
You should see an output like:
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`
The component continues to run until you execute ffx component stop
. As long as the component runs
you can run ffx inspect
and observe your output.
This concludes part 1. Optionally, you may commit your changes:
git commit -am "solution to part 1"
Part 2: Diagnosing inter-component problems
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++
fuchsia::examples::inspect::FizzBuzzPtr fizz_buzz;
context->svc()->Connect(fizz_buzz.NewRequest());
fizz_buzz->Execute(30, [](std::string result) { FX_LOGS(INFO) << "Got FizzBuzz: " << result; });
Rust
let fizzbuzz_fut = async move {
let fizzbuzz = client::connect_to_protocol::<FizzBuzzMarker>()
.context("failed to connect to fizzbuzz")?;
match fizzbuzz.execute(30u32).await {
Ok(result) => info!(%result, "Got FizzBuzz"),
Err(_) => {}
};
Ok(())
};
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
You will need to diagnose and solve this problem.
Diagnose the issue with Inspect
Run the component to see what is happening:
C++
ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_2.cm
Rust
ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_2.cm
Fortunately the FizzBuzz team instrumented their component using Inspect.
Read the FizzBuzz Inspect data using
ffx inspect
as before, you should see:"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.
Add Inspect to identify the problem:
C++
// CODELAB: Instrument our connection to FizzBuzz using Inspect. Is there an error? fuchsia::examples::inspect::FizzBuzzPtr fizz_buzz; context->svc()->Connect(fizz_buzz.NewRequest()); fizz_buzz.set_error_handler([&](zx_status_t status) { // CODELAB: Add Inspect here to see if there is a response. }); fizz_buzz->Execute(30, [](std::string result) { // CODELAB: Add Inspect here to see if there was a response. FX_LOGS(INFO) << "Got FizzBuzz: " << result; });
Rust
let fizzbuzz_fut = async move { let fizzbuzz = client::connect_to_protocol::<FizzBuzzMarker>() .context("failed to connect to fizzbuzz")?; match fizzbuzz.execute(30u32).await { Ok(result) => { // CODELAB: Add Inspect here to see if there is a response. info!(%result, "Got FizzBuzz"); } Err(_) => { // CODELAB: Add Inspect here to see if there is an error } }; Ok(()) };
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: inspector
has a method called Health()
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::component
has a function called health()
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!");
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
You should see an output like:
...
... 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
You should see an output like:
...
... 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. ...
Sandboxing errors are a common pitfall that are sometimes difficult to uncover.
Looking at part2 meta, you can see it is missing the service:
C++
Add a use
entry for Fizzbuzz
to part_2/meta:
use: [
{ protocol: "fuchsia.examples.inspect.FizzBuzz" },
],
Rust
Add a use
entry for Fizzbuzz
to part_2/meta:
use: [
{ protocol: "fuchsia.examples.inspect.FizzBuzz" },
],
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
You should see an output like:
[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
You should see an output like:
[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
This concludes Part 2.
Optionally, you can now commit your solution:
git commit -am "solution for part 2"
Part 3: Unit Testing for Inspect
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
The unit test ensures that Reverser works properly (and doesn't hang!), but it does not check that the Inspect output is as expected.
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++
binding_set_.AddBinding(std::make_unique<Reverser>(ReverserStats::CreateDefault()),
ptr.NewRequest());
// Alternatively
binding_set_.AddBinding(std::make_unique<Reverser>(inspect::Node()),
ptr.NewRequest());
Rust
let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<ReverserMarker>();
let reverser = ReverserServer::new(ReverserServerMetrics::default());
reverser.spawn(stream);
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 OpenReverser
to take the dependency for Reverser
as an argument and use it when constructing Reverser.
Hint: Create an
inspect::Inspector
in 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_reverser
to take the dependency for a ReverserServerFactory
as an argument and use it when constructing Reverser.
Hint: Create a
fuchsia_inspect::Inspector
in 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
.
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++
#include <lib/inspect/testing/cpp/inspect.h>
fpromise::result<inspect::Hierarchy> hierarchy =
RunPromise(inspect::ReadFromInspector(inspector));
ASSERT_TRUE(hierarchy.is_ok());
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:
auto* global_count =
hierarchy.value().node().get_property<inspect::UintPropertyValue>("request_count");
ASSERT_TRUE(global_count);
EXPECT_EQ(3u, global_count->value());
auto* connection_0 = hierarchy.value().GetByPath({"connection_0x0"});
ASSERT_TRUE(connection_0);
auto* requests_0 =
connection_0->node().get_property<inspect::UintPropertyValue>("request_count");
ASSERT_TRUE(requests_0);
EXPECT_EQ(2u, requests_0->value());
Rust
use diagnostics_assertions::assert_data_tree;
let inspector = inspect::Inspector::default();
assert_data_tree!(inspector, root: {
reverser_service: {
total_requests: 3u64,
connection_count: 2u64,
"connection0": {
request_count: 2u64,
response_count: 2u64,
},
"connection1": {
request_count: 1u64,
response_count: 1u64,
},
}
});
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.
Optionally, you may commit your changes:
git commit -am "solution to part 3"
Part 4: Integration Testing for Inspect
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
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
Rust
Find the component manifest (cml) in part_4/meta
You should see:
{ ... use: [ { protocol: "fuchsia.diagnostics.ArchiveAccessor" }, ] }
This file uses the protocol
fuchsia.diagnostics.ArchiveAccessor
from parent. This protocol is available to all tests to enable to read diagnostics about all components under the test realm.Look at the integration test itself. The individual test cases are fairly straightforward:
C++
Locate the integration test in part4/tests/integration_test.cc.
TEST_F(IntegrationTestPart4, StartWithFizzBuzz) { auto ptr = ConnectToReverser({.include_fizzbuzz = true}); bool error = false; ptr.set_error_handler([&](zx_status_t unused) { error = true; }); bool done = false; std::string result; ptr->Reverse("hello", [&](std::string value) { result = std::move(value); done = true; }); RunLoopUntil([&] { return done || error; }); ASSERT_FALSE(error); EXPECT_EQ("olleh", result); // CODELAB: Check that the component was connected to FizzBuzz. }
StartComponentAndConnect
is responsible for creating a new test environment and starting the codelab component inside of it. Theinclude_fizzbuzz_service
option 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.
#[fuchsia::test] async fn start_with_fizzbuzz() -> Result<(), Error> { let test = IntegrationTest::start(4, TestOptions::default()).await?; let reverser = test.connect_to_reverser()?; let result = reverser.reverse("hello").await?; assert_eq!(result, "olleh"); // CODELAB: Check that the component was connected to FizzBuzz. Ok(()) }
IntegrationTest::start
is responsible for creating a new test environment and starting the codelab component inside of it. Theinclude_fizzbuzz
option 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.Add the following method to your test fixture to read from the ArchiveAccessor service:
C++
#include <rapidjson/document.h> #include <rapidjson/pointer.h> std::string GetInspectJson() { fuchsia::diagnostics::ArchiveAccessorPtr archive; auto svc = sys::ServiceDirectory::CreateFromNamespace(); svc->Connect(archive.NewRequest()); while (true) { ContentVector current_entries; fuchsia::diagnostics::BatchIteratorPtr iterator; fuchsia::diagnostics::StreamParameters stream_parameters; stream_parameters.set_data_type(fuchsia::diagnostics::DataType::INSPECT); stream_parameters.set_stream_mode(fuchsia::diagnostics::StreamMode::SNAPSHOT); stream_parameters.set_format(fuchsia::diagnostics::Format::JSON); { std::vector<fuchsia::diagnostics::SelectorArgument> args; args.emplace_back(); args[0].set_raw_selector(ReverserMonikerForSelectors() + ":root"); fuchsia::diagnostics::ClientSelectorConfiguration client_selector_config; client_selector_config.set_selectors(std::move(args)); stream_parameters.set_client_selector_configuration(std::move(client_selector_config)); } archive->StreamDiagnostics(std::move(stream_parameters), iterator.NewRequest()); bool done = false; iterator->GetNext([&](auto result) { if (result.is_response()) { current_entries = std::move(result.response().batch); } done = true; }); RunLoopUntil([&] { return done; }); // Should be at most one component. ZX_ASSERT(current_entries.size() <= 1); if (!current_entries.empty()) { std::string json; fsl::StringFromVmo(current_entries[0].json(), &json); // Ensure the component is either OK or UNHEALTHY. if (json.find("OK") != std::string::npos || json.find("UNHEALTHY") != std::string::npos) { return json; } } // Retry with delay until the data appears. usleep(150000); } return ""; }
Rust
use anyhow::format_err; use diagnostics_assertions::{assert_data_tree, AnyProperty}; use diagnostics_reader::{ArchiveReader, DiagnosticsHierarchy, Inspect}; async fn get_inspect_hierarchy(test: &IntegrationTest) -> Result<DiagnosticsHierarchy, Error> { let moniker = test.reverser_moniker_for_selectors(); ArchiveReader::new() .add_selector(format!("{}:root", moniker)) .snapshot::<Inspect>() .await? .into_iter() .next() .and_then(|result| result.payload) .ok_or(format_err!("expected one inspect hierarchy")) }
Exercise. Use the returned data in your tests and add assertions to the returned data:
C++
rapidjson::Document document; document.Parse(GetInspectJson());
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_EQ
by passing in the expected value as a rapidjson::Value:rapidjson::Value("OK")
.
rapidjson::GetValueByPointerWithDefault( document, "/payload/root/fuchsia.inspect.Health/status", "")
Rust
let hierarchy = get_inspect_hierarchy(&test).await?;
Add assertions on the returned
DiagnosticsHierarchy
.- 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.
Optionally, 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