程式碼研究室:使用檢查

在這個程式碼研究室中,您將瞭解如何使用檢查 (Rust 和 C++) 發布程式的診斷資訊,以及如何使用檢查資訊來偵錯程式。

在本程式碼研究室中,您將修改程式,以便輸出檢查資料。您將學到:

  • 如何加入檢查程式庫。

  • 如何在元件中初始化「檢查」功能。

  • 如何透過寫入及讀取檢查資料,解決實際錯誤。

  • 如何讀取檢查資料,確認程式是否正常運作。

什麼是「檢查」?

檢查功能可讓 Fuchsia 元件公開有關其目前狀態的結構化階層資訊。

如要進一步瞭解「檢查」功能,請參閱 Fuchsia 元件檢查說明文件。

檢查工具有哪些優點?

元件檢查支援多種用途,包括:

  • 偵錯

    查看執行中元件的檢查資料,找出問題。舉例來說,您可以瞭解元件目前是否已連結至依附元件。

  • 監控系統健康狀態

    檢查資料可提供整體系統狀態的深入分析資訊。舉例來說,您可以瞭解系統無法連上網際網路的原因。

  • 收集使用或成效統計資料

    您可以同時讀取多個元件的檢查資料,瞭解系統效能。舉例來說,您可以查看元件收到的連線清單,以及元件的記憶體用量。

我可以在檢查中儲存哪些資訊?

您可以決定在檢查中公開的資料結構和內容。例如:

  • 開放的 Wi-Fi 連線數。
  • 程式已放送的要求數量。
  • 剖析器遇到的錯誤數量。
  • 資料結構的內容。

API 參考資料

必備事項

  • 具備 Rust 或 C++ 的基本知識。
  • 存取可執行建構指令的 Fuchsia 來源樹狀結構。

原始碼

您可以前往以下網址取得程式碼:

這個程式碼研究室分為多個部分,每個部分都有各自的子目錄。程式碼研究室的起點是第 1 部分,每個部分的程式碼都包含先前部分的解決方案。

在本程式碼研究室中,您可以繼續將解決方案新增至「part_1」,也可以跳過,並在現有解決方案的基礎上進行建構。

必要條件

設定開發環境。

本程式碼研究室假設您已完成「開始」課程,並具備以下條件:

  1. 已簽出並建構的 Fuchsia 樹狀結構。
  2. 搭載 Fuchsia 的裝置或模擬器 (ffx emu)。
  3. 工作站,可為 Fuchsia 裝置或模擬器提供元件 (fx serve)。

如要建構並執行本程式碼研究室中的範例,請將下列引數新增至 fx set 叫用:

C++

fx set core.x64 \
--with //examples/diagnostics/inspect/codelab/cpp \
--with //examples/diagnostics/inspect/codelab/cpp:tests

荒漠油廠

fx set core.x64 \
--with //examples/diagnostics/inspect/codelab/rust \
--with //examples/diagnostics/inspect/codelab/rust:tests

第 1 部分:有錯誤的元件

有一個元件會提供名為 Reverser 的通訊協定:

// Implementation of a string reverser.
@discoverable
closed protocol Reverser {
    // Returns the input string reversed character-by-character.
    strict Reverse(struct {
        input string:1024;
    }) -> (struct {
        response string:1024;
    });
};

這個通訊協定只有一個方法,稱為 Reverse,這個方法只會將傳入的任何字串倒轉。提供通訊協定的實作項目,但有重大錯誤。這個錯誤會導致嘗試呼叫 Reverse 方法的用戶端,看到其呼叫無限期掛起。您必須自行修正這個錯誤。

執行元件

有個用戶端應用程式會啟動 Reverser 元件,並將其餘指令列引數以字串形式傳送至 Reverse:

  1. 查看用量

    您可以根據要執行的程式講義部分,啟動 client_i 元件,其中 i 是介於 [1, 5] 範圍內的數字。舉例來說,如要啟動與程式碼研究室第 2 部分的反轉器對話的用戶端,請執行下列操作:

    C++

    ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_2.cm

    荒漠油廠

    ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_2.cm
  2. 執行第 1 部分程式碼,並將字串「Hello」倒轉

    如要只指定單一字串「Hello」,請修改 common.shard.cmlprogram.args 部分,並建構及執行以下內容:

    C++

    ffx component run /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm

    如要查看指令輸出內容,請查看記錄:

    ffx log --tags inspect_cpp_codelab

    這個指令會輸出一些含有錯誤的內容。

    荒漠油廠

    ffx component run /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm

    如要查看指令輸出內容,請查看記錄:

    ffx log --tags inspect_rust_codelab

    我們在記錄中看到,元件取得的輸入內容為「Hello」,但我們沒有看到正確的反轉輸出內容。

    如您在記錄中看到的,反轉器無法正常運作。

  3. 請嘗試搭配更多引數執行用戶端:

    將字串「World」新增至 common.shard.cmlprogram.args 部分:

    {
        program: {
            args: [
                "Hello",
                "World",
            ],
        },
    }
    

    建構並執行以下項目:

    C++

     ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm
     ```

    荒漠油廠

     ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm
     ```

    我們可以看到元件列印了第一個輸入內容,但沒有看到預期的輸出內容,也沒有第二個輸入內容。

