添加新的指标类型

本文档介绍如何在 FOG 中添加新的指标类型。只有在 Glean SDK 中添加了新的指标类型,并且 Firefox 桌面版需要该类型时,才需要执行此操作。

IPC

有关 IPC 设计的详细信息,包括禁止操作的列表,请参阅 FOG IPC 文档

添加新的指标类型时,主要的 IPC 注意事项包括

  • 哪些操作默认情况下是被禁止的,因为它们不是可交换的?

    • 大多数 set 样式的操作无法在多个进程之间进行合理的协调。

    • 但是,通过使用 permit_non_commutative_operations_over_ipc 指标元数据属性,仍然可以使用这些“默认情况下禁止”的操作。

  • 此指标在非主进程中将具有什么部分表示形式?换句话说,它在 IPC 负载 中将占用什么形状的存储空间?

    • 例如,计数器可以将所有部分计数聚合到单个“部分和”中。因此,它在 IPC 负载中的表示形式 仅为每个计数器的一个数字。

    • 相反,计时分布的桶安排仅核心知道,因此它无法组合子进程中的样本计数。相反,我们以最高分辨率(纳秒)记录持续时间,并通过 IPC 发送一系列高精度样本。

为了在指标类型中实现 IPC 支持,我们将 FOG 的 Rust 指标实现分为三个部分

  1. 一个名为 MetricTypeMetric 的总括 enum

    • 它具有 ChildParent 变体。

      • 如果存在仅偶尔需要支持的不可交换操作,则还需要 UnorderedChild 变体。它将通过 Rust 代码生成的 with_unordered_ipc 构造函数构建。

    • 它具有 IPC 意识,并负责

      • 如果在非父进程上,则将部分表示形式存储在 IPC 负载中,并在调用禁止的非测试 API 时记录错误。(或者如果调用测试 API 则发生恐慌。)

      • 如果在父进程上,则在其内部 Rust 语言绑定指标上分派 API 调用。

  2. 父进程实现由 RLB 提供。

    • 为了进行测试,它将标识此特定指标的 MetricId 以跨进程方式存储。

    • 为了进行测试,它公开了 child_metric() 函数以创建其 Child 等效项。

    • 为了进行测试,以及如果它支持非父进程中的操作,它公开了 metric_id() 函数以访问存储的 MetricId

  3. MetricTypeIpc 是非父进程实现。

    • 如果它确实支持非父进程中的操作,则它将标识此特定指标的 MetricId 以跨进程方式存储。

镜像

FOG 可以通过 Glean Interface For Firefox Telemetry 将 Glean 指标镜像到 Telemetry 探测器。

此指标类型可以镜像吗?是否应该镜像?

如果是,请为其添加一个相应的 Telemetry 探测器进行镜像,并在 GIFFT 文档 中记录兼容性。

GIFFT 测试

如果添加了 GIFFT 镜像,请不要忘记测试镜像是否正常工作。可以通过向 toolkit/components/glean/tests/xpcshell/test_GIFFT.js 添加任务来实现。

GIFFT C++ 状态:典型的锁定和关闭

某些指标类型(labeled_*timespantiming_distribution)需要在 C++ 中保持状态才能使 GIFFT 工作。Ping 也保持状态以支持 testBeforeNextSubmit()。如果新的指标类型需要 C++ 中的状态,则当前最先进的技术是 StaticDataMutex 锁定的 UniquePtrnsTHashTable。对内部映射的访问受锁保护,并通过单个访问函数进行控制和延迟初始化。例如,请参阅 Ping 的 GetCallbackMapLock()

清除此状态以避免泄漏非常重要。(请参阅 bug 1752417。)但是,检测可能会随时调用指标 API。

因此,GIFFT 在 AppShutdownTelemetry 关闭阶段 之后明确停止支持这些需要状态的操作。这是因为在下一个阶段(XPCOMWillShutdown)我们将清除状态。

Rust

FOG 使用 Rust 语言绑定 API(glean 仓)并在其之上添加了一层 IPC。

IPC 添加和 glean-core 特性实现位于 fog 仓的 private 模块 中。

每个指标类型都有自己的文件,模仿 glean_coreglean 中的结构。当然,除非该指标是带标签的指标类型。然后子指标类型有自己的文件,并且需要通过为新类型实现 Sealed(按照 api/src/private/labeled.rs 中的模式)来为其添加“标签”。

目前,指标类型上的每个方法都是公开的,包括测试方法,并且至少是通过 指标特性 公开的全部方法。

为了支持 IPC 和 MLA FFI(见下文),我们通过 MetricId 识别指标实例,并将它们存储在 metrics.rs__glean_metric_maps 模块 中的映射中。这项工作由 rust.pyrust(_pings).jinja2 扩展到 glean_parser 完成,这些扩展可以在 build_scripts/glean_parser_ext/ 文件夹 中找到。

对于新的指标类型,你不应该需要编辑这些文件,因为为此类型对 glean_parser 进行的原始修改应该已经生成了正确的代码。

Rust 测试

你应该能够在 Rust 单元测试中对基本功能进行冒烟测试。你可以在指标类型实现文件中直接执行此操作。

C++ 和 JS

C++ 和 JS API 的实现位于 Rust API 之上。我们将它们一起处理,因为尽管它们是不同的语言,但它们都在 C++ 中实现,并且共享大部分实现。

