本快速入门将指导您了解使用
组件检查的基本知识。您将学习如何使用特定于语言的库将检查功能集成到
您的组件中,以及如何使用
ffx inspect查看数据。
如需详细了解检查概念,请参阅 检查 Codelab。
项目设置
请参阅下文,了解您所选语言的快速入门指南:
C++
本部分假定您正在编写异步组件,并且组件的某些部分(通常是 main.cc)如下所示:
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
auto context_ = sys::ComponentContext::CreateAndServeOutgoingDirectory();
// ...
loop.Run();
这会设置一个异步循环,创建一个 ComponentContext 封装由运行时提供的句柄,然后在完成一些其他初始化工作后运行该循环。
将检查库依赖项添加到 BUILD.gn 文件中:
"//sdk/lib/inspect/component/cpp",
"//sdk/lib/sys/cpp",
添加以下 include:
#include <lib/inspect/component/cpp/component.h>
添加以下代码以初始化检查:
inspector_ = std::make_unique<inspect::ComponentInspector>(async_get_default_dispatcher(),
inspect::PublishOptions{});
您现在可以使用检查功能了!通过将属性附加到根节点,在检查树中创建属性:
// Attach properties to the root node of the tree
inspect::Node& root_node = inspector_->root();
// Important: Hold references to properties and don't let them go out of scope.
auto total_requests = root_node.CreateUint("total_requests", 0);
auto bytes_processed = root_node.CreateUint("bytes_processed", 0);
如需查看您可以尝试的完整数据 类型列表,请参阅支持的数据类型。
健康检查
健康检查子系统提供了一个用于检查组件健康状况的标准化指标。您可以使用健康状况节点报告组件的整体状态:
inspector_->Health().StartingUp();
// ...
inspector_->Health().Ok();
测试
如需测试检查代码,您可以使用 //sdklib/inspect/testing/cpp/inspect.h:
#include <fidl/fidl.examples.routing.echo/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <gtest/gtest.h>
#include <src/lib/testing/loop_fixture/real_loop_fixture.h>
using namespace inspect::testing;
此库包含一组完整的匹配器,用于验证检查树的内容。
// Validate the contents of the tree match
auto hierarchy_result = inspect::ReadFromVmo(inspector_.DuplicateVmo());
ASSERT_TRUE(hierarchy_result.is_ok());
EXPECT_THAT(hierarchy_result.take_value(),
NodeMatches(AllOf(PropertyList(::testing::UnorderedElementsAre(
UintIs("bytes_processed", 24), UintIs("total_requests", 2))))));
Rust
本部分假定您正在编写异步组件,并且组件的某些部分(通常是 main.rs)如下所示:
async fn main() -> Result<(), Error> {
// ...
let mut service_fs = ServiceFs::new();
// ...
service_fs.take_and_serve_directory_handle().unwrap();
service_fs.collect::<()>().await;
Ok(())
}
将检查库依赖项添加到 BUILD.gn 文件中:
"//src/lib/diagnostics/inspect/runtime/rust",
"//src/lib/diagnostics/inspect/rust",
添加以下代码以初始化检查:
// This creates the root of an Inspect tree
// The Inspector is a singleton that you can access from any scope
let inspector = fuchsia_inspect::component::inspector();
// This serves the Inspect tree, converting failures into fatal errors
let _inspect_server_task =
inspect_runtime::publish(inspector, inspect_runtime::PublishOptions::default());
您现在可以使用检查功能了!通过将属性附加到根节点,在检查树中创建属性:
// Attach properties to the root node of the tree
let root_node = inspector.root();
let total_requests = root_node.create_uint("total_requests", 0);
let bytes_processed = root_node.create_uint("bytes_processed", 0);
如需查看您可以尝试的完整数据 类型列表,请参阅支持的数据类型。
健康检查
健康检查子系统提供了一个用于检查组件健康状况的标准化指标。您可以使用健康状况节点报告组件的整体状态:
fuchsia_inspect::component::health().set_starting_up();
// ...
fuchsia_inspect::component::health().set_ok();
测试
如需测试检查代码,您可以使用 assert_data_tree 验证检查树的内容:
// Get a reference to the root node of the Inspect tree
let inspector = fuchsia_inspect::component::inspector();
// ...
// Validate the contents of the tree match
diagnostics_assertions::assert_data_tree!(inspector, root: {
total_requests: 2u64,
bytes_processed: 24u64,
});
检查库
现在您已经拥有 root_node,可以开始构建层次结构了。本部分介绍了一些重要的概念和模式,可帮助您入门。
- 一个节点可以有任意数量的键值对,称为属性 。
- 值的键始终是 UTF-8 字符串,值可以是以下 支持的类型之一。
一个节点可以有任意数量的子节点,这些子节点也是节点。
C++
上面的代码可让您访问名为“root”的单个节点。hello_world_property 是一个包含字符串值的属性(恰如其分地称为 StringProperty )。
- 值和节点是在父节点下创建的。
Node 类具有适用于每种支持的值的创建器方法。hello_world_property 是使用 CreateStringProperty 创建的。您可以通过调用 root_node.CreateChild("child name") 在根节点下创建一个子节点
。请注意,名称必须始终是 UTF-8 字符串。
- 值和节点具有严格的所有权语义。
hello_world_property 拥有该属性。当它被销毁(超出范围)时,底层属性将被删除,并且不再显示在组件的检查输出中。对于子节点也是如此。
如果您要创建不需要修改的值,请使用
ValueList
使它们保持有效,直到不再需要它们为止。
- 检查是尽力而为的。
由于空间限制,检查库可能无法满足 Create 请求。此错误不会显示在您的代码中:您将收到一个节点/属性对象,其方法是空操作。
- 模式:将子节点传递给子对象。
为自己的类的构造函数添加 inspect::Node 实参非常有用。父对象(应拥有自己的 inspect::Node)可以在构造子对象时将 CreateChild(...) 的结果传递给子对象:
class Child {
public:
Child(inspect::Node my_node) : my_node_(std::move(my_node)) {
// Create a string that doesn't change, and emplace it in the ValueList
my_node_.CreateString("version", "1.0", &values_);
// Create metrics and properties on my_node_.
}
private:
inspect::Node my_node_;
inspect::StringProperty some_property_;
inspect::ValueList values_;
// ... more properties and metrics
};
class Parent {
public:
// ...
void AddChild() {
// Note: inspect::UniqueName returns a globally unique name with the specified prefix.
children_.emplace_back(my_node_.CreateChild(inspect::UniqueName("child-")));
}
private:
std::vector<Child> children_;
inspect::Node my_node_;
};
Rust
Rust 库提供了两种管理节点和属性的方法:创建和记录。
使用 create_* 方法时,属性或节点对象的所有权属于调用方。当返回的对象被舍弃时,该属性将被移除。
例如:
{
let property = root.create_int("name", 1);
}
在此示例中,property 超出了范围,因此系统会调用对该属性的舍弃。读取器将看不到此属性。
使用 record_* 方法时,属性的生命周期与父节点相关联。当节点被删除时,记录的属性也会被删除。
{
let node = root.create_child("name");
{
node.record_uint(2); // no return
}
// The uint property will still be visible to readers.
}
在此示例中,与 name 关联的 uint 属性对读取器可见,直到父 node 超出范围。
动态值
本部分介绍了检查库中对在读取时延迟扩充的节点的支持。这些方法接受回调函数而不是值。当读取属性值时,系统会调用回调函数。
C++
C++ 库有两个用于动态值的属性创建器:CreateLazyNode 和 CreateLazyValues。
这两种方法都接受一个回调,该回调返回对 inspect::Inspector 的 promise,唯一的区别在于动态值在树中的存储方式。
root->CreateLazyNode(name, callback) 使用给定的 name 创建
root 的子节点。callback 返回对 inspect::Inspector 的 promise,其根节点在读取时会拼接成父层次结构。以下示例显示,存在一个名为“lazy”的子节点,该子节点具有字符串属性“version”,并且还有一个名为“lazy”的子节点。
root->CreateLazyValues(name, callback) 的工作方式与 root->CreateLazyNode(name,
callback) 类似,不同之处在于,promise 根节点上的所有属性和子节点都
直接作为值
添加到原始 root 中。在此示例的第二个输出中,内部延迟节点不会显示,其值会扁平化为 root 上的属性。
root->CreateLazy{Node,Values}("lazy", [] {
Inspector a;
a.GetRoot().CreateString("version", "1.0", &a);
a.GetRoot().CreateLazy{Node,Values}("lazy", [] {
Inspector b;
b.GetRoot().RecordInt("value", 10);
return fpromise::make_ok_promise(std::move(b));
}, &a);
return fpromise::make_ok_promise(std::move(a));
});
输出 (CreateLazyNode):
root:
lazy:
version = "1.0"
lazy:
value = 10
输出 (CreateLazyValues):
root:
value = 10
version = "1.0"
CreateLazy{Node,Values} 的返回值是拥有传递的回调的 LazyNode。 一旦 LazyNode 被销毁,回调将永远不会被调用。如果您在执行回调的同时销毁 LazyNode,则销毁操作将被阻止,直到回调返回其 promise。
如果您想动态公开 this 上的属性,只需编写以下内容:
class Employee {
public:
Employee(inspect::Node node) : node_(std::move(node)) {
calls_ = node_.CreateInt("calls", 0);
// Create a lazy node that populates values on its parent
// dynamically.
// Note: The callback will never be called after the LazyNode is
// destroyed, so it is safe to capture "this."
lazy_ = node_.CreateLazyValues("lazy", [this] {
// Create a new Inspector and put any data in it you want.
inspect::Inspector inspector;
// Keep track of the number of times this callback is executed.
// This is safe because the callback is executed without locking
// any state in the parent node.
calls_.Add(1);
// ERROR: You cannot modify the LazyNode from the callback. Doing
// so may deadlock!
// lazy_ = ...
// The value is set to the result of calling a method on "this".
inspector.GetRoot().RecordInt("performance_score",
this->CalculatePerformance());
// Callbacks return a fpromise::promise<Inspector>, so return a result
// promise containing the value we created.
// You can alternatively return a promise that is completed by
// some asynchronous task.
return fpromise::make_ok_promise(std::move(inspector));
});
}
private:
inspect::Node node_;
inspect::IntProperty calls_;
inspect::LazyNode lazy_;
};
Rust
请参阅 C++ 动态值支持,因为类似的概念也适用于 Rust。
示例:
root.create_lazy_{child,values}("lazy", [] {
async move {
let inspector = Inspector::default();
inspector.root().record_string("version", "1.0");
inspector.root().record_lazy_{node,values}("lazy", || {
let inspector = Inspector::default();
inspector.root().record_int("value", 10);
// `_value`'s drop is called when the function returns, so it will be removed.
// For these situations `record_` is provided.
let _value = inspector.root().create_int("gone", 2);
Ok(inspector)
});
Ok(inspector)
}
.boxed()
});
Output (create_lazy_node):
root:
lazy:
version = "1.0"
lazy:
value = 10
Output (create_lazy_values):
root:
value = 10
version = "1.0"
字符串引用
C++
节点和属性的名称会自动使用字符串内部化。
using inspect::Inspector;
Inspector inspector;
for (int i = 0; i < 100; i++) {
inspector.GetRoot().CreateChild("child", &inspector);
}
将仅生成一个 "child" 副本,该副本被引用 100 次。
Rust
字符串名称会在 Rust 检查中自动去重。例如,
use fuchsia_inspect::Inspector;
let inspector = Inspector::default();
for _ in 0..100 {
inspector.root().record_child("child");
}
将仅生成一个 "child" 副本,该副本被引用 100 次。
这为每个子节点节省了 16 字节,共享数据的成本为 32 字节。最终节省了 1568 字节。
查看检查数据
您可以使用 ffx inspect 命令查看从组件导出的检查数据
。
本部分假定您拥有对正在运行的 Fuchsia 系统的 SSH 访问权限,并且您已开始运行组件。我们将使用名称 my_component.cm 作为组件清单名称的占位符。
读取检查数据
以下命令会输出系统中运行的所有组件的检查层次结构:
ffx inspect show使用 ffx inspect list 的输出,您可以将单个组件(例如 core/network/netstack)指定为 ffx inspect show 的输入:
ffx inspect show core/network/netstack您可以指定多个组件(例如 core/font_provider 和 core/my_component):
ffx inspect show core/font_provider core/my_component您还可以指定节点和属性值。如需查看所有可能的选择器的
列表,请使用ffx inspect selectors:
ffx inspect selectors core/my_component然后,您可以将指向节点的选择器指定为 ffx inspect show 的输入:
ffx inspect show core/my_component:root/my_node这将生成包含该节点及其所有子节点和嵌套属性的输出:
core/my_component:
metadata:
name = root
component_url = fuchsia-pkg://fuchsia.com/my_package#meta/my_component.cm
timestamp = 1234567890
payload:
root:
my_node:
hello = "goodbye"
world = 2
a_child:
test = 4.2
您还可以将指向属性的选择器指定为 ffx inspect show 的输入:
ffx inspect show core/my_component:root/my_node:hello这将生成如下所示的输出:
core/my_component:
metadata:
name = root
component_url = fuchsia-pkg://fuchsia.com/my_package#meta/my_component.cm
timestamp = 1234567890
payload:
root:
my_node:
hello = "goodbye"
如果您不知道组件的 moniker,可以传递一个您认为与组件清单、网址、moniker 等相关的字符串。然后,该工具会对所有组件进行模糊匹配。如果找到多个匹配项,它会要求消除歧义,否则它会返回您期望的输出。
例如,以下内容可能会返回多个匹配项:
ffx inspect show network此操作会返回:
Fuzzy matching failed due to too many matches, please re-try with one of these:
bootstrap/boot-drivers:PCI0.bus.00_04_0.00_04_0.virtio-net
core/network
core/network-tun
core/network/dhcpd
core/network/dhcpv6-client
core/network/dns-resolver
core/network/http-client
core/network/netcfg
core/network/netcfg/netcfg-config
core/network/netstack
core/network/netstack/dhcp-client
core/network/reachability
此示例展示了生成单个匹配项的调用:
ffx inspect show feedback此示例输出了 core/feedback 的检查数据:
在测试环境中输出检查数据
使用 JSON 美化打印工具获取完整列表。例如:
use diagnostics_assertions::JsonGetter;
...
#[fuchsia::test]
fn my_test() {
let inspect = fuchsia_inspect::component::inspector();
...
print!("{}", inspect.get_pretty_json());
}
支持的数据类型
| 类型 | 说明 | 备注 |
|---|---|---|
| IntProperty | 包含带符号 64 位整数的指标。 | 所有语言 |
| UIntProperty | 包含无符号 64 位整数的指标。 | Dart 中不支持 |
| DoubleProperty | 包含双精度浮点数的指标。 | 所有语言 |
| BoolProperty | 包含双精度浮点数的指标。 | 所有语言 |
| {Int,Double,Uint}Array | 指标类型的数组,包括各种直方图的类型化封装容器。 | 与基本指标类型相同的语言支持 |
| StringArray | 字符串数组。表示为 StringReference。 | Dart 中不支持。 |
| StringProperty | 具有 UTF-8 字符串值的属性。 | 所有语言 |
| ByteVectorProperty | 具有任意字节值的属性。 | 所有语言 |
| Node | 一个节点,指标、属性和更多节点可以嵌套在该节点下。 | 所有语言 |
| LazyNode | 动态实例化完整的节点树。 | C++、Rust |