在驅動程式中使用檢查功能

本指南說明如何使用 Inspect 檢測 Fuchsia 驅動程式架構 (DFv2) 驅動程式,以及如何從驅動程式庫元件查詢 Inspect 資料。

總覽

在 Fuchsia 的驅動程式架構 (DFv2) 中,驅動程式是元件。驅動程式會直接參與 Fuchsia 的元件診斷架構,並使用標準 fuchsia.inspect.InspectSink 能力發布檢查資料,就像任何其他 Fuchsia 元件一樣。

DFv2 中「檢查」的主要特徵:

  • 標準功能轉送:驅動程式庫元件資訊清單會使用標準 inspect/client.shard.cml 分片要求 fuchsia.inspect.InspectSink。
  • 元件歸因檢查:檢查階層會歸因於元件路徑名稱下的每個個別驅動程式元件 (例如 bootstrap/boot-drivers 或 bootstrap/base-drivers 下)。
  • 標準工具:使用 ffx inspect 或 iquery,透過標準元件選取器或元件網址比對,檢查 Driver Inspect 資料。

必要條件

如果您不熟悉檢查元素的基本概念,請參閱下列指南:

在驅動程式庫中加入 Inspect

1. 更新元件資訊清單 (.cml)

在驅動程式庫的 .cml 檔案中加入 Inspect 用戶端分片:

{
    include: [
        "inspect/client.shard.cml",
        "syslog/client.shard.cml",
    ],
    program: {
        runner: "driver",
        binary: "driver/my_driver.so",
        bind: "meta/bind/my_driver.bindbc",
    },
}

inspect/client.shard.cml 分片會將 fuchsia.inspect.InspectSink 通訊協定從上層診斷目錄路徑,導向驅動程式庫傳入命名空間。

2. 新增建構依附元件

C++

在驅動程式庫的 BUILD.gn 中新增 //sdk/lib/inspect/component/cpp:

fuchsia_cc_driver("my_driver") {
  deps = [
    "//sdk/lib/driver/component/cpp",
    "//sdk/lib/inspect/component/cpp",
  ]
}

如果使用 Bazel 建構,請在 BUILD.bazel 中新增 @fuchsia_sdk//pkg/inspect_component_cpp:

fuchsia_cc_driver(
    name = "my_driver",
    deps = [
        "@fuchsia_sdk//pkg/driver_component_cpp",
        "@fuchsia_sdk//pkg/inspect_component_cpp",
    ],
)

荒漠油廠

在驅動程式庫的 BUILD.gn 中,視 //sdk/lib/driver/component/rust、//src/lib/diagnostics/inspect/rust 和 //src/lib/fuchsia-async 而定:

fuchsia_rust_driver("my_driver") {
  deps = [
    "//sdk/lib/driver/component/rust",
    "//src/lib/diagnostics/inspect/rust",
    "//src/lib/fuchsia-async",
  ]
}

3. 初始化並發布檢查

C++

在 C++ 中,從 fdf::DriverBase2 繼承的驅動程式可以使用 context.CreateInspector(this) 發布檢查樹狀結構:

#include <lib/driver/component/cpp/driver_base2.h>
#include <lib/driver/component/cpp/driver_export2.h>
#include <lib/inspect/component/cpp/component.h>

class MyDriver : public fdf::DriverBase2 {
 public:
  MyDriver() : fdf::DriverBase2("my_driver") {}

  zx::result<> Start(fdf::DriverContext context) override {
    // 1. Create and publish the ComponentInspector.
    // Must be called before context.take_incoming() if transferring ownership.
    component_inspector_ = context.CreateInspector(this);

    // 2. Build the Inspect hierarchy from the root node.
    hardware_node_ = component_inspector_->root().CreateChild("hardware_status");
    revision_prop_ = hardware_node_.CreateUint("revision_id", 0x10);
    requests_count_ = hardware_node_.CreateUint("requests_count", 0);

    // 3. For values that do not change, use Record* helpers to avoid storing handles:
    hardware_node_.RecordString("serial_number", "ABC-1234");

    // 4. (Optional) Report component health:
    component_inspector_->Health().Ok();

    return zx::ok();
  }

  void HandleDeviceRequest() {
    requests_count_.Add(1);
  }

  // Accessor for unit tests:
  const inspect::Inspector& inspector() const {
    return component_inspector_->inspector();
  }

 private:
  std::optional<inspect::ComponentInspector> component_inspector_;
  inspect::Node hardware_node_;
  inspect::UintProperty revision_prop_;
  inspect::UintProperty requests_count_;
};

FUCHSIA_DRIVER_EXPORT2(MyDriver);

重點:

  • context.CreateInspector(this):自動連線至傳入命名空間中的 fuchsia.inspect.InspectSink,並發布檢查樹狀結構,且樹狀結構名稱設為驅動程式庫的 name()。透過 context.take_incoming() 取得命名空間前,請先叫用 context.CreateInspector(this)。
  • 節點和屬性生命週期 (RAII):檢查屬性是否為 RAII 物件。如果屬性會隨時間變更 (例如 requests_count_),請將屬性物件儲存為類別成員,或儲存在 inspect::ValueList 中。如果未讓屬性保持有效,系統會從檢查 VMO 中移除該屬性。如要使用不可變動的值,請使用 Record* 輔助方法 (例如 RecordString、RecordUint),這些方法會直接將值提交至 VMO,不需要物件控制代碼。
  • 元件健康狀態:使用 component_inspector_->Health().Ok()、.Starting() 或 .Unhealthy(reason) 回報標準元件健康狀態。

