内核 track 事件:格式和约定

本文描述了一种以这种方式构建 Linux 内核 tracepoint 的约定,使 perfetto 能够自动在 UI 和 SQL 级别将它们呈现为 Slice/Counter Tracks,而无需更改或重建 perfetto 代码。

这是一个 perfetto 约定,不需要(也不需要)任何专用的上游内核代码。在本地内核上黑客攻击或编写不会被上游的自包含模块时,它最能使用。它也没有明确绑定到静态 tracepoint,创建具有相关字段的 tracefs 条目的动态 Probe(例如 kprobe)也将起作用。

本文档的结构作为参考,在"使用 ftrace 对 Linux 内核进行仪器化"中有带有生成 UI 的示例和截图的介绍。

此约定仍然可塑,如果你最终使用它和/或发现设计问题,请发送电子邮件到我们的邮件列表或提交 github 问题。

Slice 和瞬时事件

Perfetto 在事件的数据表示中查找具有特定类型和名称的字段。这在使用 TRACE_EVENT() 宏定义tracepoint时由 TP_STRUCT__entry() 定义。

对于表示 Slice(开始 + 结束)和瞬时事件(按 Track 分组),知名字段为:

必需? 类型 名称
必需 char track_event_type
必需 __string slice_name
可选 intX scope_{...}
可选 __string track_name

其中 intX 表示任何整数类型,而 __string 是内核类型,用于在 trace event 中存储动态大小的字符串。

在运行时,事件有效负载将解释如下:

此外:

tracepoint 名称和子系统可以是任意的。你的头文件可以声明匹配这些模板的任意数量的 tracepoint。每个 tracepoint 将被独立处理。

对具有附加字段、字段顺序或 TRACE_EVENT() 声明的其他部分没有约束。请注意,这包括 printk 说明符,因此tracepoint的文本格式可以是任意的(你甚至不需要打印 perfetto 特定的字段)。

计数器

对于表示 Counter 值(按 Track 分组),知名字段为:

必需? 类型 名称
必需 intX counter_value
可选 intX scope_{...}
可选 __string track_name

作用域(分组)事件的详细信息

本节解释了记录的事件如何分组到 Track 中的规则,因为通常使用单个 tracepointtrace 记录可能导致 N 个单独的 Track。分组规则对于 Slice 和 Counter Track 是相同的。

注意: Slice Track 上的 Slice 必须具有严格嵌套 - 所有 Slice 必须在它们的父项之前终止(有关更多详细信息,请参阅[异步 Slice][async-slice-link]的概念)。你需要使用 Track 命名或作用域来确保保持该不变量。

如果只指定必填字段,默认行为是线程范围的。事件按命中 tracepoint 的线程的线程 id 分组。每个线程将有一个带有事件的 Track。结束('E')事件将终止该线程上最后打开的 Slice。

如果事件具有以 scope_ 为前缀的字段,则事件将按该字段的值分组,其中一些预定义名称具有特殊含义(见上文)。例如,如果指定 scope_tgid,那将 Track 变为进程范围 - 所有共享相同 scope_tgid 值的事件将被放在同一个 Track 上。此外,UI 将在进程组中呈现该 Track。

如果你的事件包括 track_name 字段,则事件将按该名称作为上述的额外维度进行分组。也就是说,结束('E')事件将终止具有该确切 Track 名称的最后打开的 Slice,即使在同一线程/进程/cpu 等作用域中有多个命名 Track。

最终效果是记录的事件按以下唯一组合分组: {tracepoint} x {track name} x {scope id}。最后两个分别默认为 tracepoint名称和线程 id。