本快速入門導覽課程會逐步說明如何使用元件檢查功能。您將瞭解如何使用語言專屬程式庫,將檢查工具整合至元件,並使用 ffx inspect 檢查資料。
如要進一步瞭解「檢查」概念,請參閱 檢查程式碼研究室。
專案設定
請參閱下方的快速入門指南,選擇您偏好的語言:
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 <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(())
}
將 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());
您現在正在使用檢查功能!將屬性附加至根節點,即可在「檢查」樹狀圖中建立屬性:
// 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 擁有資源。當它遭到銷毀 (超出範圍) 時,基礎的 Property 就會遭到刪除,且不再出現在元件的「檢查」輸出內容中。子節點也是如此。
如果您建立的值不需要修改,請使用 ValueList 讓這些值持續存在,直到不再需要為止。
- 檢查作業會盡力完成。
由於空間有限,檢查程式庫可能無法滿足 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 程式庫提供兩種管理節點和屬性的方式:建立和記錄。
使用 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 超出範圍為止。
動態值
本節說明檢查程式庫支援在讀取時延遲加載的節點。這些方法會接受回呼函式,而非值。讀取屬性值時,系統會叫用回呼函式。
C++
C++ 程式庫有兩個屬性建立器,可用於動態值:CreateLazyNode 和 CreateLazyValues。
這兩種方法都會使用回呼,傳回 inspect::Inspector 的承諾,唯一的差異在於動態值如何儲存在樹狀圖中。
root->CreateLazyNode(name, callback) 會使用指定的 name 建立 root 的子節點。callback 會為 inspect::Inspector 傳回承諾,其根節點會在讀取時插入父項階層。以下範例顯示名為「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,刪除作業會遭到封鎖,直到回呼傳回承諾為止。
如要動態公開 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 個位元組。
查看檢查資料
您可以使用 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_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"
如果您不知道元件的路徑名稱,可以傳遞您認為與元件資訊清單、網址、路徑名稱等相關的字串。接著,工具會對所有元件進行模糊比對。如果找到多個相符項目,系統會要求您進行區分,否則會傳回您預期的輸出內容。
舉例來說,以下指令可能會傳回多個符合項目:
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 |