檢查快速入門導覽課程

本快速入門導覽課程會逐步說明如何使用元件檢查。您將瞭解如何使用特定語言的程式庫,將 Inspect 整合至元件,並使用 ffx inspect 檢查資料。

如要詳細瞭解檢查概念,請參閱「檢查程式碼研究室」。

專案設定

請參閱下方所選語言的快速入門指南:

C++

本節假設您要編寫非同步元件,且元件的某個部分 (通常是 main.cc) 如下所示:

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

這會設定非同步迴圈、建立由執行階段提供的 ComponentContext 包裝控制代碼,然後在完成其他初始化工作後執行該迴圈。

BUILD.gn 檔案中加入 Inspect 程式庫依附元件:

"//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{});

您現在可以使用「檢查」功能!將屬性附加至根節點,在檢查樹狀結構中建立屬性:

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

荒漠油廠

本節假設您要編寫非同步元件,且元件的某個部分 (通常是 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 檔案中加入 Inspect 程式庫依附元件:

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

您現在可以使用「檢查」功能!將屬性附加至根節點,在檢查樹狀結構中建立屬性:

// 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,可以開始建構階層。本節將說明一些重要概念和模式,協助您開始使用。

  • 節點可以有任意數量的鍵/值組合,稱為「屬性」
  • Value 的鍵一律為 UTF-8 字串,值則可以是下列支援的類型
  • 節點可以有任意數量的子項,這些子項也是節點。

C++

上述程式碼可讓您存取名為「root」的單一節點。hello_world_property 是包含字串值的 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 程式庫提供兩種管理節點和屬性的方式:建立和記錄。

使用 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 超出範圍為止。

動態值

本節介紹 Inspect 函式庫對讀取時延遲載入的節點的支援。這些方法會接受回呼函式,而非值。讀取屬性值時,系統會叫用回呼函式。

C++

C++ 函式庫有兩個用於動態值的屬性建立器:CreateLazyNodeCreateLazyValues

這兩種方法都會採用回呼,傳回 inspect::Inspector 的 Promise,唯一的差異在於動態值在樹狀結構中的儲存方式。

root->CreateLazyNode(name, callback) 會使用指定的 name,建立 root 的子節點。callback 會傳回 inspect::Inspector 的 Promise,讀取時,其根節點會併入父項階層。下面的範例顯示,存在一個名為“lazy”的子節點,其字串屬性為“version”,並且還有一個名為“lazy”的子節點。

root->CreateLazyValues(name, callback) 的工作方式與 root->CreateLazyNode(name, callback) 類似,只是承諾的根節點上的所有屬性和子節點都直接作為值添加到原始的 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_;
};

荒漠油廠

請參閱「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 Inspect 會自動移除重複的字串名稱。例如:

use fuchsia_inspect::Inspector;

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

只會產生 1 份 "child" 副本,並參照 100 次。

這樣一來,每個子節點可節省 16 個位元組,共用資料的費用則為 32 個位元組。最終節省了 1568 個位元組。

事件記錄和時間戳記

雖然「檢查」屬性通常代表元件的即時狀態,但元件通常需要記錄歷來事件 (例如連線嘗試、狀態轉換或錯誤)。

如要有效記錄滾動事件記錄和時間戳記,請按照下列步驟操作:

  • 請遵循@time命名慣例,讓開發人員清楚識別時間戳記,並讓 Fuchsia Snapshot Viewer (FSV) 剖析時間戳記。

  • 使用有界清單節點,維護事件的固定容量先進先出緩衝區,避免記憶體無限制成長。

時間戳記屬性慣例

Inspect 沒有專屬的時間戳記原始類型。時間戳記會改為以 64 位元整數屬性 (IntPropertyUintProperty) 記錄,通常代表奈秒或秒。Fuchsia 快照檢視器 (FSV) 不會對時間軸做出任何假設;使用者必須從情境中瞭解如何解讀時間戳記。

FSV 會剖析名為 @time 或以 @time 結尾的屬性鍵 (<prefix>@time),以便算繪使用者可讀取的日期,並計算經過的時間長度:

  • 事件時間戳記 (@time):使用確切的屬性鍵 @time,做為事件或狀態變更的主要時間戳記。

  • 間隔時間戳記 (start@timeend@time):在追蹤時間範圍內的時間戳記和持續時間時,請在屬性鍵 (例如 start@timeend@timecreated@timelast_seen@time) 上使用 @time 後置字元。

有界清單節點

有界清單節點會在父項節點下維護子節點的循環 FIFO 緩衝區。每個新事件都會以自動遞增的索引 ("0""1""2" 等) 命名,並新增為子節點。當清單達到容量上限時,建立新項目會自動移除最舊的項目。

荒漠油廠

在 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"

查看檢查資料

您可以使用 ffx inspect 指令,查看從元件匯出的檢查資料。

本節假設您已透過 SSH 存取正在執行的 Fuchsia 系統,並已開始執行元件。我們會使用名稱 my_component.cm 做為元件資訊清單名稱的預留位置。

讀取檢查資料

下列指令會列印系統中執行的所有元件檢查階層:

ffx inspect show

使用 ffx inspect list 的輸出內容,您可以指定單一元件 (例如 core/network/netstack) 做為 ffx inspect show 的輸入內容:

ffx inspect show core/network/netstack

您可以指定多個元件 (例如 core/font_providercore/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"

如果您不知道元件的路徑名稱,可以傳遞您認為與元件資訊清單、網址、路徑名稱等相關的字串。然後,這項工具會對所有元件進行模糊比對。如果找到多個相符項目,系統會要求消歧,否則會傳回預期輸出內容。

舉例來說,下列項目可能會傳回多個相符項目:

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 具有任意位元組值的屬性。 所有語言
節點 節點下方可能巢狀內嵌指標、屬性和更多節點。 所有語言
LazyNode 動態例項化完整的節點樹狀結構。 C++、Rust