本快速入門導覽課程將引導您瞭解使用元件檢查的基本知識。您將瞭解如何使用特定語言的程式庫將 Inspect 整合至元件,並使用 ffx inspect 查看資料。
如需 Inspect 概念的詳細逐步操作說明,請參閱 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:
#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))))));
荒漠油廠
本節假設您正在編寫非同步元件,且元件的某些部分 (通常是 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();
測試
如要測試檢查程式碼,可以使用 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 擁有房源。當它被刪除 (超出範圍) 時,基礎屬性也會被刪除,不再出現在元件的檢查輸出中。子節點也是如此。
如果您建立的值不需要修改,請使用 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 程式庫提供兩種管理節點和屬性的方式:建立和記錄。
使用 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) 類似,但承諾根節點上的所有屬性和子節點,都會直接新增為原始 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 個位元組。
事件記錄和時間戳記
雖然檢查屬性通常代表瞬時元件狀態,但元件通常需要記錄歷來事件 (例如連線嘗試、狀態轉換或錯誤)。
如要有效記錄滾動式事件記錄和時間戳記,請按照下列步驟操作:
請遵循
@time命名慣例,讓開發人員能清楚識別時間戳記,並由 Fuchsia 快照檢視器 (FSV) 剖析。使用有界限清單節點,維護固定容量的先進先出事件緩衝區,避免記憶體無限制成長。
時間戳記屬性慣例
「檢查」沒有專屬的時間戳記原始類型。而是記錄為 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 中,請使用 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 階層空間用盡時,建立新節點和屬性會失敗,但不會當機或記錄錯誤。
您可以設定自訂 VMO 容量,並透過提供 fuchsia.inspect.Stats 延遲節點來監控記憶體用量。
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();
荒漠油廠
如要增加單例模式元件檢查器的 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 指令,查看從元件匯出的檢查資料。
本節假設您有權透過 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/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 |