崩溃事件

崩溃事件指的是 Gecko 中的一个特殊子系统,旨在捕获与进程崩溃和挂起相关的感兴趣事件。

当发生值得记录的事件时,包含该事件信息的的文件将写入文件系统上的一个定义明确的位置。Gecko 进程定期扫描生成的 文件并将其信息整合到更统一、更有效的后端存储中。

崩溃事件文件

当发生与崩溃相关的事件时,描述该事件的文件将写入定义明确的目录。该目录可能位于当前活动配置文件的目录中。但是,如果 Gecko 进程中尚未激活配置文件,则该目录可能位于用户的“应用程序数据”目录中(来自目录服务的 UAppData)。

事件文件的名称并不相关。但是,生产者需要明智地选择文件名以避免名称冲突和竞争条件。由于在崩溃时文件锁定可能很危险,因此采用了生成 UUID 并将其用作文件名的约定。

文件格式

所有崩溃事件文件都共享相同的高级文件格式。该格式由以下用 UNIX 换行符 (n) 字符分隔的字段组成

  • 事件名称字符串(有效的 UTF-8,但可能是 ASCII)

  • 自 UNIX 纪元以来的整数秒的字符串表示形式

  • 有效负载

有效负载是特定于事件的,可能包含 UNIX 换行符。建议的解析方法是在 UNIX 换行符上最多拆分 3 次,然后根据事件名称分派到特定于事件的解析器。

如果遇到未知的事件类型,则可以安全地忽略该事件,直到以后再处理。这有助于确保应用程序降级(可能是由于崩溃率升高)不会导致数据丢失。

一旦事件类型提交到 Firefox 主存储库,每种事件类型的格式和语义都应该保持不变。如果需要捕获新的元数据或事件中捕获的数据的含义发生变化,则应通过发明新的事件类型来表达此更改。因此,强烈建议事件名称包含版本。例如,我们更喜欢 Gecko 进程崩溃 v1 事件,而不是 Gecko 进程崩溃 事件。

事件类型

每个小节都记录了可能产生的不同类型的崩溃事件。每个部分名称对应于崩溃事件文件的首行。

目前只有主进程崩溃会生成事件文件。由于子进程中的崩溃和挂起可以由主进程轻松记录,因此我们预计无需为子进程编写事件文件,尽管存在以下设计考虑因素。

crash.main.3

当主进程崩溃时,会生成此事件。

此事件的有效负载由 UNIX 换行符 (n) 分隔,包含以下字段

  • 崩溃 ID 字符串,很可能是一个 UUID

  • 一行包含作为 JSON 字符串序列化的崩溃元数据

crash.main.2

当主进程崩溃时,会生成此事件。

此事件的有效负载由 UNIX 换行符 (n) 分隔,包含以下字段

  • 崩溃 ID 字符串,很可能是一个 UUID

  • 0 行或更多行元数据,每行包含一个键=值对文本

此事件已过时。

crash.main.1

当主进程崩溃时,会生成此事件。

此事件的有效负载是崩溃 ID 字符串,很可能是一个 UUID。磁盘上应该有 UUID.dmpUUID.extra 文件,由 Breakpad 保存。

此事件已过时。

crash.submission.1

提交崩溃时会生成此事件。

此事件的有效负载由 UNIX 换行符 (n) 分隔,包含以下字段

  • 崩溃 ID 字符串

  • 如果提交成功,则为“true”,否则为“false”

  • 如果提交成功,则为远程崩溃 ID 字符串

聚合事件日志

崩溃事件会聚合到一个统一的事件日志中。目前,此日志实际上是一个 JSON 文件。但是,这是一个实现细节,随时可能更改。JavaScript API 提供的崩溃数据接口是唯一受支持的接口。

设计考虑因素

许多因素影响着此子系统的设计。我们尝试在本节中记录它们。

事件文件与最终数据结构的解耦

虽然 Gecko 进程当然可以将数据直接写入磁盘上的最终数据结构,但事件生成与其转换为最终存储之间存在有意解耦。同样,生产者将事件写入多个文件的选择也是经过深思熟虑的。

一些记录的事件在进程崩溃后立即写入。对于主机系统来说,这是一个非常不确定的时间。系统很有可能处于异常状态,例如内存不足。因此,崩溃后采取的任何操作都需要非常谨慎地处理其操作。过多的内存分配和某些系统调用可能会导致系统再次崩溃或机器状况恶化。这意味着记录崩溃事件的行为必须非常轻量级。从无到有地写入一个新文件非常轻量级。这就是我们编写单独文件的原因之一。

我们编写单独文件的另一个原因是,如果主 Gecko 进程本身崩溃(而不是插件进程),则崩溃报告程序(而不是 Gecko)正在运行,并且崩溃报告程序需要处理事件信息的写入。如果此写入很复杂(例如加载、解析、更新和重新序列化回磁盘),则需要在 Gecko 和崩溃报告程序中实现此逻辑,或者需要以一种方式实现,以便两者都可以使用。从软件生命周期管理的角度来看,这两种方法都不实用。让单独的进程写入一个简单的文件,并让单个实现完成所有复杂的工作要容易得多。

幂等事件处理

事件文件的处理设计为,无论处理这些文件的顺序如何,结果都是幂等的。这不仅是一个好的设计决策,而且可以说是必要的。虽然事件文件按文件 mtime 的顺序处理,但文件系统时间可能没有足够的精度来进行正确的排序。因此,处理顺序只是一个乐观的假设。

聚合存储格式

崩溃事件会聚合到磁盘上的统一数据结构中。该数据结构目前是 LZ4 压缩的 JSON,由单个文件表示。

最初选择单个 JSON 文件是为了节省时间和减少复杂性。在更改格式或添加大量新数据之前,必须考虑一些因素。

首先,在正常安装中,崩溃数据应该最少。崩溃和挂起将很少发生,因此随着时间的推移,崩溃数据的大小应该保持较小。

随着崩溃数据量的增加,选择单个 JSON 文件会产生更大的影响。随着新数据的累积,我们需要读取和写入整个文件才能进行小的更新。LZ4 压缩有助于减少 I/O。但是,存在文件无限增长的可能性。我们为记录的最大生存期设置了一个限制。超出此限制的任何内容都将被修剪。我们还为每天存储的崩溃数量设置了一个每日限制。一天中超过前 N 次崩溃的所有崩溃都没有有效负载,仅通过计数的存在来记录。此计数确保我们可以区分 N100 * N,它们是截然不同的值!