您現在可以查看程式碼來排解問題。

查看程式碼

您現在可以重現問題,請查看用戶端的運作情形:

C++

client main 中:

// Repeatedly send strings to be reversed to the other component.
for (int i = 1; i < argc; i++) {
  FX_LOGS(INFO) << "Input: " << argv[i];

  std::string output;
  status = zx::make_result(reverser->Reverse(argv[i], &output));
  if (status.is_error()) {
    FX_LOGS(ERROR) << "Error: Failed to reverse string.";
    return status.status_value();
  }

  FX_LOGS(INFO) << "Output: " << output;
}

荒漠油廠

client main 中:

for string in args.strings {
    info!("Input: {}", string);
    match reverser.reverse(&string).await {
        Ok(output) => info!("Output: {}", output),
        Err(e) => error!(error = ?e, "Failed to reverse string"),
    }
}

在這個程式碼片段中,用戶端會呼叫 Reverse 方法,但似乎從未收到回應。似乎沒有錯誤訊息或輸出內容。

請查看程式碼研究室中這個部分的伺服器程式碼。您可以設定許多標準元件:

C++

第 1 部分主內容中:

  • 記錄初始化

    fuchsia_logging::LogSettingsBuilder builder;
    builder.WithTags({"inspect_cpp_codelab", "part1"}).BuildAndInitialize();
    
  • 建立非同步執行緒

    async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
    auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
    
  • 提供公共服務

    context->outgoing()->AddPublicService(Reverser::CreateDefaultHandler());
    

荒漠油廠

第 1 部分主內容中:

  • 記錄初始化

    #[fuchsia::main(logging_tags = ["inspect_rust_codelab", "part1"])]
    
  • ServiceFs 初始化

    let mut fs = ServiceFs::new();
    
  • ServiceFs 集合

    let running_service_fs = fs.collect::<()>().map(Ok);
    
  • 提供公共服務

    fs.dir("svc").add_fidl_service(move |stream| reverser_factory.spawn_new(stream));
    fs.take_and_serve_directory_handle()?;
    

查看反轉器定義:

C++

reverser.h 中:

class Reverser final : public fuchsia::examples::inspect::Reverser {
 public:
  // CODELAB: Create a new constructor for Reverser that takes an Inspect node.

  // Implementation of Reverser.Reverse().
  void Reverse(std::string input, ReverseCallback callback) override;

  // Return a request handler for the Reverser protocol that binds incoming requests to new
  // Reversers.
  static fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> CreateDefaultHandler();
};

此類別會實作 Reverser 通訊協定。名為 CreateDefaultHandler 的輔助方法會建構 InterfaceRequestHandler,為傳入要求建立新的 Reverser

荒漠油廠

reverser.rs 中:

pub struct ReverserServerFactory {}

impl ReverserServerFactory {
    // CODELAB: Create a new() constructor that takes an Inspect node.
    pub fn new() -> Self {
        Self {}
    }

    pub fn spawn_new(&self, stream: ReverserRequestStream) {
        // CODELAB: Add stats about incoming connections.
        ReverserServer::new().spawn(stream);
    }
}

struct ReverserServer {}

impl ReverserServer {
    // CODELAB: Create a new() constructor that takes an Inspect node.
    fn new() -> Self {
        Self {}
    }

    pub fn spawn(self, mut stream: ReverserRequestStream) {
        fasync::Task::local(async move {
            while let Some(request) = stream.try_next().await.expect("serve reverser") {
                // CODELAB: Add stats about incoming requests.
                let ReverserRequest::Reverse { input, responder: _ } = request;
                let _result = input.chars().rev().collect::<String>();
                // Yes, this is silly. Just for codelab purposes.
                fasync::Timer::new(fasync::MonotonicInstant::after(
                    zx::MonotonicDuration::from_hours(10),
                ))
                .await
            }
        })
        .detach();
    }
}

這個結構體可提供 Reverser 通訊協定。當建立與 Reverser 的新連線時,ReverserServerFactory (稍後會更清楚) 會建構 ReverserServer

新增檢查

您現在已瞭解程式碼結構,可以開始使用檢查工具檢測程式碼,找出問題所在。

