Fuchsia 组件检查概览

Fuchsia 中的组件可能会公开符合 Inspect API 的结构化信息。本文档介绍了组件检查的概念、接口、接口的 C++ 语言实现,以及用于与公开信息的组件进行交互的面向用户的工具。

不知道从何处入手?

  • 快速入门:组件检查入门指南。
  • ffx inspect:用于检查组件的 ffx 命令。
  • Codelab:一个完整的 C++ 和 Rust Codelab。
  • C++ 和 Rust 的 API 参考文档。
  • 健康检查:介绍健康检查子系统。
  • 最佳实践:有关层次结构设计和虚拟内存对象 (VMO) 大小的建议。

概念

组件可能会公开一个节点树,每个节点都有一组属性。

图:节点树

节点

节点是组件中导出的实体,可能有 0 个或多个子级。每个节点都有一个名称,并且节点的每个子节点在子节点中必须具有唯一的名称。

图:节点

属性

节点可以具有任意数量的属性。属性具有字符串键和值,该值可以是多种类型中的任何一种:

数值类型

  • UintProperty - 64 位无符号整数。
  • IntProperty - 64 位有符号整数。
  • DoubleProperty - 64 位浮点值。
  • BoolProperty - 布尔值。

字符串类型

  • StringProperty - UTF-8 字符串。
  • ByteVectorProperty - 字节向量。

数组类型

  • UintArray、IntArray、DoubleArray - 对应数字类型的数组。

直方图类型

  • LinearUintHistogram、LinearIntHistogram、LinearDoubleHistogram

一个直方图,其中包含存储在数组中的固定大小的存储桶。

  • ExponentialUintHistogram、ExponentialIntHistogram、ExponentialDoubleHistogram

一个直方图,其中包含存储在数组中的指数大小的存储桶。

检查文件格式

Inspect 文件格式是一种二进制格式,支持在运行时高效插入、修改和删除节点和属性。读取器在不与写入器通信的情况下获取内容的一致快照。

文件系统接口

顶级节点在组件的传出目录中公开为 VmoFile,并以扩展名 .inspect 结尾。组件通常会将其主要树或根树公开为 out/diagnostics/root.inspect。

语言库

C++

C++ Inspect 库为 Inspect 文件格式提供全面的写入和读取支持。

写入检查数据的组件应避免读取这些数据。 读取操作需要遍历整个缓冲区,这非常耗费资源。

Inspector 类提供了一个封装容器,用于创建一个包含一个可添加到的根节点的新缓冲区。节点和属性具有类型化封装容器,当它们超出范围时,会自动从缓冲区中删除底层数据。

inspect_component 库提供了一个简单的 ComponentInspector 单例接口,可帮助您处理从组件公开单个层次结构的常见情况。

健康功能支持以健康检查工具已知的格式公开结构化健康信息。

stats 功能支持公开运行时 Inspect 分配指标和缓冲区利用率。

有界列表功能支持在固定容量的 FIFO 节点缓冲区中记录滚动事件记录。

测试匹配器库提供 GMock 匹配器,用于验证在测试中从 Inspect 层次结构中读取的数据。

朗读支持

读取库支持将 Inspect 文件解析为层次结构。Hierarchy 包含 NodeValue 和 PropertyValues,它们分别是 Node 和 Property 的解析版本。

Hierarchy 的 NodeValue 由 node() 返回,子 Hierarchy 由 children() 在向量中返回。GetByPath 函数支持按路径读取特定的子级层次结构。

特定 NodeValue 的属性可通过 properties() 访问器获取。您可以通过将相应的 PropertyValue 类型作为模板参数传递给 Contains<T>() 方法,来确定属性是否包含特定类型:

  // Returns true if the first property of the hierarchy's node is an INT value.
  if (hierarchy.node().properties()[0].Contains<IntPropertyValue>()) {
    // ...
  }

使用 Get<T>() 方法获取属性:

  // Get the IntPropertyValue of the first property on the node.
  // Note: This causes a runtime exception if the property does not contain
  // the given type, crashing the program.
  hierarchy.node().properties()[0].Get<IntPropertyValue>();

您也可以根据不同的可能格式类型进行切换:

  const auto& property = hierarchy.node().properties()[0];
  switch (property.format()) {
    case FormatType::INT:
      const auto& value = property.Get<IntPropertyValue>();
      /* ... */
      break;
    /* ... */
  }

