Codelab:使用“检查”功能

在此 Codelab 中,您将学习如何使用 Inspect(Rust 和 C++)从程序中发布诊断信息,以及如何使用 Inspect 信息来调试程序。

在此 Codelab 中,您将修改程序以输出 Inspect 数据。 您将了解:

  • 如何添加 Inspect 库。

  • 如何在组件中初始化 Inspect。

  • 如何通过写入和读取检查数据来解决实际 bug。

  • 如何读取检查数据,以验证程序是否在执行您想要的操作。

什么是“检查”?

通过 Inspect,Fuchsia 组件可以公开有关其当前状态的结构化分层信息。

如需详细了解检查,请参阅 Fuchsia 组件检查文档。

Inspect 有哪些优势?

组件检查支持多种使用场景,包括:

  • 调试

    查看正在运行的组件的检查数据,以发现问题。例如,您可以了解组件当前是否已连接到依赖项。

  • 监控系统运行状况

    检查数据可深入了解整体系统状态。例如,您可以了解系统未连接到互联网的原因。

  • 收集使用情况或性能统计信息

    您可以同时读取多个组件的检查数据,以了解系统性能。例如,您可以查看组件的传入连接列表以及组件的内存用量。

我可以在 Inspect 中存储哪些类型的信息?

您可以确定在“检查”中公开的数据的结构和内容。下面列举了几款带心率传感器的手表:

  • 打开的 WLAN 连接数。
  • 程序已处理的请求数。
  • 解析器遇到的错误数。
  • 数据结构的内容。

API 参考文档

所需条件

  • Rust 或 C++ 基础知识。
  • 对 Fuchsia 源代码树的访问权限,您可以在其中执行 build 命令。

源代码

您可以在以下网址找到该代码:

此 Codelab 分为多个部分,每个部分都有自己的子目录。此 Codelab 的起点是第 1 部分,每个部分的代码都包含前几个部分的解决方案。

在完成此 Codelab 时,您可以继续将解决方案添加到“part_1”中,也可以基于现有解决方案跳过部分内容。

前提条件

设置开发环境。

本 Codelab 假定您已完成使用入门,并且:

  1. 已检出并构建的 Fuchsia 树。
  2. 搭载 Fuchsia 的设备或模拟器 (ffx emu)。
  3. 一个工作站,用于向 Fuchsia 设备或模拟器提供组件 (fx serve)。

如需构建并运行此 Codelab 中的示例,请将以下实参添加到 fx set 调用中:

C++

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

Rust

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

第 1 部分:有 bug 的组件

有一个提供名为 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,该方法只是反转传递给它的任何字符串。系统提供了协议的实现,但其中存在严重 bug。此 bug 会导致尝试调用 Reverse 方法的客户端看到其调用无限期挂起。您需要自行修复此 bug。

运行组件

