检查快速入门

本快速入门将引导您了解组件检查的基础知识。您将学习如何使用特定语言的库将 Inspect 集成到组件中,并使用 ffx inspect 查看数据。

如需详细了解 Inspect 概念,请参阅 Inspect Codelab。

项目设置

请参阅下文,获取您所选语言的快速入门指南:

C++

本部分假设您正在编写异步组件,并且组件的某些部分(通常为 main.cc)如下所示:

async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
auto context_ = sys::ComponentContext::CreateAndServeOutgoingDirectory();
// ...
loop.Run();

这会设置一个异步循环,创建一个 ComponentContext 封装由运行时提供的句柄,然后在完成一些其他初始化工作后运行该循环。

将 Inspect 库依赖项添加到 BUILD.gn 文件中:

"//sdk/lib/inspect/component/cpp",
"//sdk/lib/sys/cpp",

添加以下 include 语句:

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

添加以下代码以初始化 Inspect:

inspector_ = std::make_unique<inspect::ComponentInspector>(async_get_default_dispatcher(),
                                                           inspect::PublishOptions{});

您现在使用的是“检查”功能!通过将属性附加到根节点,在 Inspect 树中创建属性:

// 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;

此库包含一整套匹配器,用于验证 Inspect 树的内容。

// 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(())
}

将 Inspect 库依赖项添加到 BUILD.gn 文件中:

"//src/lib/diagnostics/inspect/runtime/rust",
"//src/lib/diagnostics/inspect/rust",

添加以下代码以初始化 Inspect:

// 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());

您现在使用的是“检查”功能!通过将属性附加到根节点,在 Inspect 树中创建属性:

// 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();

测试

如需测试 Inspect 代码,您可以使用 assert_data_tree 验证 Inspect 树的内容:

// 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 拥有该媒体资源。当它被销毁(超出范围)时,底层属性会被删除,并且不再出现在组件的 Inspect 输出中。对于子节点也是如此。

如果您要创建不需要修改的值,请使用 ValueList 使其保持活跃状态,直到不再需要它们。

  • 检查是尽力而为的。

由于空间限制,Inspect 库可能无法满足 Create 请求。此错误不会显示在您的代码中:您将收到一个 Node/Property 对象,其方法为无操作。

  • 模式:将子节点传入子对象。

向您自己的类的构造函数添加 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 超出了范围,因此系统会调用对该属性的 drop。读者不会看到此属性。

使用 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 超出范围。

动态值

本部分介绍了 Inspect 库对在读取时延迟膨胀的节点的支持。这些方法接受回调函数,而不是值。读取属性值时,系统会调用回调函数。

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 Inspect 会自动对字符串名称进行去重。例如,

use fuchsia_inspect::Inspector;

let inspector = Inspector::default();
for _ in 0..100 {
  inspector.root().record_child("child");
}

将仅生成 1 个 "child" 副本,该副本会被引用 100 次。

这样可以为每个子节点节省 16 个字节,而共享数据会占用 32 个字节。最终结果是节省了 1568 个字节。

事件日志记录和时间戳

虽然 Inspect 属性通常表示瞬时组件状态,但组件通常需要记录历史事件(例如连接尝试、状态转换或错误)。

如需有效记录滚动事件日志和时间戳,请执行以下操作:

  • 遵循 @time 命名惯例,以便开发者清楚地识别时间戳,并可由 Fuchsia Snapshot Viewer (FSV) 解析。

  • 使用有界列表节点来维护固定容量的先进先出 (FIFO) 事件缓冲区,而不会导致内存无限制增长。

时间戳属性规范

Inspect 没有专用的时间戳基元类型。而是记录为 64 位整数属性(IntProperty 或 UintProperty),通常表示纳秒或秒。Fuchsia 快照查看器 (FSV) 不会对时间轴做出任何假设;用户必须根据上下文了解如何解读时间戳。

FSV 会解析名为 @time 或以 @time (<prefix>@time) 结尾的属性键,以呈现人类可读的日期并计算经过的时间:

  • 事件时间戳 (@time):使用确切的属性键 @time 作为事件或状态更改的主要时间戳。

  • 时间间隔时间戳(start@time、end@time):在跟踪时间范围内的时间戳和时长时,请在属性键(例如 start@time、end@time、created@time 或 last_seen@time)上使用 @time 后缀。

有界列表节点

有界列表节点在父节点下维护子节点的循环 FIFO 缓冲区。每个新事件都会添加为子节点,并以自动递增的索引("0"、"1"、"2" 等)命名。当列表达到其最大容量时,创建新条目会自动逐出最旧的条目。

Rust

在 Rust 中,使用 fuchsia-inspect-contrib crate 中的 BoundedListNode。将其与 inspect_log! 宏结合使用,即可通过自动 @time 属性注入来记录带时间戳的事件:

use fuchsia_inspect_contrib::inspect_log;
use fuchsia_inspect_contrib::nodes::BoundedListNode;