数组类型可以采用特殊格式,以包含直方图。GetBuckets() 方法支持从 {Int,Uint,Double}ArrayValue 类型返回直方图存储桶数组。如果底层数组不是特殊格式的直方图,则该数组将为空。

Rust

Rust Inspect 库为 Inspect 文件格式提供全面的写入和读取支持。

写入检查数据的组件应避免读取这些数据。 读取操作需要遍历整个缓冲区,这非常耗费资源。

Inspector 类提供了一个封装容器,用于创建一个包含一个可添加到的根节点的新缓冲区。节点和属性具有类型化封装容器,当它们超出范围时,会自动从缓冲区中删除底层数据。

组件模块支持简单的 inspector 函数,以处理从组件公开单个层次结构的常见用法。

health 模块支持以健康检查工具已知的格式公开结构化健康信息。

stats 模块支持公开运行时 Inspect 分配指标和缓冲区利用率。

contrib crate 为带时间戳的事件日志记录提供 BoundedListNode、inspect_log! 和时间实用程序。

测试模块支持 assert_data_tree! 宏,以匹配 Inspect 数据进行测试。

测试

验证器架构介绍了 Inspect 语言库的集成测试框架。

用户空间工具

ffx inspect

如需检查组件的检查层次结构,您可以使用 ffx inspect show 命令:

  • 输出所有检查层次结构:

    ffx inspect show
  • 输出特定组件(例如 core/font_provider)的检查层次结构:

    ffx inspect show core/font_provider

ffx inspect show 还会对输入内容进行模糊匹配。例如:

ffx inspect show archivist.cm

只要 是系统中唯一一个公开网址以 archivist.cm 结尾的 Inspect 的组件 ,此命令就会输出 bootstrap/archivist 的所有 Inspect。

如果输入不够具体,无法识别单个组件,则模糊匹配将失败;在这种情况下,该工具将输出与查询匹配的名称列表。