您可能先前曾透過列印或記錄來偵錯程式。雖然這通常很有效,但持續執行的非同步元件經常會隨著時間的推移,輸出許多內部狀態記錄。本程式碼研究室說明如何使用「檢查」功能,不必深入研究記錄檔,即可取得元件目前狀態的快照。

  1. 包含檢查依附元件:

    C++

    BUILD.gn 中:

    source_set("lib") {
      sources = [
        "reverser.cc",
        "reverser.h",
      ]
    
      public_deps = [
        "//examples/diagnostics/inspect/codelab/fidl:fuchsia.examples.inspect_hlcpp",
        "//sdk/lib/inspect/component/cpp",
      ]
    }
    
    

    荒漠油廠

    rustc_binary("bin") 底下的 deps 中,在 BUILD.gn 中:

    "//src/lib/diagnostics/inspect/runtime/rust",
    "//src/lib/diagnostics/inspect/rust",
    "//src/lib/fuchsia",
    
    
  2. 初始化 Inspect:

    C++

    main.cc 中:

    #include <lib/inspect/component/cpp/component.h>
    inspect::ComponentInspector inspector(loop.dispatcher(), {});
    

    荒漠油廠

    main.rs 中:

    use fuchsia_inspect::component;
    let _inspect_server_task = inspect_runtime::publish(
        component::inspector(),
        inspect_runtime::PublishOptions::default(),
    );
    
    

    您目前正在使用檢查功能。

  3. 新增簡單的「version」屬性,顯示正在執行的版本:

    C++

    inspector.root().RecordString("version", "part2");
    

    這個程式碼片段會執行以下操作:

    1. 取得檢查階層的「root」節點。

      元件的檢查階層由節點樹狀結構組成,每個節點都包含任意數量的屬性。

    2. 使用 CreateString 建立新資源。

      這會在根目錄中新增 StringProperty。這個 StringProperty 稱為「version」,其值為「part2」。我們將屬性設為「part1」。

    3. 在檢查器中放置新屬性。

      屬性的生命週期與 Create 傳回的物件相關聯,因此摧毀物件會導致屬性消失。選用的第三個參數會將新屬性放入 inspector 中,而不是傳回。因此,新屬性會與檢查器本身 (元件的整個執行作業) 一樣長壽。

    荒漠油廠

    component::inspector().root().record_string("version", "part2");
    

    這個程式碼片段會執行以下操作:

    1. 取得檢查階層的「root」節點。

    元件的檢查階層由節點樹狀結構組成,每個節點都包含任意數量的屬性。

    1. 使用 record_string 建立新資源。

    這會在根目錄中新增 StringProperty。這個 StringProperty 稱為「version」,其值為「part2」。我們將屬性設為「part1」。

    1. 並將其記錄在根節點中。

    建立屬性的常用方法是透過節點上的 create_* 方法。使用這些方法建立的屬性生命週期會與傳回的物件綁定,因此毀損物件會導致屬性消失。程式庫提供便利方法 record_*,可執行屬性建立作業,並將屬性生命週期繫結至呼叫方法的節點。因此,只要節點本身存在,新屬性就會持續存在 (在本例中,只要根節點存在,元件就會持續執行)。

讀取檢查資料