// Create a bounded list with a capacity of 10 entries under "events".
let mut events = BoundedListNode::new(root.create_child("events"), 10);

// Log an event using key-value syntax. An "@time" property is recorded
// automatically.
inspect_log!(events, state: "connected", address: 42u64);

// Log an event using block syntax with multiple fields.
inspect_log!(events, {
    state: "disconnected",
    reason: "timeout",
    retry_count: 3u32,
});

// Log optional fields and nested structures.
let peer_id: Option<u64> = Some(1234);
inspect_log!(events, {
    event: "peer_discovered",
    peer_id?: peer_id,
    details: {
        rssi: -45i16,
        channel: 6u8,
    },
});

如需在任何节点上手动记录时间戳(例如开始时间和结束时间),请使用 NodeTimeExt 扩展程序特征:

use fuchsia_inspect_contrib::nodes::{BootTimeline, NodeTimeExt};

// Record an "@time" property with the current boot timestamp.
NodeTimeExt::<BootTimeline>::record_time(&node, "@time");

// Record interval timestamps on a node.
node.record_int("start@time", start_instant.into_nanos());
node.record_int("end@time", end_instant.into_nanos());

C++

在 C++ 中,通过添加 <lib/inspect/cpp/bounded_list_node.h> 来使用 inspect::BoundedListNode:

#include <lib/inspect/cpp/bounded_list_node.h>
#include <lib/zx/clock.h>

// Create a bounded list with a capacity of 10 entries under "events".
inspect::BoundedListNode events(root.CreateChild("events"), 10);

// Record a timestamped entry using CreateEntry.
events.CreateEntry([](inspect::Node& entry) {
  entry.RecordInt("@time", zx::clock::get_boot().get());
  entry.RecordString("state", "connected");
  entry.RecordUint("address", 42);
});

// Record interval timestamps when measuring operations.
events.CreateEntry([&](inspect::Node& entry) {
  entry.RecordInt("start@time", start_time.get());
  entry.RecordInt("end@time", zx::clock::get_boot().get());
  entry.RecordString("status", "success");
});

输出层次结构

使用 ffx inspect 检查时,生成的有界事件日志会在层次结构中显示为已编入索引的子节点:

root:
  events:
    "0":
      "@time" = 123456789012
      address = 42
      state = "connected"
    "1":
      "@time" = 123457890123
      reason = "timeout"
      retry_count = 3
      state = "disconnected"

管理 VMO 大小和容量

检查层次结构存储在映射的虚拟内存对象 (VMO) 中,默认最大容量为 256 KiB。当 Inspect 层次结构空间不足时,创建新节点和属性会静默失败,而不会崩溃或记录错误。

您可以通过提供 fuchsia.inspect.Stats 延迟节点来配置自定义 VMO 容量并监控内存用量。

C++

在 C++ 中,在构建 Inspector 时使用 inspect::InspectSettings 指定 maximum_size,并调用 CreateStatsNode() 来发布统计信息:

#include <lib/inspect/cpp/inspector.h>

// Initialize an Inspector with 1 MiB of VMO capacity.
inspect::InspectSettings settings{.maximum_size = 1024 * 1024};
inspect::Inspector inspector(settings);

// Serve real-time allocation statistics under root/fuchsia.inspect.Stats.
inspector.CreateStatsNode();

将 ComponentInspector 与异步调度程序搭配使用时:

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

inspect::InspectSettings settings{.maximum_size = 1024 * 1024};
auto inspector = inspect::ComponentInspector(dispatcher, {
    .inspector = inspect::Inspector(settings),
});
inspector.inspector().CreateStatsNode();

Rust

如需增加单例组件检查器的 VMO 大小,请在单例的任何其他用途(例如 fuchsia_inspect::component::inspector() 或 component::health())之前调用 init_inspector_with_size;后续调用只会记录警告并保留现有大小。调用 serve_inspect_stats() 以发布分配统计信息:

use fuchsia_inspect::component;

// Initialize the component inspector with 1 MiB of VMO capacity.
component::init_inspector_with_size(1024 * 1024);

// Serve real-time allocation statistics under root/fuchsia.inspect.Stats.
component::serve_inspect_stats();

如果您手动构建 Inspector,请使用 InspectorConfig 指定尺寸:

use fuchsia_inspect::stats::InspectorExt;
use fuchsia_inspect::{Inspector, InspectorConfig};

let inspector = Inspector::new(InspectorConfig::default().size(1024 * 1024));
inspector.record_lazy_stats();

如需详细了解容量规划和故障模式,请参阅调整 Inspect VMO 的大小以防止静默分配失败。

查看检查数据

您可以使用 ffx inspect 命令查看从组件导出的 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/dhcp-client
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/reachability

以下示例展示了产生单个匹配项的调用:

ffx inspect show feedback

此示例会输出 core/feedback 的 Inspect 数据:

使用 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 具有任意字节值的属性。 所有语言
节点 一个节点,其下可能嵌套了指标、属性和更多节点。 所有语言
LazyNode 动态实例化一个完整的节点树。 C++、Rust