| RFC-0079:检测 debuglog 数据丢失 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 一种用于检测 debuglog 数据丢失的修订机制。 |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2021-02-17 |
| 审核日期(年-月-日) | 2021-03-25 |
摘要
本文档提议更新用于检测内核 debuglog 对象中丢弃的消息的机制。
背景和动机
内核 debuglog 子系统是一种简单的日志记录工具,可让用户模式程序读取和写入日志消息。从逻辑上讲,此系统提供了一个 FIFO 日志缓冲区,多个写入器或读取器可以向该缓冲区写入或从中读取数据。
debuglog 可能会丢失数据。假设读取器可以跟上写入速率,则所有读取器都将按写入顺序看到所有写入器写入的所有消息。但是,如果读取器速度较慢且无法跟上,则会错过消息。
用户模式程序可以通过 zx_debuglog_write 将消息写入 debuglog,内核可以通过 printf 将消息写入 debuglog。可以通过 zx_debuglog_read 读取消息。此外,内核还有一个专用线程(称为 debuglog_dumper),用于从 debuglog 读取消息并将其写入调试串行端口。
debuglog 缓冲区具有固定的容量。当达到该容量时,系统可能会丢弃最近写入的消息,以便为新消息腾出空间。任何尚未“赶上”的读取器都将永远看不到丢弃的消息。
了解日志是否完整有助于推断某些事件的缺失,因此检测丢弃的日志消息是日志系统的一项重要功能。Debuglog 读取器需要能够检测到日志消息何时被丢弃。
目前,debuglog 提供了一种机制,供读取器检测何时以及丢弃了多少字节的日志数据,即 zx_log_record_t 的 uint32_t rolled_out 字段。
使用 zx_debuglog_read 读取 zx_log_record_t 后,rolled_out 字段将包含自该读取器上次成功读取以来从 debuglog 中丢弃的日志消息的字节数。该值包括丢弃的日志标头的字节数和丢弃的日志正文的字节数。
rolled_out 机制的实现方式是让每个读取器维护一个指向 debuglog 缓冲区的指针,该指针指向尚未读取的下一条消息。Debuglog 维护一个写入指针,该指针指向将写入下一条消息的位置。如果读取器注意到写入指针已超过其读取指针,则表示它错过了一条或多条消息。通过减去指针值,读取器可以确定它错过了多少字节的日志数据(包括标头)。
rolled_out 机制目前未使用。
提案
此 RFC 提议...
将面向字节的数据丢失检测替换为面向记录的检测。
为了更贴近 debuglog 读取器(尤其是 debuglog_dumper)的预期,现有的面向字节的
rolled_out机制将被替换为每个记录的序列号,该序列号可用于检测数据丢失(序列中的间隙)。Debuglog_dumper 必须将其读取的每条消息写入调试串行端口。由于串行端口的速度可能不是很快,因此 debuglog_dumper 通常无法跟上 debuglog,这会导致消息被丢弃。发生这种情况时,我们希望向串行端口输出一条消息,指明发生了数据丢失以及丢失了多少条消息,类似于 Linux 的
printk输出。使用 64 位值消除未检测到数据丢失的可能性。
由于
rolled_out字段的大小为 32 位,并且计数的是字节而不是记录,因此如果在两次调用zx_debuglog_read之间写入 4GB 的日志数据,则可能会溢出该值,这可能会导致未检测到数据丢失。这种情况在实践中不太可能发生,但最好完全消除这种可能性。如果我们用 32 位每记录序列字段替换 32 位字节序列字段,则创建溢所需的数据量将增加到大约 128GB。通过使用 64 位序列字段,即使在非常高的日志记录速率下,我们也可以完全忽略溢出的可能性。支持未来的实现优化。
有一些潜在的未来优化取决于允许多个 debuglog 读取器“共享”单个
zx_log_record_t。如果没有速度较慢的读取器,所有 debuglog 读取器都应按相同的顺序看到完全相同的日志数据。除了
rolled_out之外,zx_log_record_t的所有字段在将记录写入调试日志时都是固定的。rolled_out的不同之处在于,它是根据每个读取器在从 debuglog 中读取记录时计算的。如果我们有另一种检测数据丢失的方法,而无需使用可能因读取器而异的字段,则可以实现一些潜在的未来优化,即所有读取器共享单个记录。
设计
Debuglog 在将每个记录写入 debuglog 时,会为其分配一个 64 位序列号,从 0 开始。
每个记录的序列号都将比前一个记录的序列号大 1。移除 zx_log_record_t 的 rolled_out 字段,并将其替换为记录的序列号 uint64_t sequence。
zx_debuglog_read 的调用方随后可以检测序列中的间隙,并计算丢弃了多少条消息。
实现
zx_debuglog_read 和 zx_log_record_t 不在树外使用。虽然不需要完整的 Fuchsia 大规模更改 (LSC) 流程,但系统会提交 FYI LSC bug,并且实现将分阶段完成。
rolled_out 字段未使用,但包含的结构体 zx_log_record_t 在 fuchsia.git 中的几个位置使用。需要注意不要破坏现有代码。zx_log_record_t 不在树外使用。
zx_debuglog_read 的系统调用定义和文档实际上并未指定它返回 zx_log_record_t。相反,它们指定了 void* 和 size_t,调用方必须知道如何将 zx_log_record_t 强制转换为结果或将其“叠加”到结果之上。强制转换为 zx_log_record_t* 容易出错,因此 void* 系统调用参数将更改为 zx_log_record_t*,并且调用方将更新。
目前没有与 zx_log_record_t 等效的 Rust 类型,并且 Rust 调用方使用硬编码的偏移量来访问字段,因此更改其字段的大小或偏移量可能会悄无声息地破坏这些调用方。作为实现的一部分,我们将创建与 zx_log_record_t 等效的 Rust 类型,并更新 Rust 调用方以使用它。
相关步骤如下:
向 debuglog 消息的私有内部表示法 (
dlog_header_t) 添加 64 位序列号。更改 debuglog 使用方,以使用
zx_log_record_t(或语言等效项)而不是硬编码的字段偏移量。具体而言,创建与 Rust 等效的类型,并更新 Rust 代码以使用它。将
zx_debuglog_read的void*参数更改为zx_log_record_t*。将
zx_log_record_t的rolled_out字段更改为填充零的unused字段。将
zx_log_record_t的unused字段替换为uint64_t sequence,并确保不会创建隐式结构体填充。对所有与zx_log_record_t等效的类型执行相同的操作,无论使用哪种语言。更新
zx_debuglog_read文档,以说明调用方如何使用新的序列字段来检测数据丢失。
步骤 1、2 和 3 将各自获得自己的 CL。步骤 4、5 和 6 将在单个 CL 中进行。
性能
管理序列计数器的运行时费用
Debuglog 操作是在持有锁的情况下执行的,因此我们可以使用常规 uint64_t 值来生成序列。预计不会对性能产生可衡量的影响。
每个记录序列值的大小影响
zx_log_record_t 和 dlog_header_t(内核的私有记录实现)的大小都会增加。zx_log_record_t的 32 位 rolled_out 字段将被替换为 64 位记录序列字段,从而净增 4 个字节。
dlog_header_t 大小的变化更有趣,因为它是日志记录在 FIFO 中存储的本机形式。dlog_header_t 的大小为 32 字节,没有 rolled_out 字段,因此其净增量将为完整的 8 字节。Debuglog 对象中的 FIFO 空间有限,因此将每个日志记录增加 8 字节会减少 FIFO 中可存储的最大记录数,并减少最大消息大小(从 224 字节减少到 216 字节)。
FIFO 可以存储 128KB 的标头和消息。消息抽样表明,平均大小约为 100 字节。假设此平均大小,标头为 32 字节,则 FIFO 可以存储大约 971 条消息。如果使用每个记录的序列号,则该数字将减少到大约 917。
安全注意事项
该提案不会改变系统的安全性。Debuglog 读取器是特权组件。如果没有数据丢失,debuglog 读取器已经可以准确地合成日志记录序列。
隐私注意事项
不影响隐私。
测试
内核内单元测试将验证底层 debuglog 实现,debuglog 核心测试将验证在系统调用层可观察到的行为。
文档
将更新 zx_log_record_t 的文档。
缺点、替代方案和未知事项
不进行任何操作
由于 rolled_out 尚未被使用,因此目前实现该提案所需的工程工作量相对较小。一旦下游代码使用 rolled_out,实现此提案或类似提案的成本就会更高。
可以通过记录 UINT32_MAX 的语义来稍微缓解 32 位回绕问题,即表示“UINT32_MAX 或更多”。或者,我们可以将 rolled_out 的类型更改为 uint64_t。
记录序列和字节序列
如果空间空闲,我们可以在每个 zx_log_record_t 中同时放置记录序列值和字节序列值。然后,debuglog 读取器可以测量丢失的记录数或丢失的字节数中的数据丢失。但是,这会进一步增加 dlog_header_t 的大小,并且我们不清楚是否有丢弃字节的用例。
在先技术和参考文档
Linux 的 printk 报告的是丢弃/抑制的消息数,而不是丢弃/抑制的日志数据字节数。