一般来说,此命令接受以下内容:

  • 组件名字对象,例如 core/network/netstack。如果仅传递名字对象,则无需对集合标记进行转义,但也可以选择进行转义。

  • 模糊搜索参数,对应于网址片段、清单片段或名字对象片段。即,清单为 archivist.cm 的 bootstrap/archivist 的 ffx inspect show archivist.cm。

  • 组件选择器,例如 core/network/*。

  • 完整的诊断选择器,例如 core/network/netstack:root/path/to/*:property。在完整选择器的情况下,必须执行转义才能创建有效的选择器。

假设您有组件 bootstrap/driver-hosts:driver-host-337。当您想要特定属性时,尽可能避免转义的一种符合人体工程学的方法可能如下所示:

ffx inspect driver-host-337 --data root/stats:my_stat

ffx target snapshot

ffx target snapshot 会生成一个 ZIP 归档文件,其中包含系统的诊断信息,包括 inspect:

ffx target snapshot

最佳做法

避免嵌套来自其他组件的指标

在设计组件诊断时,请将每个组件的 Inspect 层次结构严格限定为该组件生成的数据。避免在中间组件的 Inspect 树下转发或嵌套来自客户端或下级组件的 Inspect 指标。

在外部组件下转发或嵌套 Inspect 指标会给工具、调试和隐私合规性带来一些问题:

  • 开发者体验和工具归因:检查数据按组件名称进行归因。ffx inspect show 和自动快照分类查询等诊断工具会使用组件名字(例如 core/network/netstack)检查层次结构。当您将多个组件中的指标嵌套在单个中介服务中时,查询提供方组件的名字不会返回任何数据,而查询中介服务会返回无法管理的汇总层次结构。

  • 隐私审核和许可清单强制执行:Fuchsia 诊断流水线和快照导出器使用基于名字对象的选择器强制执行隐私控制。将来自多个生产者的指标置于外部名称下会掩盖数据来源、阻止精细的数据隐去,并使隐私权审核验证变得复杂。

  • 生命周期和故障隔离:代表其他组件保存指标的中间组件可以独立于实际指标生成器而存在或终止。如果中间件崩溃,这会导致诊断信息过时或数据丢失。

每个组件都应直接在其自己的名字对象下发布自己的 Inspect 树。

调整 Inspect VMO 的大小,以防止静默分配失败

Inspect 层次结构使用伙伴分配的 Inspect VMO 格式存储在映射的 VMO 中。默认情况下,C++ 和 Rust 库分配的最大 VMO 大小均为 256 KiB(262,144 字节)。当节点和属性写入层次结构时,内存会以 4 KiB 页面动态提交,直至达到此上限。

隐性分配失败

如果 Inspect 树超出其配置的最大 VMO 容量,或者内存碎片导致无法分配所请求大小的连续块,Inspect 库会静默失败:

  • 创建新的子节点或属性会失败,并返回空操作对象(或者,当多范围字符串或字节载荷在后续范围中空间不足时,会存储截断的前缀)。

  • 在容量已满时,附加到有界列表(例如 BoundedListNode)仍会先逐出最旧的条目,然后再尝试分配新条目,并且如果空间在记录其属性时用尽,则可能会丢弃新条目或留下空条目节点或部分填充的条目节点。

  • 对现有数字属性、布尔值属性、数字数组和直方图进行的就地更改将继续正常运行。

  • 如果无法分配初始字符串块或其第一个溢出范围,则更新字符串属性或字符串数组槽会保留先前的值;如果后续溢出范围失败,则会存储截断的前缀。更新字节属性会先释放其旧区段,如果第一个区段未能分配,则会留下空值;如果后续区段失败,则会留下截断的前缀。

  • 组件不会崩溃或发生 panic。系统不会记录失败的节点和属性创建;仅当 StringProperty::set 无法分配初始字符串块或其第一个溢出范围,或者 BytesProperty::set 无法分配其第一个范围时,Rust 库才会记录错误。

不过,静默分配失败意味着在开发和问题排查期间,指标缺失或历史事件日志被截断的情况可能会被忽视。

使用 fuchsia.inspect.Stats 检测分配失败

您可以通过在层次结构的根节点提供 fuchsia.inspect.Stats 延迟节点,来监控 Inspect 内存用量并检测丢弃的分配:

使用 ffx inspect show 查询组件时,fuchsia.inspect.Stats 节点会显示实时分配指标:

ffx inspect show <moniker>:root/fuchsia.inspect.Stats

此命令会输出类似于以下内容的指标:

fuchsia.inspect.Stats:
  allocated_blocks = 142
  current_size = 36864
  deallocated_blocks = 28
  failed_allocations = 0
  maximum_size = 262144
  peak_bytes_requested = 34560
  total_dynamic_children = 1
  utilization_per_ten_k = 1406

failed_allocations 的非零值表示 Inspect 已用尽其 VMO 容量并丢弃了数据。peak_bytes_requested 报告并发请求的字节数的高水位线。失败的请求仍会计入该值,并且永远不会释放,因此重复的失败分配会使该值超出真实峰值需求。您可以使用此值作为提示,了解 Inspector VMO 需要多大才能避免分配失败。

容量调整建议和配置

设计组件诊断时:

  • 估算缓冲区要求:如果您的组件使用有界集合(例如 BoundedListNode)记录历史事件日志,请计算预期的内存占用(条目数 × 平均条目大小)或检查 fuchsia.inspect.Stats 中的 peak_bytes_requested,并确保 VMO 的大小留有足够的余量。

  • 配置自定义 VMO 容量:如果 256 KiB 不够,请使用明确的大小初始化 Inspector:

    • C++:通过设置了 maximum_size 的 inspect::InspectSettings 结构体构造 inspect::Inspector。如需通过 inspect::ComponentInspector 发布,请在 inspect::PublishOptions 的 inspector 字段中传递该 Inspector。

    • Rust:在任何其他使用 component 单例(inspector()、health() 或 serve_inspect_stats())之前调用 fuchsia_inspect::component::init_inspector_with_size(max_size),或通过 Inspector::new(InspectorConfig::default().size(max_size)) 配置 InspectorConfig。

  • 对大型或动态数据集使用延迟节点:将大型集合(例如路由表或详细缓存条目)分流到延迟节点,以便按需计算,而不是永久占用 VMO 空间。

如需查看有关配置 VMO 大小和传送统计信息的具体代码示例,请参阅快速入门指南中的管理 VMO 大小和容量。