有一个客户端应用将启动 Reverser 组件,并将其余命令行实参作为字符串发送给 Reverse:

  1. 查看用量

    根据您要运行的 Codelab 部分,启动 client_i 组件,其中 i 是介于 [1, 5] 之间的数字。例如,如需启动与 Codelab 第 2 部分中的反向器通信的客户端,请运行以下命令:

    C++

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

    Rust

    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 --tag inspect_cpp_codelab

    我们在日志中看到,该组件收到了“Hello”作为输入,但没有看到正确的反向输出。

    Rust

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

    如需查看命令输出,请查看日志:

    ffx log --tag 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
     ```

    Rust

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

    我们可以看到,该组件输出了第一个输入,但我们没有看到预期的输出,也没有看到第二个输入。

现在,您可以查看代码以排查问题了。

查看代码

既然您能重现问题,不妨看看客户端在做什么:

C++

客户端主中:

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

Rust

客户端主中:

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 方法,但似乎从未收到响应。似乎没有错误消息或输出,组件只是挂起。

查看本部分 Codelab 的服务器代码。有很多标准组件设置:

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

Rust

第 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

Rust

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

添加检查

现在,您已了解代码结构,可以开始使用 Inspect 对代码进行插桩,以找出问题。

您之前可能通过打印或记录来调试程序。虽然这种方法通常很有效,但持续运行的异步组件往往会随着时间的推移输出大量有关其内部状态的日志。此 Codelab 演示了 Inspect 如何提供组件当前状态的快照,而无需深入挖掘日志。

  1. 添加 Inspect 依赖项:

    C++

    source_set("lib") 下的 public_deps 中的 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",
      ]
    }
    
    

    Rust

    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(), {});
    

    Rust

    main.rs 中:

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

    您目前使用的是“检查”功能。

  3. 添加一个简单的“版本”属性,以显示正在运行的版本:

    C++

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

    此代码段会执行以下操作:

    1. 获取检查层次结构的“根”节点。

      组件的检查层次结构由一个节点树组成,每个节点都包含任意数量的属性。

    2. 创建作为 RecordString 一部分的新属性。

      这会在根目录下添加新的 StringProperty。此 StringProperty 称为“版本”,其值为“part2”。我们将属性设置为“part1”。

    3. 在根节点中记录新属性。

      创建属性的常用方法是通过节点上的 Create* 方法。使用这些方法创建的属性的生命周期与返回的对象相关联,销毁对象会导致属性消失。该库提供了便捷方法 Record*,用于创建属性并将属性生命周期与调用该方法的节点相关联。因此,新属性的生命周期与节点本身一样长(在本例中,与根节点一样长,因此与组件的整个执行过程一样长)。

    Rust

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

    此代码段会执行以下操作:

    1. 获取检查层次结构的“根”节点。

    组件的检查层次结构由一个节点树组成,每个节点都包含任意数量的属性。

    1. 使用 record_string 创建新的媒体资源。

    这会在根目录下添加新的 StringProperty。此 StringProperty 称为“版本”,其值为“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 --tag inspect_cpp_codelab

    Rust

    ffx component run --recreate /core/ffx-laboratory:client_part_1 fuchsia-pkg://fuchsia.com/inspect_rust_codelab#meta/client_part_1.cm
    ffx log --tag 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 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

    Rust

    ffx inspect show 'core/ffx-laboratory:client_part_1/reverser'
    # or `ffx inspect show 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
      }
    ]
    

    Rust

    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
      }
    ]
    

对代码进行插桩以查找 bug

现在,您已经初始化了 Inspect 并了解了如何读取数据,接下来就可以对代码进行插桩并找出错误了。

上一个输出显示了组件的实际运行方式,以及组件并未完全挂起。否则,检查读取会挂起。

添加了新的连接信息,以观察连接是否正在被组件处理。

  1. 向根节点添加了一个新子级,用于包含有关 reverser 服务的统计信息:

    C++

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

    Rust

    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) {
    

    Rust

    更新 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),
              // ...
    

    Rust

    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 位无符号整数),并将其设置为 0。在处理程序(针对每个连接运行)中,该属性会递增 1。

  4. 重新构建并重新运行组件,然后运行 ffx inspect

    C++

    ffx --machine json-pretty inspect show inspect_cpp_codelab

    Rust

    ffx --machine json-pretty inspect show 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-"));
    

    Rust

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

    这将创建以“connection”开头的唯一名称。

C++

提示:为 Reverser 创建一个接受 inspect::Node 的构造函数会很有帮助。此 Codelab 的第 3 部分将说明为什么这是一个有用的模式。

Rust

提示:您会发现,为 ReverserServer 创建一个采用 inspect::Node 的构造函数很有用,原因与我们为 ReverserServerFactory 创建构造函数的原因相同。

  • 提示:您需要在 Reverser 上创建一个成员来持有 request_count 媒体资源。其类型将为 inspect::UintProperty

  • 跟进搜索:请求数是否提供了您所需的所有信息?还添加了 response_count

  • 高级:能否在所有连接上添加所有请求的计数?反向器对象必须共享某些状态。您可能会发现,将 Reverser 的实参重构为单独的结构体很有帮助(如需了解此方法,请参阅第 2 部分中的解决方案)。

完成本练习并运行 ffx inspect 后,您应该会看到类似以下内容的输出:

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

上面的输出显示,连接仍处于打开状态,并且已收到一个请求。

C++

如果您还添加了“response_count”,则可能已经注意到该 bug。 Reverse 方法会接收 callback,但永远不会使用 output 的值进行调用。

Rust