总体设计是在多语言架构 (MLA) 的 FFI 之上构建 C++ API,然后在 C++ API 之上构建 JS API。这使得像 Glean Interface For Firefox Telemetry (GIFFT) 这样的仅针对 C++ 和 JS 的功能可以在 C++ 层中更简单地实现。不鼓励对此进行例外(JS 直接使用 FFI 的情况)。

每个指标类型都有六个部分需要涵盖

1. MLA FFI

  • 使用我们方便的宏,在 api/src/ffi/ 中的 Rust API 之上定义指标类型的多语言架构 FFI 层。

2. C++ 实现

  • bindings/private/ 中的 mozilla::glean::impl 中实现一个名为 XMetric(例如 CounterMetric)的类型。

    • 其方法名称应与 Rust API 中的方法名称相同,转换为 CamelCase

    • 它们都应该是公开的。

    • 将 FFI 的 test_havetest_get 函数多路复用到单个 TestGetValue 函数中,该函数返回一个包装 C++ 类型(最适合指标类型)的 mozilla::Maybe

  • 将新的指标类型包含在 bindings/MetricTypes.h 中。

  • 将新文件包含在 moz.build 中。头文件应添加到 EXPORTS.mozilla.glean.bindings 中,而 .cpp 文件应添加到 UNIFIED_SOURCES 中。

3. IDL

  • 将公共 API(包括其文档)复制到 dom/webidl/GleanMetrics.webidl 中,名称为 GleanX(例如 GleanCounter)。

    • 继承自 GleanMetric

    • 此处的命名风格为 lowerCamelCase

    • 如果指标方法是保留字,则在其前面加上 _

    • Web IDL 绑定使用 自己的类型映射。如果您选择最接近 C++ 类型的映射,将会使您的工作更轻松。

  • dom/bindings/Bindings.conf 中添加新的映射。

    'GleanX': {
        'nativeType': 'mozilla::glean::GleanX',
        'headerFile': 'mozilla/glean/bindings/X.h',
    },
    
    • 如果不这样做,您将收到一个构建错误,提示 fatal error: 'mozilla/dom/GleanX.h' file not found

4. JS 实现

  • 在与 toolkit/components/glean/bindings/private/ 中的 XMetric 相同的头文件和 .cpp 文件中实现 GleanX(例如 GleanCounter)类型。

    • 它应该拥有一个 XMetric 的实例,并将方法实现委托给它。

    • GleanX 的定义中,成员标识符恢复为 CamelCase

    • 仅测试方法可以在失败时抛出 DataError

    • 查看 Web IDL 绑定文档,了解有关可选、可空和非基本类型的帮助。

6. 测试

两种语言意味着两个测试套件。

  • 将一个永不过期的仅测试指标添加到 test_metrics.yaml 中。

    • 您可以随意使用聪明的名称,但请确保明确表明它是仅测试指标。

  • **C++ 测试 (GTest)** - 在 gtest/TestFog.cpp 中添加一个小型测试用例。

  • **JS 测试 (xpcshell)** - 在 xpcshell/test_Glean.jsxpcshell/test_JOG.js 中添加一个小型测试用例。如果您的指标类型支持 IPC 操作,也请在这些测试文件的 IPC 变体中添加用例。

7. API 文档

指标 API 文档集中在 Glean SDK 手册 中。

您需要针对 SDK 创建一个拉取请求,为特定指标类型的 API 文档添加 C++ 和 JS 示例。

在两个示例的顶部添加一个通知,说明这些 API 仅在 Firefox 桌面版中可用。

<div data-lang="C++" class="tab">

> **Note**: C++ APIs are only available in Firefox Desktop.

```cpp
#include "mozilla/glean/GleanMetrics.h"

mozilla::glean::category_name::metric_name.Api(args);
```

There are test APIs available too:

```cpp
#include "mozilla/glean/GleanMetrics.h"

ASSERT_EQ(value, mozilla::glean::category_name::metric_name.TestGetValue().ref());
```
</div>

// and again for <div data-lang="JS">

如果您幸运的话,Rust API 可能已经添加了。否则,您还需要为其编写一个示例。

8. 带标签的指标(如有必要)

如果您的新指标类型是带标签的,则需要做更多工作。我假设您已经按照上述步骤实现了非带标签的子指标类型。现在您必须向其添加“带标签性”。

这包含五个部分

Rust

  • 如果您的新带标签的指标类型支持 IPC,则需要在名为 labeled_x.rs 的文件中构建一个名为 LabeledXMetric 的类型(例如 toolkit/components/glean/api/src/private/labeled_counter.rs),该类型在调用之间存储子指标的标签,以便可以将其提供给 IPC 负载。

  • 如果您的新带标签的指标类型不支持 IPC,您仍然需要一个 LabeledXMetric 类型,但此类型可以通过在 toolkit/components/glean/api/src/private/mod.rs 中使用 pub use self::x::XMetric as LabeledXMetric; 来重新导出未带标签的类型(例如 LabeledBooleanMetric)。

FFI

C++

JS

  • 已经为您处理,因为所有 JS 类型都继承自 GleanMetric,并且 JS 模板知道将您的新类型添加到 NewSubMetricFromIds(...) 中(如果您好奇,请参阅 GleanLabeled::NamedGetter)。

测试

  • 带标签的变体需要与步骤 #6 相同的测试。提示:请确保测试两个具有不同值的标签。

Python 测试

我们有一套测试,用于确保代码生成生成相应的代码。您应该为您的新指标类型在 该套件 中添加一个指标。您需要重新生成预期的文件。