很多性能问题不是点一下就能复现的。开机慢、偶发卡顿、亮灭屏后异常、几小时后音频断续、车机周期性抖动、温升后的性能下降,都不适合靠 10 秒手工 trace 解决。
第 15 篇讲现场长 Trace:怎么提前定义窗口,怎么用 trigger 封存问题现场,怎么让文件安全写完,怎么拿回来之后判断数据是否可信。Boot Trace 放在后面作为特殊窗口处理。
现场长 Trace 的目标是保留问题发生前后一段足够解释根因的数据。把所有开关打开抓一小时,只会更快碰到文件、开销和数据丢失问题。现场方案的质量,取决于能不能用可控开销换到可复查证据。
长 Trace 先设计证据窗口
长 Trace 的核心是取舍:问题前后各保留多少,哪些数据源值得留下。开始写配置前,先把问题窗口问清楚:
| 问题 | 影响配置的地方 |
|---|---|
| 问题多久复现一次 | duration_ms、是否需要后台运行、是否需要周期 snapshot |
| 根因在问题前还是问题后 | 选择 STOP trigger 还是 START trigger |
| 需要保留问题前多少秒 | Ring Buffer 大小、数据源宽度、写文件周期 |
| 用户能不能手工停止 | 是否需要自动 trigger、是否接入 bugreport |
| 早期 ADB 不可用 | Boot Trace、平台预置、init/service 触发或厂商方案 |
| trace 启动后可能断 USB | --background-wait、detached mode、snapshot 或后台服务 |
| 文件最大能多大 | max_file_size_bytes、上传策略、本地 TTL |
| 数据是否敏感 | log tag 白名单、是否禁用 atrace_apps: "*" |
偶发 UI 卡顿通常需要问题前几十秒;温升降频可能需要几分钟趋势;车机长稳问题可能要跑几个小时。这类场景通常只周期性拉取 snapshot,或只上传 summary。
常见场景可以先映射到 preset,再写 TraceConfig:
| 场景 | 推荐模式 | 触发和验收重点 |
|---|---|---|
| UI 偶发卡顿 | Ring Buffer + STOP trigger | App marker、FrameTimeline 覆盖目标窗口、触发前窗口足够 |
| ANR 前兆 | Ring Buffer + STOP trigger | 主线程、Binder、锁等待、I/O 和关键业务阶段同时在场 |
| 温升降频 | write_into_file 或 snapshot |
CPU freq、idle、thermal 状态和 App 关键阶段按分钟级趋势保留 |
| 业务 timeout | App marker + STOP/START trigger | 根因在 timeout 前就用 STOP;需要继续观察恢复过程就用 START |
| 音频 underrun | Ring Buffer + STOP trigger | audio/server 事件、sched、binder、wakeups 和 underrun marker 同步 |
现场采集先受设备和权限限制
现场方案不能只看 TraceConfig,还要看设备版本、build 类型和触发者权限。
| 环境 | 可以依赖什么 | 不该假设什么 |
|---|---|---|
| userdebug / root 研发设备 | adb root、/data/misc/perfetto-configs、android.log、更多系统服务和 ftrace 事件 |
仍要控制 log 白名单、buffer 大小和上传范围 |
| user build + adb shell | 常规系统 trace、包名限定的 atrace、审核过的 preset | 不默认依赖 android.log;Android 10/11 非 root 不一定能从 /data/misc/perfetto-configs 读配置 |
| 普通 App | 记录 App 侧 ATrace / Track Event,激活预先声明的 trigger | 不能决定采集哪些系统数据、抓多大、传到哪里 |
| CPU / native / Java profiling | 按数据源要求检查 debuggable、profileable、Android 版本 |
不把 <profileable> 当成现场系统 trace 的通用权限开关 |
文本 proto 配置的扩展名可以是 .pbtxt 或 .pbtx;解析方式由 perfetto --txt 或工具链里的编码步骤决定。本文后面统一用 .pbtxt 做文件名。
Boot Trace 是特殊现场窗口
App 冷启动和系统开机是两类问题。05 篇和旧 Systrace 响应速度实战已经讲过 App 启动路径,这里只处理设备级开机:ADB 还没可用、但 Android 已经进入 /data 挂载后的启动阶段,怎么把系统 trace 抓全、看准、写成可复查结论。
需要 Boot Trace 的场景通常有几类:
| 现象 | 为什么普通 trace 不够 |
|---|---|
| 开机到桌面慢 | 问题发生在 ADB 可用前 |
| OTA 后第一次启动慢 | init、zygote、dexopt、system_server 都要看 |
| Launcher 首帧晚 | 要同时看 system_server、SurfaceFlinger、Launcher |
| 某服务开机阻塞 | 需要 init/service manager/Binder 上下文 |
| 用户说“重启后很久才能用” | 要先定义 ready 标准 |
Boot Trace 是平台或系统启动专项窗口;App 冷启动不走这条路线。分析前要先讲边界:
- Android boot tracing 是 Android 13+ 的能力。更早版本需要厂商自己的启动采集方案。
- 它在 persistent properties 加载后启动,也就是
/data已挂载之后;不覆盖 PMIC 上电、bootloader、kernel early boot、first-stage init。更早阶段要靠 pstore、kernel cmdline/ftrace persistent buffer 或 vendor init instrumentation。 - 这类流程通常假设研发设备或 userdebug 上可以
adb root。生产 user build 需要平台预置、工程版本或厂商诊断入口。 - Trace 的 0 点是 tracing 开始,不是电源键按下或 PMIC 上电。报告里比较同一份 trace 内的相对阶段,不要把不同设备的绝对
ts硬比较。 - “开机完成”没有唯一标准。Launcher 第一帧、可输入、网络可用、导航服务 ready、后台任务完成,可能是不同团队的指标。
- 输出文件要等 trace 停止后,或至少等首次
flush_period_ms后才会出现;persist.debug.perfetto.boottrace是 one-shot,下一次 boot 前要重新设置。
boottrace.pbtxt 必须有合理的 duration_ms 或 flush_period_ms。第一版不要太贪心,先抓 init、sched、freq、idle、进程生命周期、process stats、Activity/Window/SurfaceFlinger 相关 ATrace;log 只在 userdebug 或平台诊断版本上按白名单开启。
Boot Trace 的 buffer 策略要和 ready 点匹配。DISCARD 更容易保住早期 init/zygote 事件,但 buffer 满了会丢后续 ready 点;RING_BUFFER 能保住最近窗口,却可能覆盖早期启动事实。90 秒启动窗口不要用 32MB DISCARD 硬扛,优先加大 buffer、缩窄数据源,或打开周期写文件并验收 traced_buf_chunks_discarded。
1 | duration_ms: 90000 |
一次可复用的抓取流程可以写成这样。pull 前不要只等文件非空;工程脚本应该等待配置里的 duration 结束,再确认文件大小连续稳定:
1 | adb root |
拿到 Boot Trace 后,先验收再分析:
- Trace 是否覆盖从
/data挂载后的 Android boot 窗口到目标 ready 点。 stats里是否有 data loss、packet loss、ftrace setup error。- init、system_server、surfaceflinger、launcher 这些进程是否可见。
- process stats、sched、freq 是否都有数据;log 只在配置和 build 类型都支持时检查。
- 设备是冷启动、
adb reboot,还是 OTA 后第一次启动。 metadata.json是否记录按键/重启方式、boot id、ready 标准、trace start/end、设备 build 和采集者权限。
分析顺序建议固定:init/service 启动、zygote、system_server、PackageManager/ActivityTaskManager、SurfaceFlinger、Launcher、首帧/可输入 ready 点。报告里要写清 ready 标准,否则“开机慢 3 秒”很容易变成跨团队争论。
三种现场长 Trace 模式
Perfetto 的现场长 Trace 可以按这三种模式设计:
| 模式 | 行为 | 适合场景 | 风险 |
|---|---|---|---|
| Ring Buffer + STOP trigger | 一直循环记录最近窗口,异常发生后停止 | 掉帧、ANR 前兆、音频 underrun | Buffer 太小会覆盖根因;trigger 未命中就没有事故现场 |
write_into_file 长录制 |
周期把 Buffer 写到文件 | 几十分钟到数小时趋势 | I/O 开销、文件过大 |
--clone-by-name snapshot |
原始 session 继续跑,按需克隆当前窗口 | 调参、温控、电源策略实验 | 需要设备端 perfetto 支持 --clone-by-name,以 perfetto --help / --version 为准;Android 通常按 Android 14(U)+ 和厂商回合确认 |
不要把三种模式混成一个“大而全配置”。先按问题选择模式,再决定数据源。
Ring Buffer 的基线配置
下面这份配置用于 UI 偶发卡顿:trace 启动后立刻记录,Ring Buffer 只保留最近窗口;当 App、平台服务或测试 harness 检测到慢帧并激活 app_ui_jank trigger 后,Perfetto 再记录 1 秒并停止。
这份配置假设第 13 篇讲过的 App 侧业务 marker 和 trigger emitter 已经接入。trigger_config 不会自己根据 FrameTimeline 自动触发;没有 emitter,session 只会等到 trigger_timeout_ms 清理,不能当成“末尾窗口兜底”。
1 | unique_session_name: "ui_jank_flight_recorder" |
这里放了两个 Buffer:高频 ftrace/atrace 进入大 Buffer,FrameTimeline、process stats、App Track Event 进入小的语义 Buffer。原因是图形问题里,sched 事件保住了但 FrameTimeline、App marker 或进程描述被覆盖,仍然无法判断是 App、SurfaceFlinger、BufferQueue 还是系统调度导致卡顿。
producer_name_regex 用来限定能激活 trigger 的 producer。正则要和实际 emitter producer 名称一致;如果由测试 harness 代理触发,就把 harness producer 加进白名单,或者为 harness 单独开 trigger 名称。
ftrace_config.buffer_size_kb、buffer_size_lower_bound、drain_period_ms 会增加 kernel per-CPU ftrace buffer 占用和 traced_probes drain 唤醒频率,不能当成所有现场 preset 的默认值。这里把它们留在 UI jank preset,是为了降低高频调度事件在短窗口里被 kernel ftrace 覆盖的概率;上线前要用短 trace 验证 ftrace_setup_errors、CPU 开销和写入速率。
FrameTimeline 需要 Android 12 或更高版本。它不是 trigger emitter,只负责在 trace 里提供 app frame、SurfaceFlinger frame、jank_type、present_type、on_time_finish 和 app-SF flow 等事实。atrace_apps 必须限定目标包名,不要把 atrace_apps: "*" 当成现场默认方案。
Ring Buffer 能保留多久,不由 trigger_timeout_ms 决定,而由数据写入速率和 Buffer 大小决定。估算方式仍然是第 12 篇那条:保留窗口秒数 = buffer MB / 写入速率 MB/s。
对 ftrace 还要再看三层 buffer:kernel per-CPU ftrace buffer、producer shared memory、Perfetto central buffer。central buffer 变大只能延长最终保留窗口,救不了 kernel buffer 被 traced_probes 来不及 drain 时的早期丢事件。
现场长 Trace 调优时才考虑 ftrace_config.buffer_size_kb、buffer_size_lower_bound、drain_period_ms、drain_buffer_percent;这些字段要先在短 trace 里验证开销和设备兼容性。
如果 ftrace 写入 2MB/s,64MB 只能留下约 32 秒;如果现场打开 log、Track Event、camera/audio 事件,窗口还会缩短。要保留问题前 2 分钟,就要么减数据源,要么增大 Buffer,要么换成周期写文件。STOP trigger 未命中时不会产出可分析事故现场;需要人工兜底时,用手动停止、snapshot,或另起 fallback session。
UI jank preset 抓完后,先确认 FrameTimeline 真的存在:
1 | SELECT 'expected' AS table_name, COUNT(*) AS rows |
再确认目标窗口内 App 和 SurfaceFlinger 都有帧记录:
1 | WITH target_window AS ( |
如果怀疑 hardirq、softirq 或 kworker 抢 CPU,不要把这些事件放进默认长 trace。先做短窗口专项 preset,再按结果决定是否进入现场方案:
1 | data_sources { |
长录制要周期写文件
几小时问题不能只靠内存 Buffer。这个片段只展示长录制相关字段,实际使用时再加数据源:
1 | duration_ms: 3600000 |
duration_ms 默认不计算系统 suspend 时间。亮灭屏、待机、车机休眠这类按 wall-clock 看的现场问题,应该考虑 prefer_suspend_clock_for_duration: true;它也会影响 trigger stop_delay_ms 的时间语义。
官方 buffers 文档给出的 Android scheduler tracing 典型值约 1 到 2 MB/s;实际要以当前 preset 实测写入速率为准。可录制秒数按 max_file_size_bytes / 实测写入速率 估算。2GB 上限在 1 到 2 MB/s 下只有约 17 到 34 分钟,撑不到 1 小时;要抓 1 小时,就要缩窄数据源、提高文件上限,或改成 summary/snapshot 策略。
log、syscall、page fault、camera/audio、过宽的 Track Event 都可能把写入速率放大一个数量级。flush_period_ms 对长 trace 有价值,它能减少低频 data source 长时间不提交导致的乱序和导入问题;高频 flush 会增加 producer 唤醒和 I/O 压力,现场 preset 通常从 10 到 30 秒试起,并把 Perfetto/Android 版本、write_flush_mode、实测写入速率写进报告。
后台运行和安全停止
现场测试经常会断 USB。普通现场长 Trace 优先用 --background-wait,它会等 data source 启动后再返回 PID,比直接 --background 少丢开头。
Android 12+、root/userdebug 或平台预置路径可用时,将配置放进 /data/misc/perfetto-configs,再启动后台 trace:
1 | adb push field.pbtxt /data/misc/perfetto-configs/field.pbtxt |
Android 10/11 的非 root user build 不要假设这条路径可读,改用 stdin。生产 M2M 工具链更应该用审核过的 preset 或 binary TraceConfig,而不是让服务端下发任意文本配置:
1 | PID=$(adb shell perfetto --background-wait --txt \ |
停止时不要 kill 后立刻 adb pull。Perfetto 还要把尾部数据写完。至少等 perfetto 进程退出,并确认文件存在且大小稳定后再拉取:
1 | adb shell "kill -INT $PID" |
如果脚本能控制两端,可以在发 kill 前启动 inotifyd 监听 close_write;设备上没有 inotifyd 时,用连续大小稳定作为 fallback。现场工具至少要做到一点:确认文件关闭后再打包。
trigger 怎么用
Perfetto trigger 的价值是权限隔离:有权限的一方提前声明 trigger 名称和行为;普通 App 或系统组件只报告“事件发生了”,不能决定抓什么、抓多大、传哪里。
现场方案要把 trigger emitter 写清楚。UI jank 可以由 App 侧 JankStats / FrameMetrics / 自研阈值检测后激活 app_ui_jank,也可以由平台服务或测试 harness 代理激活。无论哪条路,都要做去重和限流,并在 metadata 里记录 trigger source、触发时刻和判断阈值。
App emitter 协议建议固定成一条 JSON 事件,并同时写入同 event_id 的 Track Event:
1 | { |
服务端或分析脚本用 event_id 和时间窗口把外部 trigger 记录、trace 内 Track Event、FrameTimeline 慢帧对上。对不上时,这份 trace 只能当系统侧上下文,不能直接归因到页面或业务动作。
测试环境可以直接激活 trigger:
1 | adb shell /system/bin/trigger_perfetto app_ui_jank |
也可以用一段只包含 activate_triggers 的配置激活同名 trigger:
1 | echo 'activate_triggers: "app_ui_jank"' | \ |
STOP trigger 适合“根因在触发之前”的问题,比如慢帧、ANR 前兆、音频 underrun。START trigger 适合“事件发生后还要继续观察”的问题,比如温度进入 hot 状态后继续抓 10 秒。
使用 START trigger 时,不要再同时配置普通 duration_ms;触发窗口由 trigger_config 管。STOP trigger 未命中时也不要当成普通长录制结果来分析,它没有封存到事故现场。
snapshot 适合调参
有些场景不想停止原 Trace,只想每隔几秒拿一张当前窗口的快照,例如调 CPU policy、thermal 参数、电源策略。--clone-by-name 需要设备端 perfetto 命令支持,Perfetto v49.0+ 才有这条路;Android 上通常按 Android 14(U)+ 和厂商回合确认,最终以 adb shell perfetto --help / --version 为准。
配置里给 session 起名:
1 | unique_session_name: "thermal_snapshot" |
启动后,按需克隆当前 Ring Buffer。原 session 会继续运行:
1 | adb shell perfetto --clone-by-name thermal_snapshot \ |
snapshot 不是问题现场的唯一方案。它适合“反复观察趋势”的实验,不适合需要一次性封存事故现场的触发型问题。
detached mode 少用
--detach 会让 tracing session 脱离 perfetto 命令生命周期,由 traced 服务继续持有 session。官方文档明确不推荐常规使用,因为它容易泄漏 session,让设备长时间处在 tracing 状态。
必须用 detached mode 时,配置里要打开 write_into_file,并准备检查和停止命令:
1 | adb shell perfetto --detach=field_session --txt \ |
工程脚本里要有兜底清理:启动前检查旧 session,测试结束后停止 session,超过 TTL 自动停止。
Bugreport 是现场兜底入口
现场问题经常由用户、测试或运维先触发 bugreport。需要跟 bugreport 合包的长 trace,要在 TraceConfig 里显式声明:
1 | unique_session_name: "field_ui_jank_bugreport" |
bugreport_score > 0 才会成为 bugreport 候选;分数越高,越优先被保存。bugreport_filename 是 Android V / Perfetto v42 之后用于 --save-all-for-bugreport 的可读文件名,旧版本会按默认名字保存。它和普通 -o /path/trace.perfetto-trace、--upload 是不同路径:前者服务 bugreport,后两者服务本次命令输出或 framework reporting。
研发或平台脚本可以用这两条命令验证 bugreport 行为:
1 | adb shell perfetto --save-for-bugreport |
Android S/T 上,bugreport 保存会取走 trace 内容并提前停止 session;Android U+ 上,系统会创建只读 snapshot,原 session 可以继续运行。报告里要写清 Android 版本和验证命令输出,不要把 bugreport 合包当成普通后台 trace 的等价路径。
现场证据包里应该有什么
只拿 .perfetto-trace 不够。现场证据包至少要能回答“这是什么场景、什么时候触发、文件是否完整”。
1 | case-20260504-153012/ |
metadata.json 建议包含这些字段:
1 | { |
这里的 metadata 不替代 trace,它给排查者定位问题窗口、过滤版本、判断环境差异。第 13 篇已经讲过 App 侧如何补页面、业务阶段和用户操作;现场证据包里的 metadata 要负责把这些业务 marker 对上。
summary.json 则是自动化入口,字段要能直接驱动 triage、上传和留存:
1 | { |
summary.json 不写结论作文,只写机器可读状态。evidence_grade 可以按 A/B/C/D 分级:A 表示目标窗口、关键数据源、stats 和 metadata 都完整;B 表示有轻微 data loss 但核心判断可用;C 表示只能保留方向性判断;D 表示 trace 只能用于环境记录。
长 trace 还要写清时钟域。ftrace 常用 CLOCK_BOOTTIME,Android log 带 wall time,其他数据源可能经过 Perfetto 的 ClockSnapshot 同步。
外部 metadata 至少同时记录 elapsed realtime、wall time 和 boot id;更稳的做法是把 trigger 本身也写成 trace 内 Track Event。分析前如果看到 clock sync failure 或 invalid clock snapshot,跨数据源同步要降级处理。
裁剪优先发生在采集侧
Perfetto trace 里有增量状态、track descriptor、stats、metadata。事后硬切二进制文件,很容易破坏解析。推荐顺序是:
- 用 Ring Buffer 控制“只保留最近窗口”。
- 用
stop_delay_ms控制触发后保留多久。 - 用
max_file_size_bytes控制文件上限。 - 上传完整问题窗口的原始 trace。
- 服务端生成 summary、截图、SQL 结果,作为轻量索引。
如果必须离线裁剪,工具要按 TracePacket 重写,并保留必要 descriptor 和 stats。不要用 dd、文本截断、随机切 protobuf 字节流。
上传和清理策略
现场长 Trace 接近真实用户环境,上传策略不能只考虑研发便利:
- 默认只在 Wi-Fi、充电或用户授权条件下上传。
- 文件超过阈值时先上传 summary,原始 trace 留在本地等待人工导出。
- log 按 tag 白名单采集,并在上传前脱敏。
- trace 文件设置 TTL,例如 3 天自动清理。
- trigger 同时使用 TraceConfig 的
max_per_24_h和客户端限流。 - 上传前跑
stats,把 data loss 写进 metadata。 - 不允许服务端下发任意 text proto 配置到用户设备,只允许选择审核过的 preset。
这些限制会减少临时调参空间,但能控制上传、隐私和稳定性风险。
分析前固定检查
现场长 Trace 更容易出现 data loss。分析前先看两个验收门:trace 覆盖的时间是否达到预期,stats 是否说明关键数据源可信。
1 | SELECT |
如果时长不符合预期,先查 trigger_timeout_ms、max_file_size_bytes、后台进程是否被杀、文件是否还没 close、duration_ms 是否按 suspend 语义计算。数据不完整时,报告要写明哪些判断不能下。
再跑全局质量检查:
1 | SELECT name, idx, severity, source, value |
再跑 per-buffer 检查。idx 对应 TraceConfig 里 buffer 的下标,用来判断是高频 ftrace buffer、语义 buffer,还是写文件路径出问题:
1 | SELECT name, idx, severity, source, value |
RING_BUFFER 下的 traced_buf_chunks_overwritten 不一定是错误,它通常表示窗口外的旧数据被覆盖。要单独看它是否把 trigger 前根因覆盖掉:
1 | SELECT name, idx, severity, source, value |
如果现场打开 ftrace,还要记录 ftrace clock、可用事件和 setup error。跨 trace 比较时,clock 或事件集不同,会直接影响 sched/freq 结论可信度:
1 | adb shell cat /sys/kernel/tracing/trace_clock |
把偶发问题变成证据
现场长 Trace 要提前定义窗口、触发、完整性和上传边界。Ring Buffer 保留最近窗口,trigger 封存现场,write_into_file 控制长录制,--background-wait 和文件 close 检查保证落盘,stats 和 metadata 说明这份证据还能信多少。
第 13 篇讲 App 侧 Track Event,本文讲现场窗口。两者合起来,才是慢帧、ANR 前兆、业务 timeout 这类问题能进入同一个问题包的基础。
参考文档
- Trace configuration
- Buffers and dataflow
- Tracing in Background
- Running perfetto in detached mode
- Perfetto command-line reference
- TraceConfig proto reference
- Android boot tracing
- FrameTimeline
- ATrace
- Android Log
- Clock synchronization
- Trace Processor Stats
关于我 && 博客
欢迎关注 Android Performance。