如果您还添加了“response_count”,则可能已经注意到该 bug。 Reverse 方法会接收 responder,但永远不会使用 result 的值进行调用。

  1. 发送回答:

    C++

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

    Rust

    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 --tag 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`
    

    Rust

    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 --tag 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 部分:诊断组件间问题

您收到了 bug 报告。“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; });

Rust

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 --tag inspect_cpp_codelab

Rust

ffx log --tag 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

    Rust

    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 Inspect 数据,您应该会看到:

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

    Rust

    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!");

Rust

高级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!");

完成此练习后,您应该会看到连接错误处理程序被调用,并显示“未找到”错误。检查输出显示 FizzBuzz 正在运行,因此可能存在配置错误。遗憾的是,并非所有内容都使用 Inspect(目前还不是!),因此请查看日志:

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. ...

Rust

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++

part_2/meta 添加 Fizzbuzzuse 条目:

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

Rust

part_2/meta 添加 Fizzbuzzuse 条目:

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

添加“fuchsia.examples.inspect.FizzBuzz”后,重新构建并再次运行。您现在应该会在日志中看到 FizzBuzz,并看到“确定”状态:

C++

ffx log --tag 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

Rust

ffx log --tag 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 部分:针对 Inspect 的单元测试

Fuchsia 上的所有代码都应经过测试,这同样适用于 Inspect 数据。

虽然一般情况下不需要测试检查数据,但您需要测试其他工具(例如 Triage 或 Feedback)所依赖的检查数据。

Reverser 具有基本的单元测试。运行该文件:

C++

单元测试位于 reverser_unittests.cc 中。

fx test inspect_cpp_codelab_unittests

Rust

单元测试位于 reverser.rs > mod tests 中。

fx test inspect_rust_codelab_unittests

该单元测试可确保 Reverser 正常运行(且不会挂起),但不会检查 Inspect 输出是否符合预期。

将节点传递到构造函数中是一种依赖项注入形式,可让您传入依赖项的测试版本来检查其状态。

用于打开 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());

Rust

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

Rust

练习:更改 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());

上面的代码段会读取包含 Inspect 数据的底层虚拟内存对象 (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());

Rust

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,
        },
    }
});

上述代码段从包含 Inspect 数据的底层虚拟内存对象 (VMO) 读取快照,并将其解析为可读的层次结构。

练习:为其余的检查数据添加断言。

第 3 部分到此结束。

您可以选择提交更改:

git commit -am "solution to part 3"

第 4 部分:Inspect 的集成测试

集成测试是 Fuchsia 软件开发工作流程的重要组成部分。通过集成测试,您可以观察实际组件在系统上运行时的行为。

运行集成测试

您可以按如下所示运行 Codelab 的集成测试:

C++

fx test inspect_cpp_codelab_integration_tests

Rust

fx test inspect_rust_codelab_integration_tests

此命令会针对此 Codelab 的所有部分运行集成测试。

查看代码

看看集成测试是如何设置的:

  1. 查看集成测试的组件清单:

    C++

    part_4/meta 中找到组件清单 (cml)

    Rust

    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 负责创建新的测试环境,并在其中启动 Codelab 组件。include_fizzbuzz_service 选项指示该方法选择性地包含 FizzBuzz。此功能用于测试,如果无法连接到 FizzBuzz(如第 2 部分中所述),您的检查输出是否符合预期。

    Rust

    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 负责创建新的测试环境,并在其中启动 Codelab 组件。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 "";
    }
    

    Rust

    use anyhow::format_err;
    use diagnostics_assertions::{AnyProperty, assert_data_tree};
    use diagnostics_reader::{ArchiveReader, DiagnosticsHierarchy};
    async fn get_inspect_hierarchy(test: &IntegrationTest) -> Result<DiagnosticsHierarchy, Error> {
        let moniker = test.reverser_moniker_for_selectors();
        ArchiveReader::inspect()
            .add_selector(format!("{}:root", moniker))
            .snapshot()
            .await?
            .into_iter()
            .next()
            .and_then(|result| result.payload)
            .ok_or_else(|| 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", "")
    

    Rust

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

    针对返回的 DiagnosticsHierarchy 添加了断言。

    • 提示:打印 JSON 输出可能有助于查看架构。

现在,集成测试将确保您的检查输出正确无误。

第 4 部分到此结束。

您可以选择提交解决方案:

git commit -am "solution to part 4"

第 5 部分:反馈选择器

本部分正在建设中。

  • 待办:编写反馈选择器并向集成测试添加测试。

  • TODO:反馈和其他流水线的选择器