在元件中加入「檢查」後,您可以查看該元件顯示的內容:

  1. 重新建構及更新目標系統

    fx build && fx ota
  2. 執行用戶端:

    C++

    ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm
    ffx log --tags inspect_cpp_codelab

    荒漠油廠

    ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm
    ffx log --tags inspect_rust_codelab
  3. 使用 ffx inspect 查看輸出內容:

    ffx inspect show

    這會轉儲整個系統的所有檢查資料,可能會產生大量資料。

  4. 由於 ffx inspect 支援 glob 比對,請執行以下命令:

    C++

    ffx inspect show 'core/ffx-laboratory\:client_part_1/reverser'
    # or `ffx inspect show --component inspect_cpp_codelab`
    metadata:
      filename = fuchsia.inspect.Tree
      component_url = fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/part_1.cm
      timestamp = 4728864898476
    payload:
      root:
        version = part1

    荒漠油廠

    ffx inspect show 'core/ffx-laboratory\:client_part_1/reverser'
    # or `ffx inspect show --component inspect_rust_codelab`
    metadata:
      filename = fuchsia.inspect.Tree
      component_url = fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/part_1.cm
      timestamp = 4728864898476
    payload:
      root:
        version = part1
  5. 您也可以將輸出內容設為 JSON:

    C++

    ffx --machine json-pretty inspect show 'core/ffx-laboratory\:client_part_1/reverser'

    您應該會看到類似以下的輸出內容:

    [
      {
        "data_source": "Inspect",
        "metadata": {
          "errors": null,
          "filename": "fuchsia.inspect.Tree",
          "component_url": "fuchsia-pkg://fuchsia.com/inspect_pp_codelab#meta/part_1.cm",
          "timestamp": 5031116776282
        },
        "moniker": "core/ffx-laboratory\\:client_part_5/reverser",
        "payload": {
          "root": {
            "version": "part1",
        },
        "version": 1
      }
    ]
    

    荒漠油廠

    ffx --machine json-pretty inspect show 'core/ffx-laboratory\:client_part_1/reverser'

    您應該會看到類似以下的輸出內容:

    [
      {
        "data_source": "Inspect",
        "metadata": {
          "errors": null,
          "filename": "fuchsia.inspect.Tree",
          "component_url": "fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/part_1.cm",
          "timestamp": 5031116776282
        },
        "moniker": "core/ffx-laboratory\\:client_part_5/reverser",
        "payload": {
          "root": {
            "version": "part1",
        },
        "version": 1
      }
    ]
    

檢測程式碼以找出錯誤

您已完成 Inspect 的初始化,也知道如何讀取資料,因此可以開始檢測程式碼並找出錯誤。

先前的輸出內容會顯示元件實際執行的方式,以及元件並未完全掛起。否則「檢查」讀取作業會停止運作。

為每個連線新增新資訊,觀察連線是否由元件處理。

  1. 在根節點中新增子項,以便納入 reverser 服務的統計資料:

    C++

    context->outgoing()->AddPublicService(
        Reverser::CreateDefaultHandler(inspector.root().CreateChild("reverser_service")));
    

    荒漠油廠

    let reverser_factory =
        ReverserServerFactory::new(component::inspector().root().create_child("reverser_service"));
    
  2. 更新伺服器以接受這個節點:

    C++

    更新 reverser.hreverser.cc 中的 CreateDefaultHandler 定義:

    #include <lib/inspect/cpp/inspect.h>
    fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> Reverser::CreateDefaultHandler(
        inspect::Node node) {
    

    荒漠油廠

    更新 ReverserServerFactory::new,以便在 reverser.rs 中接受這個節點:

    use fuchsia_inspect as inspect;
    pub struct ReverserServerFactory {
        node: inspect::Node,
        // ...
    }
    
    impl ReverserServerFactory {
        pub fn new(node: inspect::Node) -> Self {
            // ...
            Self {
                node,
                // ...
            }
        }
    
        // ...
    }
    
  3. 新增資源來追蹤連線數量:

    C++

    fidl::InterfaceRequestHandler<fuchsia::examples::inspect::Reverser> Reverser::CreateDefaultHandler(
        inspect::Node node) {
      // ...
    
      // Return a handler for incoming FIDL connections to Reverser.
      //
      // The returned closure contains a binding set, which is used to bind incoming requests to a
      // particular implementation of a FIDL interface. This particular binding set is configured to
      // bind incoming requests to unique_ptr<Reverser>, which means the binding set itself takes
      // ownership of the created Reversers and frees them when the connection is closed.
      return [connection_count = node.CreateUint("connection_count", 0), node = std::move(node),
              // ...
    

    荒漠油廠

    use {
        // ...
        fuchsia_inspect::NumericProperty,
        // ...
    };
    
    pub struct ReverserServerFactory {
        node: inspect::Node,
        // ...
        connection_count: inspect::UintProperty,
    }
    
    impl ReverserServerFactory {
        pub fn new(node: inspect::Node) -> Self {
            // ...
            let connection_count = node.create_uint("connection_count", 0);
            Self {
                node,
                // ...
                connection_count,
            }
        }
    
        pub fn spawn_new(&self, stream: ReverserRequestStream) {
            self.connection_count.add(1);
    

    這個程式碼片段示範如何建立名為 connection_count 的新 UintProperty (包含 64 位元未簽署的 int),並將其設為 0。在處理常式 (針對每個連線執行) 中,屬性會遞增 1。

  4. 重新建構及執行元件,然後執行 ffx inspect

    C++

    ffx --machine json-pretty inspect show --component inspect_cpp_codelab

    荒漠油廠

    ffx --machine json-pretty inspect show --component inspect_rust_codelab

    您現在應該會看到:

    ...
    "payload": {
     "root": {
       "version": "part1",
       "reverser_service": {
         "connection_count": 1,
       }
     }
    }
    

上述輸出內容顯示用戶端已成功連線至服務,因此掛起的問題一定是 Reverser 實作本身造成的。特別是,您需要瞭解下列資訊:

  1. 如果連線在用戶端掛起時仍處於開啟狀態。

  2. 是否已呼叫 Reverse 方法。

練習:為每個連線建立子節點,並在 Reverser 中記錄「request_count」。

  • 提示:有個實用功能可產生不重複的名稱:

    C++

    auto child = node.CreateChild(node.UniqueName("connection-"));
    

    荒漠油廠

    let node = self.node.create_child(inspect::unique_name("connection"));
    

    這會建立開頭為「connection」的專屬名稱。

C++

提示:建議您為 Reverser 建立建構函式,以便接收 inspect::Node。本程式碼研究室的第 3 部分會說明這為何是實用的模式。

荒漠油廠

提示:您會發現,為 ReverserServer 建立建構函式,並讓該函式接收 inspect::Node 會很有幫助,這與我們為 ReverserServerFactory 建立建構函式時的做法相同。

  • 提示:您需要在 Reverser 上建立成員,以便保留 request_count 屬性。其類型為 inspect::UintProperty

  • 後續追蹤:請問請求數量是否提供您所需的所有資訊?也新增 response_count

  • 進階:您可以在所有連線上新增所有要求的計數嗎?Reverser 物件必須共用部分狀態。您可能會發現,將 Reverser 的引數重構為個別結構體會很有幫助 (如要瞭解這個方法,請參閱第 2 部分的解決方案)。

完成本練習並執行 ffx inspect 後,您應該會看到類似以下的內容:

...
"payload": {
  "root": {
    "version": "part1",
    "reverser_service": {
      "connection_count": 1,
      "connection0": {
        "request_count": 1,
      }
    }
  }
}

上述輸出結果顯示連線仍處於開啟狀態,且已收到一項要求。

C++

如果您也新增了「response_count」,可能會發現這個錯誤。Reverse 方法會接收 callback,但不會以 output 的值呼叫。

荒漠油廠

如果您也新增了「response_count」,可能會發現這個錯誤。Reverse 方法會接收 responder,但不會以 result 的值呼叫。

  1. 傳送回應:

    C++

    // At the end of Reverser::Reverse
    callback(std::move(output));
    

    荒漠油廠

    responder.send(&result).expect("send reverse request response");
    
  2. 再次執行用戶端:

    C++

    ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_1.cm

    您應該會看到類似以下的輸出內容:

    Creating component instance: client_part_1
    
    ffx log --tags inspect_cpp_codelab
    [00039.129068][39163][39165][inspect_cpp_codelab, client] INFO: Input: Hello
    [00039.194151][39163][39165][inspect_cpp_codelab, client] INFO: Output: olleH
    [00039.194170][39163][39165][inspect_cpp_codelab, client] INFO: Input: World
    [00039.194402][39163][39165][inspect_cpp_codelab, client] INFO: Output: dlroW
    [00039.194407][39163][39165][inspect_cpp_codelab, client] INFO: Done reversing! Please use `ffx component stop`
    

    荒漠油廠

    ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm

    您應該會看到類似以下的輸出內容:

    Creating component instance: client_part_1
    
    ffx log --tags inspect_rust_codelab
    [00039.129068][39163][39165][inspect_rust_codelab, client] INFO: Input: Hello
    [00039.194151][39163][39165][inspect_rust_codelab, client] INFO: Output: olleH
    [00039.194170][39163][39165][inspect_rust_codelab, client] INFO: Input: World
    [00039.194402][39163][39165][inspect_rust_codelab, client] INFO: Output: dlroW
    [00039.194407][39163][39165][inspect_rust_codelab, client] INFO: Done reversing! Please use `ffx component stop`
    

元件會繼續執行,直到您執行 ffx component stop 為止。只要元件執行,您就可以執行 ffx inspect 並觀察輸出內容。

這就是第 1 部分的內容。您也可以選擇提交變更:

git commit -am "solution to part 1"

第 2 部分:診斷元件間的問題

您收到錯誤報告。「FizzBuzz」團隊表示,他們無法從您的元件接收資料。

除了提供 Reverser 通訊協定外,元件也會連線至「FizzBuzz」服務,並列印回應:

C++

fuchsia::examples::inspect::FizzBuzzPtr fizz_buzz;
context->svc()->Connect(fizz_buzz.NewRequest());
fizz_buzz->Execute(30, [](std::string result) { FX_LOGS(INFO) << "Got FizzBuzz: " << result; });

荒漠油廠

let fizzbuzz_fut = async move {
    let fizzbuzz = client::connect_to_protocol::<FizzBuzzMarker>()
        .context("failed to connect to fizzbuzz")?;
    match fizzbuzz.execute(30u32).await {
        Ok(result) => info!(%result, "Got FizzBuzz"),
        Err(_) => {}
    };
    Ok(())
};

如果您看到記錄,就會發現這個記錄從未列印。

C++

ffx log --tags inspect_cpp_codelab

荒漠油廠

ffx log --tags inspect_rust_codelab

您必須診斷並解決這個問題。

使用檢查工具診斷問題

  1. 執行元件,看看會發生什麼事:

    C++

    ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_cpp_codelab#meta/client_part_2.cm

    荒漠油廠

    ffx component run /core/ffx-laboratory:client_part_2 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_2.cm

    幸運的是,FizzBuzz 團隊使用 Inspect 為元件加入檢測功能。

  2. 使用 ffx inspect 讀取 FizzBuzz 檢查資料,您應該會看到:

    "payload": {
        "root": {
            "fizzbuzz_service": {
                "closed_connection_count": 0,
                "incoming_connection_count": 0,
                "request_count": 0,
                ...
    

    這項輸出內容表示 FizzBuzz 沒有收到任何連線。

  3. 新增檢查功能來找出問題:

    C++

    // CODELAB: Instrument our connection to FizzBuzz using Inspect. Is there an error?
    fuchsia::examples::inspect::FizzBuzzPtr fizz_buzz;
    context->svc()->Connect(fizz_buzz.NewRequest());
    fizz_buzz.set_error_handler([&](zx_status_t status) {
      // CODELAB: Add Inspect here to see if there is a response.
    });
    fizz_buzz->Execute(30, [](std::string result) {
      // CODELAB: Add Inspect here to see if there was a response.
      FX_LOGS(INFO) << "Got FizzBuzz: " << result;
    });
    

    荒漠油廠

    let fizzbuzz_fut = async move {
        let fizzbuzz = client::connect_to_protocol::<FizzBuzzMarker>()
            .context("failed to connect to fizzbuzz")?;
        match fizzbuzz.execute(30u32).await {
            Ok(result) => {
                // CODELAB: Add Inspect here to see if there is a response.
                info!(%result, "Got FizzBuzz");
            }
            Err(_) => {
                // CODELAB: Add Inspect here to see if there is an error
            }
        };
        Ok(())
    };
    

練習題:在 FizzBuzz 連線中加入檢查功能,找出問題

  • 提示:請使用上述程式碼片段做為起點,該程式碼片段會為連線嘗試提供錯誤處理常式。

C++

後續追蹤:您可以將狀態儲存在某處嗎?您可以使用 zx_status_get_string(status) 將其轉換為字串。

進階inspector 有一個名為 Health() 的方法,可在特殊位置宣告整體健康狀態。我們的服務必須連上 FizzBuzz,否則無法正常運作,請問您能否加入以下內容:

 /*
 "fuchsia.inspect.Health": {
     "status": "STARTING_UP"
 }
 */
 inspector.Health().StartingUp();

 /*
 "fuchsia.inspect.Health": {
     "status": "OK"
 }
 */
 inspector.Health().Ok();

 /*
 "fuchsia.inspect.Health": {
     "status": "UNHEALTHY",
     "message": "Something went wrong!"
 }
 */
 inspector.Health().Unhealthy("Something went wrong!");

荒漠油廠

進階fuchsia_inspect::component 設有名為 health() 的函式,該函式會傳回物件,在特殊位置 (檢查樹狀結構根節點的子節點) 宣告整體健康狀態。我們的服務必須連上 FizzBuzz,否則無法正常運作,因此請您納入以下內容:

/*
"fuchsia.inspect.Health": {
    "status": "STARTING_UP"
}
*/
fuchsia_inspect::component::health().set_starting_up();

/*
"fuchsia.inspect.Health": {
    "status": "OK"
}
*/
fuchsia_inspect::component::health().set_ok();

/*
"fuchsia.inspect.Health": {
    "status": "UNHEALTHY",
    "message": "Something went wrong!"
}
*/
fuchsia_inspect::component::health().set_unhealthy("something went wrong!");

完成這項練習後,您應該會看到系統呼叫連線錯誤處理常式,並顯示「not found」錯誤。檢查輸出內容後,我們發現 FizzBuzz 正在執行,因此可能有某些項目設定錯誤。很抱歉,目前並非所有項目都會使用檢查功能 (至少目前還沒有),因此請查看記錄:

C++

ffx log --filter FizzBuzz

您應該會看到類似以下的輸出內容:

...
...  No capability available at path /svc/fuchsia.examples.inspect.FizzBuzz
for component /core/ffx-laboratory:client_part_2/reverser, verify the
component has the proper `use` declaration. ...

荒漠油廠

ffx log --filter FizzBuzz

您應該會看到類似以下的輸出內容:

...
... No capability available at path /svc/fuchsia.examples.inspect.FizzBuzz
for component /core/ffx-laboratory:client_part_2/reverser, verify the
component has the proper `use` declaration. ...

沙箱錯誤是常見的陷阱,有時很難發現。

查看 part2 的元資料,您會發現其中缺少服務:

C++

Fizzbuzzuse 項目新增至 part_2/meta

use: [
    { protocol: "fuchsia.examples.inspect.FizzBuzz" },
],

荒漠油廠

Fizzbuzzuse 項目新增至 part_2/meta

use: [
    { protocol: "fuchsia.examples.inspect.FizzBuzz" },
],

新增「fuchsia.examples.inspect.FizzBuzz」後,請重新建構並再次執行。您現在應該會在記錄中看到 FizzBuzz 和「OK」狀態:

C++

ffx log --tags inspect_cpp_codelab

您應該會看到類似以下的輸出內容:

[inspect_cpp_codelab, part2] INFO: main.cc(57): Got FizzBuzz: 1 2 Fizz
4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz
22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz

荒漠油廠

ffx log --tags inspect_rust_codelab

您應該會看到類似以下的輸出內容:

[inspect_rust_codelab, part2] INFO: main.rs(52): Got FizzBuzz: 1 2 Fizz
4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz
22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz

這就是第 2 部分的內容。

您現在可以選擇提交解決方案:

git commit -am "solution for part 2"

第 3 部分:檢查的單元測試

您應測試 Fuchsia 上的所有程式碼,這也適用於檢查資料。

雖然一般來說,不必測試檢查資料,但您需要測試其他工具 (例如分類或意見回饋) 所依賴的檢查資料。

Reverser 有基本單元測試。執行方式:

C++

單元測試位於 reverser_unittests.cc 中。

fx test inspect_cpp_codelab_unittests

荒漠油廠

單元測試位於 reverser.rs > mod tests

fx test inspect_rust_codelab_unittests

單元測試可確保 Reverser 正常運作 (且不會掛起),但不會檢查檢查輸出內容是否符合預期。

將節點傳遞至建構函式,是一種依附元件植入形式,可讓您傳遞依附元件的測試版本,以便檢查其狀態。

開啟 Reverser 的程式碼如下所示:

C++

binding_set_.AddBinding(std::make_unique<Reverser>(ReverserStats::CreateDefault()),
                        ptr.NewRequest());
// Alternatively
binding_set_.AddBinding(std::make_unique<Reverser>(inspect::Node()),
                        ptr.NewRequest());

荒漠油廠

let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<ReverserMarker>();
let reverser = ReverserServer::new(ReverserServerMetrics::default());
reverser.spawn(stream);

檢查節點的預設版本會傳遞至 Reverser。這可讓反轉程式碼在測試中正常執行,但不支援在檢查輸出內容上進行斷言。

C++

練習:變更 OpenReverser,將 Reverser 的依附元件做為引數,並在建構 Reverser 時使用該依附元件。

  • 提示:在測試函式中建立 inspect::Inspector。您可以使用 inspector.GetRoot() 取得根目錄。

  • 提示:您需要在根層級建立子項,才能傳遞至 OpenReverser

荒漠油廠

練習:變更 open_reverser,將 ReverserServerFactory 的依附元件做為引數,並在建構 Reverser 時使用該依附元件。

  • 提示:在測試函式中建立 fuchsia_inspect::Inspector。您可以使用 inspector.root() 取得根目錄。

  • 注意:請勿在測試中直接使用 component::inspector(),這會建立一個靜態檢查器,並在所有測試中保持運作,可能導致異常或非預期的行為。對於單元測試,建議一律使用新的 fuchsia_inspect::Inspector

  • 提示:您需要在根層級建立子項,才能傳遞至 ReverserServerFactory::new

後續追蹤:建立多個反轉連線,並個別測試。

在本練習中,單元測試會在「檢查」階層中設定實際值。

新增程式碼,在「檢查」中測試輸出內容:

C++

#include <lib/inspect/testing/cpp/inspect.h>
fpromise::result<inspect::Hierarchy> hierarchy =
    RunPromise(inspect::ReadFromInspector(inspector));
ASSERT_TRUE(hierarchy.is_ok());

上述程式碼片段會讀取含有檢查資料的基礎虛擬記憶體物件 (VMO),並將其剖析為可讀取的階層。

您現在可以讀取個別的資源和子項,如下所示:

auto* global_count =
    hierarchy.value().node().get_property<inspect::UintPropertyValue>("request_count");
ASSERT_TRUE(global_count);
EXPECT_EQ(3u, global_count->value());

auto* connection_0 = hierarchy.value().GetByPath({"connection_0x0"});
ASSERT_TRUE(connection_0);
auto* requests_0 =
    connection_0->node().get_property<inspect::UintPropertyValue>("request_count");
ASSERT_TRUE(requests_0);
EXPECT_EQ(2u, requests_0->value());

荒漠油廠

use diagnostics_assertions::assert_data_tree;
let inspector = inspect::Inspector::default();
assert_data_tree!(inspector, root: {
    reverser_service: {
        total_requests: 3u64,
        connection_count: 2u64,
        "connection0": {
            request_count: 2u64,
            response_count: 2u64,
        },
        "connection1": {
            request_count: 1u64,
            response_count: 1u64,
        },
    }
});

上述程式碼片段會從含有檢查資料的基礎虛擬記憶體物件 (VMO) 讀取快照,並將其剖析為可讀的階層。

練習:為其他檢查資料新增斷言。

本節結束。

您也可以選擇提交變更:

git commit -am "solution to part 3"

第 4 部分:檢查的整合測試

整合測試是 Fuchsia 軟體開發工作流程中的重要環節。整合測試可讓您觀察實際元件在系統上執行時的行為。

執行整合測試

您可以按照下列步驟,為程式碼研究室執行整合測試:

C++

fx test inspect_cpp_codelab_integration_tests

荒漠油廠

fx test inspect_rust_codelab_integration_tests

這會為本程式碼研究室的所有部分執行整合測試。

查看程式碼

請參閱以下說明,瞭解如何設定整合測試:

  1. 查看整合測試的元件資訊清單:

    C++

    part_4/meta 中找出元件資訊清單 (cml)

    荒漠油廠

    part_4/meta 中找出元件資訊清單 (cml)

    畫面上會顯示下列訊息:

    {
       ...
       use: [
          { protocol: "fuchsia.diagnostics.ArchiveAccessor" },
       ]
    }
    

    這個檔案使用父項的通訊協定 fuchsia.diagnostics.ArchiveAccessor。所有測試都可以使用這個通訊協定,讀取測試領域下所有元件的診斷資訊。

  2. 請查看整合測試本身。個別測試案例相當簡單:

    C++

    part4/tests/integration_test.cc 中找出整合測試。

    TEST_F(IntegrationTestPart4, StartWithFizzBuzz) {
      auto ptr = ConnectToReverser({.include_fizzbuzz = true});
    
      bool error = false;
      ptr.set_error_handler([&](zx_status_t unused) { error = true; });
    
      bool done = false;
      std::string result;
      ptr->Reverse("hello", [&](std::string value) {
        result = std::move(value);
        done = true;
      });
      RunLoopUntil([&] { return done || error; });
    
      ASSERT_FALSE(error);
      EXPECT_EQ("olleh", result);
    
      // CODELAB: Check that the component was connected to FizzBuzz.
    }
    

    StartComponentAndConnect 負責建立新的測試環境,並在其中啟動程式碼研究室元件。include_fizzbuzz_service 選項會指示方法選擇性納入 FizzBuzz。如果這項功能無法連線至 FizzBuzz,這項功能會測試檢查輸出內容是否正常,就像第 2 部分一樣。

    荒漠油廠

    part4/tests/integration_test.rs 中找出整合測試。

    #[fuchsia::test]
    async fn start_with_fizzbuzz() -> Result<(), Error> {
        let test = IntegrationTest::start(4, TestOptions::default()).await?;
        let reverser = test.connect_to_reverser()?;
        let result = reverser.reverse("hello").await?;
        assert_eq!(result, "olleh");
    
        // CODELAB: Check that the component was connected to FizzBuzz.
    
        Ok(())
    }
    

    IntegrationTest::start 負責建立新的測試環境,並在其中啟動程式碼研究室元件。include_fizzbuzz 選項會指示方法選擇性啟動 FizzBuzz 元件。如果無法連線至 FizzBuzz,這項功能會測試檢查輸出內容是否正常,如第 2 部分所述。

  3. 請在測試固件中加入下列方法,以便讀取 ArchiveAccessor 服務:

    C++

    #include <rapidjson/document.h>
    #include <rapidjson/pointer.h>
    std::string GetInspectJson() {
      fuchsia::diagnostics::ArchiveAccessorPtr archive;
      auto svc = sys::ServiceDirectory::CreateFromNamespace();
      svc->Connect(archive.NewRequest());
    
      while (true) {
        ContentVector current_entries;
    
        fuchsia::diagnostics::BatchIteratorPtr iterator;
        fuchsia::diagnostics::StreamParameters stream_parameters;
        stream_parameters.set_data_type(fuchsia::diagnostics::DataType::INSPECT);
        stream_parameters.set_stream_mode(fuchsia::diagnostics::StreamMode::SNAPSHOT);
        stream_parameters.set_format(fuchsia::diagnostics::Format::JSON);
    
        {
          std::vector<fuchsia::diagnostics::SelectorArgument> args;
          args.emplace_back();
          args[0].set_raw_selector(ReverserMonikerForSelectors() + ":root");
    
          fuchsia::diagnostics::ClientSelectorConfiguration client_selector_config;
          client_selector_config.set_selectors(std::move(args));
          stream_parameters.set_client_selector_configuration(std::move(client_selector_config));
        }
        archive->StreamDiagnostics(std::move(stream_parameters), iterator.NewRequest());
    
        bool done = false;
        iterator->GetNext([&](auto result) {
          if (result.is_response()) {
            current_entries = std::move(result.response().batch);
          }
    
          done = true;
        });
    
        RunLoopUntil([&] { return done; });
    
        // Should be at most one component.
        ZX_ASSERT(current_entries.size() <= 1);
        if (!current_entries.empty()) {
          std::string json;
          fsl::StringFromVmo(current_entries[0].json(), &json);
          // Ensure the component is either OK or UNHEALTHY.
          if (json.find("OK") != std::string::npos || json.find("UNHEALTHY") != std::string::npos) {
            return json;
          }
        }
    
        // Retry with delay until the data appears.
        usleep(150000);
      }
    
      return "";
    }
    

    荒漠油廠

    use anyhow::format_err;
    use diagnostics_assertions::{assert_data_tree, AnyProperty};
    use diagnostics_reader::{ArchiveReader, DiagnosticsHierarchy, Inspect};
    async fn get_inspect_hierarchy(test: &IntegrationTest) -> Result<DiagnosticsHierarchy, Error> {
        let moniker = test.reverser_moniker_for_selectors();
        ArchiveReader::new()
            .add_selector(format!("{}:root", moniker))
            .snapshot::<Inspect>()
            .await?
            .into_iter()
            .next()
            .and_then(|result| result.payload)
            .ok_or(format_err!("expected one inspect hierarchy"))
    }
    
  4. 運動。在測試中使用傳回的資料,並在傳回的資料中加入斷言:

    C++

    rapidjson::Document document;
    document.Parse(GetInspectJson());
    

    針對傳回的 JSON 資料新增斷言。

    • 提示:建議您輸出 JSON 以查看結構定義。

    • 提示:您可以按照以下路徑讀取值:

    • 提示:您可以將預期值做為 rapidjson::Value 傳入,以便 EXPECT_EQrapidjson::Value("OK")

    rapidjson::GetValueByPointerWithDefault(
        document, "/payload/root/fuchsia.inspect.Health/status", "")
    

    荒漠油廠

    let hierarchy = get_inspect_hierarchy(&test).await?;
    

    在傳回的 DiagnosticsHierarchy 上新增斷言。

    • 提示:建議您輸出 JSON 以查看結構定義。

您的整合測試現在會確保檢查輸出內容正確無誤。

這就是第 4 部分的內容。

您也可以選擇提交解決方案:

git commit -am "solution to part 4"

第 5 部分:意見回饋選取器

這個部分正在建構中。

  • TODO:編寫意見回饋選取器,並在整合測試中加入測試。

  • TODO:意見回饋和其他管道的選取器