原文
Trace configuration - Perfetto Tracing Docs
下半部分是该文档翻译,主要描述Trace Config中的关键字段等等,
Trace configuration(痕迹、轨迹追踪 配置)
与许多“一直开着”的日志系统(例如 Linux 的 rsyslog、Android 的 logcat)不同,Perfetto 默认让所有数据源处于空闲状态,只有在收到明确指令时才开始记录数据。
一个简单的 trace 配置长这样:
duration_ms: 10000 # 总共采 10 秒
buffers {
size_kb: 65536 # 给 64 MB 环形缓冲区
fill_policy: RING_BUFFER
}
data_sources {
config {
name: "linux.ftrace" # 启用 ftrace 数据源
target_buffer: 0
ftrace_config {
ftrace_events: "sched_switch"
ftrace_events: "sched_wakeup"
}
}
}
使用方法:
perfetto --txt -c config.pbtx -o trace_file.perfetto-trace
- --txt 告诉 perfetto 这个配置是文本格式(pbtx)。
- -c 后面跟配置文件,-o 后面跟输出 trace 文件。
★ 提示:仓库里 /test/configs/ 目录下有更完整的配置示例。
注意:如果在 Android 上用 adb 抓 trace 遇到问题,请看下文“Android 注意事项”部分。
TraceConfig (轨迹配置)
TraceConfig 是一个 protobuf 消息,它定义了下面三件事:
-
整个 tracing 系统的全局行为,例如:
- 最大 trace 时长
- 内存缓冲区个数及每块大小
- 输出文件的最大体积
-
要启用哪些数据源,以及每个数据源自己的参数,例如:
- 对内核数据源(ftrace)要打开哪些事件
- 对堆分析器要指定目标进程名、采样率
具体怎么配各个内置数据源,见 “data sources”章节。
- “数据源 → 缓冲区”映射:指定每个数据源把数据写进哪块缓冲区(见下文 buffers 节)。
tracing 服务(traced)充当“配置分发器”:
它从 perfetto 命令行客户端(或其他 Consumer)收到一份 TraceConfig,然后把配置里的不同部分转发给各个已连接的 Producer。
当 Consumer 启动一次 trace 会话时,traced 会:
- 读取 TraceConfig 的外层字段(如 duration_ms、buffers 等),决定自己的运行时行为;
- 读取 data_sources 列表。对每个列出的数据源,如果已有同名的 Producer 注册(例如示例里的 "linux.ftrace"),traced 就让该 Producer 启动对应数据源,并把 data_sources 里对应的子配置整块原样传给它(详见后向/前向兼容章节)。
Buffers(缓冲区)
buffers 段定义 tracing 服务拥有的内存缓冲区的数量、大小以及填满策略。示例:
# 缓冲区 buffer_0
buffers {
size_kb: 4096
fill_policy: RING_BUFFER 环形缓冲区
}
# 缓冲区 buffer_1
buffers {
size_kb: 8192
fill_policy: DISCARD 写满即丢弃
}
每个缓冲区都有一个 fill_policy(填满策略):
RING_BUFFER(默认):环形缓冲区。写满后从头覆盖最旧的数据。DISCARD:写满即丢弃。后续写入直接丢掉,不再接受新数据。
⚠️ 注意:
DISCARD 对某些“只在 trace 结束时才提交数据”的数据源会产生意外副作用——如果缓冲区在它们提交前就满了,这些数据会永远丢失。
一份有效的 TraceConfig 至少要定义一个缓冲区。
最简单的情况:所有数据源都把数据写进同一块缓冲区。
对大多数基本场景够用,但如果各数据源写入速率差异很大,就会出问题。
举例:
假设一份配置同时打开:
- 内核调度器跟踪:典型 Android 手机上约 1 万事件/秒,持续写 1 MB/s。
- 内存定时采样:每 5 秒读一次 /proc/meminfo,每次约 100 KB。
若两者共用一块 4 MB 的缓冲区:
在 5 秒的采样间隔里,调度事件已把缓冲区写满并覆盖了好几轮,导致内存快照被挤掉。
结果:大多数 trace 里根本看不到内存数据,即使第二个数据源工作正常。
→ 解决办法:给低速数据源单独分配一块缓冲区,避免被高速数据源冲掉。
Dynamic buffer mapping(动态缓冲区映射)
Perfetto 里 “数据源 → 缓冲区” 的映射是动态可配的。
最简单的情况:只定义一块缓冲区,所有数据源默认都往里面写。
但在前面的例子里,把不同速率的数据源分开写会更安全。
做法:用 TraceConfig 里每个数据源的 target_buffer 字段指定它要写入第几块缓冲区。
示例对应关系:
数据源
- 调度器跟踪(高速)
- /proc/meminfo 轮询(低速)
- 堆分析器(中低速)
缓冲区
- Buffer #0:4 MB
- Buffer #1:8 MB
配置片段:
data_sources {
config {
name: "linux.ftrace"
target_buffer: 0 // ← 写入 4 MB 的 Buffer #0
ftrace_config { ... }
}
}
data_sources {
config {
name: "linux.sys_stats"
target_buffer: 1 // ← 写入 8 MB 的 Buffer #1
sys_stats_config { ... }
}
}
data_sources {
config {
name: "android.heapprofd"
target_buffer: 1 // ← 也写进 8 MB 的 Buffer #1
heapprofd_config { ... }
}
}
这样高速的调度事件只在 Buffer #0 里环形覆盖,不会把低速的内存快照和堆数据冲掉。
PBTX vs Binary Format(二进制格式)
给 perfetto 命令行传配置时有两种办法:
1. 文本格式(.pbtx)
- 适合人手写、临时调试。
- 文件是 ProtoBuf Textual Representation(后缀常叫 .pbtx 或 .config)。
- 命令里加 --txt 告诉 perfetto 这是文本:
perfetto -c /path/to/config.pbtx --txt -o trace_file.perfetto-trace
注意:
--txt 只有 Android 10(Q)及以后 才支持;老版本只能用二进制。
– 不要让脚本、CI、benchmark 工具生成 PBTX,否则字段改名、枚举值变化就可能炸。
2. 二进制格式
- 机器对机器(M2M)的正式方案。
- 先把 PBTX 用官方 protoc 编译成二进制:
cd ~/code/perfetto # 或 Android 源码 external/perfetto
protoc --encode=perfetto.protos.TraceConfig \
-I. protos/perfetto/config/perfetto_config.proto \
< config.pbtx \
> config.bin
- 然后 去掉 --txt 直接喂给 perfetto:
perfetto -c config.bin -o trace_file.perfetto-trace
总结:
人眼看/手写 → PBTX + --txt
脚本/工具/长期维护 → 二进制 .bin,用 protoc 提前转好。
Streaming long traces(长 trace 的流式写入)
Perfetto 默认把整个 trace 缓冲区一直放在内存里,等 tracing 会话结束时才一次性写进 -o 指定的文件。 这样做能减少运行时对性能的影响,但 trace 的最大体积就被物理内存大小卡住了,常常不够用。 流式写入场景,跑 benchmark 或难复现的 bug 时,往往需要抓比内存大得多的 trace,宁可牺牲一点 I/O 性能也要把数据持续刷到磁盘。
Perfetto 支持周期性地把缓冲区内容“倒”进目标文件(或 stdout),关键字段:
write_into_file: true
开启周期刷盘。此时用户空间缓冲区只需容纳两次刷盘间隔之间产生的数据即可。 典型 trace 数据率约 1–4 MB/s,16 MB 缓冲区大约能扛 4 秒,再久就可能丢数据。
file_write_period_ms: 3000
(可选)把默认 5 s 的刷盘间隔改短。间隔越短→所需缓冲区越小,但对性能干扰越大。如果设成 < 100 ms,系统会强制按 100 ms 执行。
max_file_size_bytes: 1_000_000_000
(可选)写到指定字节数就自动停 trace,用来给文件大小设上限。
完整示例配置见仓库:
/test/configs/long_trace.cfg 一句话:打开 write_into_file,按需调 file_write_period_ms 和 max_file_size_bytes,就能边抓边落盘,不再受内存大小限制。
Data-source specific config(数据源专属配置)
除了 整个 trace 共用的参数,TraceConfig 里还能给每个数据源单独下指令。
在 proto 模式(data_source_config.proto)里,这部分放在 TraceConfig 的 DataSourceConfig 消息内:
message TraceConfig {
repeated DataSource data_sources = 2; // 下面会用到
}
message DataSource {
optional protos.DataSourceConfig config = 1; // 真正的数据源配置
}
message DataSourceConfig {
optional string name = 1;
optional FtraceConfig ftrace_config = 180 [lazy = true];
optional AndroidPowerConfig android_power_config = 106 [lazy = true];
…
}
ftrace_config、android_power_config等等都是数据源专属的子配置。- tracing 服务完全不管这些字段里写了什么,整块
DataSourceConfig原封不动地转发给注册同名的数据源。
关于 [lazy = true]: protozero 代码生成器会把它当成“原始字段”处理,生成类似
const std::string& ftrace_config_raw()
而不是常规的
const protos::FtraceConfig& ftrace_config()
这样做是为了减少头文件依赖,避免把数据源实现层的二进制体积撑大。
向后 / 向前兼容性的说明
tracing 服务在把 DataSourceConfig 转发给同名数据源时,不做任何解码再编码,而是直接把整块原始二进制 blob 传过去。
因此,即使 TraceConfig 里出现了服务编译时还不认识的新字段,服务也会原封不动地透传给数据源。
这样就能在不升级服务的前提下,随时添加新的数据源或新字段。
⚠️ 已知问题:
目前要想给 DataSourceConfig 增加自定义 proto 字段,还得去改 Perfetto 仓库里的 data_source_config.proto,对外部项目来说很不方便。
长期计划是:
- 预留一段字段号给“非官方扩展”
- 提供通用模板接口,让客户端自己定义结构
在那之前,如果你有自定义数据源的配置需求,只能把补丁提上游,让官方先把你的字段合进 data_source_config.proto。
Multi-process data sources(多进程数据源)
有些数据源是“系统单例”。
例如 Android 自带的调度器跟踪,整个系统只有一个实例,由 traced_probes 服务统一提供。
但在更一般的情况下,多个进程都可以声明自己支持同一个数据源——最典型的场景是用 Perfetto SDK 做应用层插桩(track_event)。
默认行为:
当 TraceConfig 里启用了某个数据源,Perfetto 会让所有声明了该数据源的进程一起开始记录。
如果想只让**特定进程(或进程集合)**开启,可以用两个过滤字段:
- producer_name_filter // 精确匹配进程名
- producer_name_regex_filter // 正则匹配进程名
(注意:Perfetto 的运行模型通常是“一个进程 == 一个 Producer;一个 Producer 可托管多个数据源”。)
示例:只给 Chrome 和 Chrome Canary 开启 track_event
buffers { size_kb: 4096 }
data_sources {
config {
name: "track_event"
# 仅在这两个应用里启用
producer_name_filter: "com.android.chrome"
producer_name_filter: "com.google.chrome.canary"
}
}
加了过滤后,tracing 服务只会让匹配到的 Producer 启动该数据源,其余进程即使声明了也不会被激活。
Triggers(触发器)
在常规流程里,一次 tracing 会话的生命周期就是 perfetto 命令行的运行时间:把 TraceConfig 传进去就开始,到 duration_ms 超时或进程被 kill 时结束。
Perfetto 还提供另一种“触发器”模式,可以在配置里预先声明:
- 一组自由命名的触发器字符串;
- 每个触发器是“启动”还是“停止” trace,以及延迟多久生效。
为什么不用直接 起/停 perfetto ?
核心原因是安全模型:
在 Android 这类环境里,只有特权实体(如 adb shell)才能配置/启停 tracing;普通 App 没权限。
触发器给无权限进程提供了一种**受限的、只能“喊口号”**的方式来影响 tracing 生命周期。
概念模型:
- 特权 Consumer(如 adb shell)提前在配置里写好:
“允许哪些触发器名字、各自会做什么”。
- 无权限进程(任意 App)只能在运行时
发出触发信号,但不能决定触发器具体行为。 发信号的方法:
- 命令行:
/system/bin/trigger_perfetto trigger_name
- 或者再起一个独立 perfetto 会话,在配置里只写 activate_triggers: "trigger_name" 也能触发。
触发器分两种类型(下文继续展开)。
Start triggers(启动触发器)
作用: 先把 tracing 会话保持在“空闲待命”状态(不记录任何数据),直到
- 指定的 start 触发器被触发,或
- trigger_timeout_ms 超时。
注意:
- 一旦使用 START_TRACING 模式,就不能再同时用普通的 duration_ms(两者互斥)。
- 触发后,trace 开始记录,并可用 stop_delay_ms 设定再过多久自动结束。
示例配置(PBTX 格式):
trigger_config {
trigger_mode: START_TRACING
triggers {
name: "myapp_is_slow" # 触发器名字
stop_delay_ms: 5000 # 触发后再采 5 秒自动停
}
# 如果 30 秒内都没触发,直接结束,什么也不记录
trigger_timeout_ms: 30000
}
# 其余部分照常:缓冲区、数据源等
buffers { ... }
data_sources { ... }
使用流程:
- 特权端(adb)提前用上述配置启动 perfetto,进程进入“等待触发”状态。
- 普通 App 在关键事件处执行:
/system/bin/trigger_perfetto myapp_is_slow
3. 触发成功 → 立即开始记录,5 秒后自动停;30 秒仍无触发 → 直接退出,trace 文件为空。
Stop triggers(停止触发器)
STOP_TRACING 触发器的作用是:trace 立即开始(和普通模式一样),但当触发器被命中时提前结束,相当于“手动提前停”。
典型用法——飞行记录仪(flight-recorder)模式:
- 缓冲区设成 RING_BUFFER(循环覆盖)
- 启动后一直录,触发事件发生时再停,这样就能把事发前最近一段数据保留下来。
示例配置:
# 如果 30 秒内一直没人触发,就按超时正常结束
trigger_timeout_ms: 30000
trigger_config {
trigger_mode: STOP_TRACING
triggers {
name: "missed_frame" # 触发器名字
stop_delay_ms: 1000 # 触发后再录 1 秒然后停
}
}
# 其余照常:缓冲区、数据源等
buffers { ... }
data_sources { ... }
使用流程:
- 特权端(adb)用上述配置启动 perfetto,trace 立即开始并循环写缓冲区。
- 当 App 检测到掉帧时执行
/system/bin/trigger_perfetto missed_frame
3. 触发后继续录 1 秒 → 自动停,最终 trace 文件里保留了“掉帧前 + 1 秒”的关键数据。
Android 平台
在 Android 平台上,使用 adb shell 时需要注意以下几点:
- Ctrl+C 信号处理:通常 Ctrl+C 会优雅地终止跟踪,但通过 adb shell 运行 perfetto 时,ADB 不会传播此信号。只有在通过 adb shell 使用交互式 PTY 会话时才会传播。
- 非 Root 设备的配置传递限制:
- 在 Android 12 之前的非 Root 设备上,由于过于严格的 SELinux 规则,配置只能通过 cat config | adb shell perfetto -c - 的方式传递(- 表示标准输入)。
- 从 Android 12 开始,可以使用 /data/misc/perfetto-configs 目录来存储配置文件。
- Android 10 之前设备的跟踪文件获取:
- 在 Android 10 之前的设备上,adb 无法直接拉取 /data/misc/perfetto-traces 目录中的文件。
- 解决方法是使用 adb shell cat /data/misc/perfetto-traces/trace > trace 命令来获取跟踪文件。
- 长时跟踪的控制:
- 在基准测试或持续集成(CI)等场景中捕获长时跟踪时,可以使用 PID=PID 来停止跟踪。
其他资源
- TraceConfig 参考文档
- 缓冲区与数据流