进程间通信 (IPC)

Firefox 桌面版是一个多进程桌面应用程序。需要检测的代码可能位于其任何进程中,因此 FOG 提供了执行此操作的功能。

设计

FOG 的 IPC 设计在 bug 1618253 中制定。

它围绕几个特定的概念展开

禁止非交换操作

由于我们无法很好地跨所有进程强加指标操作的规范顺序,因此 FOG 在某些情况下禁止非 交换 指标操作。

例如,从多个进程对计数器指标进行 Add() 操作可以正常工作,因为顺序无关紧要。但是,如果多个进程同时对字符串指标进行 Set() 操作,它应该采用哪个值?

这种歧义不是构建信任的良好基础,因此我们禁止从多个进程设置字符串指标。

禁止操作列表

  • 布尔值的 set(这是指标类型的唯一操作)

  • 带标签的布尔值的 set(这是指标类型的唯一操作)

  • 字符串的 set(这是指标类型的唯一操作)

  • 带标签的字符串的 set(这是指标类型的唯一操作)

  • 字符串列表的 set

    • add 允许(不保证顺序和唯一性)

  • 时间跨度的 startstopcancel(这些是指标类型的唯一操作)

  • UUID 的 setgenerateAndSet(这些是指标类型的唯一操作)

  • 日期时间的 set(这是指标类型的唯一操作)

  • 数量的 set(这是指标类型的唯一操作)

随着新指标类型的添加,此列表可能会随着时间推移而增长。

安全阀:permit_non_commutative_operations_over_ipc

如果您希望放弃 FOG 在排序方面的保护和保证,并在子进程中使用非交换操作,则可以使用 permit_non_commutative_operations_over_ipc 元数据属性标记指标定义,如下所示

unordered_category:
  unordered_boolean_metric:
    type: boolean
    metadata:
      permit_non_commutative_operations_over_ipc: true
    ...

目前仅支持

  • 布尔指标

  • 带标签的布尔指标

注意

如果此列表中没有您需要在非父进程中使用的指标类型,请访问 #glean 频道,我们将为您提供帮助。

进程无关性

对于可以在跨进程使用的指标类型,FOG 没有提供识别检测位于哪个进程中的功能。

这意味着,如果您在多个进程中累积到 时间分布,则所有进程的所有样本都将合并到同一指标中。

如果您希望区分来自不同进程类型的样本,则需要多个指标和内联代码来为给定进程选择合适的指标。例如

if (XRE_GetProcessType() == GeckoProcessType_Default) {
  mozilla::glean::performance::cache_size.Accumulate(numBytes / 1024);
} else {
  mozilla::glean::performance::non_main_process_cache_size.Accumulate(numBytes / 1024);
}

调度

FOG 不保证何时将非主进程指标值发送到 IPC。FOG 将尽最大努力在空闲时刻和有序关闭期间进行机会性调度。

在一些情况下,我们提供了更坚实的保证

测试

Rust、C++ 和 Javascript 中有一些仅限测试的 API。这些不会等待子进程指标值的刷新。您可以使用仅限测试的方法 testFlushAllChildrenFOG XPCOM 组件上等待子进程数据的到达

await Services.fog.testFlushAllChildren();

有关 FOG 测试的更多详细信息,请参阅 测试文档。有关编写有关检测的测试,请参阅 检测测试文档

Ping

我们不保证非主进程数据已进入特定 Ping。

内置 Ping 由 Rust Glean SDK 在 FOG 不直接控制的时间提交,因此在提交内置 Ping 时,父进程中可能不存在某些数据。我们预计这不会造成问题,因为“错过”给定 Ping 的子进程数据将包含在下一个 Ping 中。

目前,自定义 Ping 必须在父进程中发送,并且没有机制安排在子进程数据到达父进程后提交。 bug 1732118 跟踪此类机制或保证的添加。

关闭

在有序关闭期间,我们将尽最大努力刷新子进程中所有挂起的数据。这意味着无序关闭(通常是崩溃)可能导致子进程数据丢失。

大小

我们不会测量或保持 IPC 负载大小的最新计算。但是,我们确实保留了 IPC 负载访问次数的计数。这被用作 IPC 负载大小的(非常)保守估计,因此我们不会超过 IPC 消息大小限制

请参阅 bug 1745660

机制

大致的设计是,父进程可以请求立即刷新挂起的数据,每个子进程可以在任何时候决定刷新其挂起的数据。前者通过 FlushFOGData() returns (ByteBuf) 实现,后者通过 FOGData(ByteBuf) 实现。

挂起数据是由 Rust 中的 bincode 在子进程中生成的字节缓冲区,传递给 C++,通过 IPC 传递,然后返回到父进程中的 Rust 中的 bincode

然后,Rust 负责将挂起数据转换为父进程中指标的 指标 API 调用。

支持的进程类型

FOG 支持以下类型的子进程与父进程之间的消息传递

  • 内容子进程(通过 PContent(目前如此。请参阅 bug 1641989))

  • gmp 子进程(通过 PGMP

  • gpu 子进程(通过 PGPU

  • rdd 子进程(通过 PRDD

  • 套接字子进程(通过 PSocketProcess

  • 实用程序子进程(通过 PUtilityProcess

有关这意味着什么的更多信息,请参阅 进程模型文档

为新进程类型添加支持

为新进程类型添加支持是将上面“机制”中提到的两个消息扩展到另一个进程类型的协议(ipdl 文件)的问题。

  1. P<ProcessType>.ipdl 的相应部分添加两个消息

    • (( 注意: PGPU 应该 是唯一一个 parent 表示非父/主/UI 进程的 ipdl,但请仔细检查您是否正确理解了这一点。))

    • async FOGData(ByteBuf&& aBuf); 添加到父/主/UI 进程端(通常是 parent:)。

    • async FlushFOGData() returns (ByteBuf buf); 添加到非父/主/UI 端(通常是 child:)。

  2. P<ProcessType>{Child|Parent}.{h|cpp} 中实现协议端点

    • 添加到 parent: 部分的消息位于 P<ProcessType>Parent.{h|cpp} 中,反之亦然。

  3. 添加到 FOGIPC.cppFlushAllChildData 代码,该代码

    1. 枚举所有新支持类型的进程(可能只有一个),

    2. 在每个进程上调用 `SendFlushFOGData,以及

    3. 将结果 Promise 添加到数组中。

  4. 将正确的 GeckoProcessType_* 枚举值添加到 FOGIPC.cppSendFOGData 中,以及获取父进程单例并对其调用 SendFOGData 的相应代码。

  5. 将关闭时刷新 IPC 数据的处理添加到 fog crate 的 register_process_shutdown 函数中。如果未添加此项,我们将在不支持的进程类型上首次使用 Glean API 时记录(但不恐慌)。

    • “处理”可能是一个带有注释的空块,解释在哪里可以找到它(例如 PROCESS_TYPE_DEFAULT 的处理方式)

    • 或者它可能是自定义代码(例如 PROCESS_TYPE_CONTENT 的)

  6. 将您添加支持的进程类型添加到已记录的 支持的进程类型列表 中。