荒漠油廠

在實作 fdf_component::Driver 的 Rust 驅動程式中:

use fdf_component::{Driver, DriverContext, DriverError};
use fuchsia_async::Scope;
use fuchsia_inspect::{Inspector, NumericProperty, UintProperty};

pub struct MyDriver {
    // Keep the scope alive for the lifetime of the driver so that
    // the Inspect publishing task continues running.
    _inspect_scope: Scope,
    inspector: Inspector,
    requests_count: UintProperty,
}

impl Driver for MyDriver {
    const NAME: &str = "my_driver";

    async fn start(mut context: DriverContext) -> Result<Self, DriverError> {
        let inspector = Inspector::default();

        // Build hierarchy
        let hardware = inspector.root().create_child("hardware_status");
        hardware.record_string("serial_number", "ABC-1234");
        let requests_count = hardware.create_uint("requests_count", 0);
        inspector.root().record(hardware);

        // Publish Inspect via InspectSink.
        let inspect_scope = Scope::new_with_name("my_driver_inspect");
        context.publish_inspect(&inspector, inspect_scope.to_handle())?;

        Ok(Self {
            _inspect_scope: inspect_scope,
            inspector,
            requests_count,
        })
    }
}

impl MyDriver {
    pub fn handle_device_request(&self) {
        self.requests_count.add(1);
    }
}

重點:

  • 保留 Scope:context.publish_inspect 會在提供的 ScopeHandle 上產生背景工作。將 Scope 保存在驅動程式庫結構中,這樣 start() 完成時就不會取消工作。
  • 樹狀結構名稱:Rust 的 context.publish_inspect 會使用預設樹狀結構名稱 (None) 發布。與透過 CreateInspector 發布的 C++ 驅動程式不同,查詢時請勿傳遞 --name 樹狀結構篩選器,除非使用自訂發布選項。

查詢驅動程式庫檢查資料

由於驅動程式是元件,因此您可以使用標準的 Fuchsia 診斷指令查詢其檢查資料。

驅動程式代號

驅動程式元件的別名會反映裝置拓撲和驅動程式庫的執行位置:

  • 開機驅動程式:bootstrap/boot-drivers:<device_node_path> (例如 bootstrap/boot-drivers:dev.sys.platform.00_00_2d)
  • 封裝驅動程式 (基本):bootstrap/base-drivers:<device_node_path>
  • 封裝驅動程式 (完整/通用):bootstrap/full-drivers:<device_node_path>

使用ffx inspect

在主機開發工作站中:

  • 依驅動程式庫元件網址 / 元件名稱查詢: sh ffx inspect show my_driver.cm

  • 查詢所有驅動程式庫元件: sh ffx inspect show "bootstrap/*-drivers*:root"

  • 依 C++ 驅動程式庫樹狀結構名稱篩選: sh ffx inspect show --name my_driver "bootstrap/*-drivers*:root"

  • 依路徑名稱查詢特定驅動程式庫程式: 由於集合子項名稱是以半形冒號分隔,因此元件路徑名稱中的半形冒號必須在選取器中逸出: sh ffx inspect show "bootstrap/boot-drivers\:suspend"

使用 iquery 達成目標

透過序列或 ffx target ssh 連線至目標裝置時:

iquery show my_driver.cm
iquery show "bootstrap/*-drivers*:root"

測試驅動程式庫 Inspect

單元測試

在 C++ 驅動程式庫單元測試中,使用驅動程式單元測試程式庫,直接從驅動程式庫檢查器驗證檢查樹狀結構:

#include <lib/driver/testing/cpp/driver_test.h>
#include <lib/fpromise/single_threaded_executor.h>
#include <lib/inspect/cpp/reader.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <gtest/gtest.h>

TEST_F(MyDriverTestFixture, InspectMetrics) {
  // Run driver methods that modify inspect state...

  dut_.RunInDriverContext([&](MyDriver& driver) {
    auto hierarchy = fpromise::run_single_threaded(
        inspect::ReadFromInspector(driver.inspector()))
        .take_value();

    const auto* hw_node = hierarchy.GetByPath({"hardware_status"});
    ASSERT_NE(hw_node, nullptr);

    const auto* prop = hw_node->node().get_property<inspect::UintPropertyValue>("requests_count");
    ASSERT_NE(prop, nullptr);
    EXPECT_EQ(prop->value(), 1u);
  });
}

使用 DriverTestRealm 進行整合測試

在 DriverTestRealm 內執行的整合測試中,使用 diagnostics_reader::ArchiveReader 查詢 Inspect。請注意,在選取器字串中,分隔集合和子項路徑名稱半形冒號必須逸出 (\\:):

use diagnostics_assertions::assert_data_tree;
use diagnostics_reader::ArchiveReader;

let moniker = format!("realm_builder\\:{}/driver_test_realm/boot-drivers\\:dev.sys.my_node", instance.root.child_name());

let hierarchy = ArchiveReader::inspect()
    .add_selector(format!("{}:[name=my_driver]root", moniker))
    .snapshot()
    .await?
    .into_iter()
    .next()
    .and_then(|result| result.payload)
    .expect("driver inspect hierarchy not found");

assert_data_tree!(hierarchy, root: contains {
    hardware_status: contains {
        requests_count: 0u64,
    }
});