EN

Android Perfetto 系列 15:把现场长 Trace 做成可复查证据

Word count: 6.5kReading time: 28 min
2026/05/04
loading

很多性能问题不是点一下就能复现的。开机慢、偶发卡顿、亮灭屏后异常、几小时后音频断续、车机周期性抖动、温升后的性能下降,都不适合靠 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-configsandroid.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 按数据源要求检查 debuggableprofileable、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_msflush_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
duration_ms: 90000
flush_period_ms: 10000
write_into_file: true
file_write_period_ms: 5000
max_file_size_bytes: 536870912

buffers { size_kb: 131072 fill_policy: DISCARD }

data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "task/task_newtask"
ftrace_events: "task/task_rename"
ftrace_events: "sched/sched_process_exec"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_fork"
ftrace_events: "sched/sched_process_free"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
atrace_categories: "am"
atrace_categories: "wm"
atrace_categories: "gfx"
}
}
}

data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 1000
}
}
}

一次可复用的抓取流程可以写成这样。pull 前不要只等文件非空;工程脚本应该等待配置里的 duration 结束,再确认文件大小连续稳定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
adb root
adb push boottrace.pbtxt /data/misc/perfetto-configs/boottrace.pbtxt
adb shell setprop persist.debug.perfetto.boottrace 1
adb reboot
adb wait-for-device
adb root
adb shell '
path=/data/misc/perfetto-traces/boottrace.perfetto-trace
sleep 100
stable=0
prev=0
while [ "$stable" -lt 3 ]; do
size=$(stat -c %s "$path" 2>/dev/null || echo 0)
if [ "$size" -gt 0 ] && [ "$size" = "$prev" ]; then
stable=$((stable + 1))
else
stable=0
fi
prev=$size
sleep 2
done
'
adb pull /data/misc/perfetto-traces/boottrace.perfetto-trace \
boottrace-$(date +%Y%m%d-%H%M%S).perfetto-trace

拿到 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
unique_session_name: "ui_jank_flight_recorder"

buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}

buffers {
size_kb: 8192
fill_policy: RING_BUFFER
}

trigger_config {
trigger_mode: STOP_TRACING
trigger_timeout_ms: 3600000
triggers {
name: "app_ui_jank"
producer_name_regex: "com\\.example\\.app"
stop_delay_ms: 1000
max_per_24_h: 10
skip_probability: 0.0
}
}

incremental_state_config {
clear_period_ms: 10000
}

data_sources {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
buffer_size_kb: 4096
buffer_size_lower_bound: true
drain_period_ms: 250
compact_sched {
enabled: true
}
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
atrace_categories: "gfx"
atrace_categories: "view"
atrace_categories: "wm"
atrace_categories: "input"
atrace_categories: "binder_driver"
atrace_apps: "com.example.app"
}
}
}

data_sources {
config {
name: "android.surfaceflinger.frametimeline"
target_buffer: 1
}
}

data_sources {
config {
name: "track_event"
target_buffer: 1
track_event_config {
enabled_categories: "ui"
enabled_categories: "memory"
enabled_categories: "app"
}
}
producer_name_filter: "com.example.app"
}

data_sources {
config {
name: "linux.process_stats"
target_buffer: 1
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 5000
}
}
}

这里放了两个 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_kbbuffer_size_lower_bounddrain_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_typepresent_typeon_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_kbbuffer_size_lower_bounddrain_period_msdrain_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
2
3
4
5
SELECT 'expected' AS table_name, COUNT(*) AS rows
FROM expected_frame_timeline_slice
UNION ALL
SELECT 'actual' AS table_name, COUNT(*) AS rows
FROM actual_frame_timeline_slice;

再确认目标窗口内 App 和 SurfaceFlinger 都有帧记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
WITH target_window AS (
SELECT trace_start() AS ts, trace_dur() AS dur
),
frame_process AS (
SELECT
p.name AS process_name,
COUNT(*) AS frame_count,
MIN(a.ts) / 1e9 AS first_frame_s,
MAX(a.ts) / 1e9 AS last_frame_s
FROM actual_frame_timeline_slice a
JOIN process p USING (upid)
JOIN target_window tw
WHERE a.ts BETWEEN tw.ts AND tw.ts + tw.dur
AND (
p.name = 'com.example.app'
OR p.name = 'surfaceflinger'
OR p.name = '/system/bin/surfaceflinger'
OR p.cmdline LIKE '%surfaceflinger%'
)
GROUP BY p.name
)
SELECT
process_name,
frame_count,
first_frame_s,
last_frame_s
FROM frame_process
ORDER BY frame_count DESC;

如果怀疑 hardirq、softirq 或 kworker 抢 CPU,不要把这些事件放进默认长 trace。先做短窗口专项 preset,再按结果决定是否进入现场方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "irq/irq_handler_entry"
ftrace_events: "irq/irq_handler_exit"
ftrace_events: "irq/softirq_entry"
ftrace_events: "irq/softirq_exit"
ftrace_events: "irq/softirq_raise"
ftrace_events: "workqueue/workqueue_queue_work"
ftrace_events: "workqueue/workqueue_execute_start"
ftrace_events: "workqueue/workqueue_execute_end"
}
}
}

长录制要周期写文件

几小时问题不能只靠内存 Buffer。这个片段只展示长录制相关字段,实际使用时再加数据源:

1
2
3
4
5
6
7
8
9
10
11
duration_ms: 3600000
prefer_suspend_clock_for_duration: true
write_into_file: true
file_write_period_ms: 10000
max_file_size_bytes: 2147483648
flush_period_ms: 30000

buffers {
size_kb: 32768
fill_policy: RING_BUFFER
}

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
2
3
4
5
6
7
adb push field.pbtxt /data/misc/perfetto-configs/field.pbtxt

PID=$(adb shell perfetto --background-wait --txt \
-c /data/misc/perfetto-configs/field.pbtxt \
-o /data/misc/perfetto-traces/field.perfetto-trace | tr -d '\r')

echo "perfetto pid: $PID"

Android 10/11 的非 root user build 不要假设这条路径可读,改用 stdin。生产 M2M 工具链更应该用审核过的 preset 或 binary TraceConfig,而不是让服务端下发任意文本配置:

1
2
3
4
5
PID=$(adb shell perfetto --background-wait --txt \
-c - \
-o /data/misc/perfetto-traces/field.perfetto-trace < field.pbtxt | tr -d '\r')

echo "perfetto pid: $PID"

停止时不要 kill 后立刻 adb pull。Perfetto 还要把尾部数据写完。至少等 perfetto 进程退出,并确认文件存在且大小稳定后再拉取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
adb shell "kill -INT $PID"
adb shell "while kill -0 $PID 2>/dev/null; do sleep 1; done"
adb shell '
path=/data/misc/perfetto-traces/field.perfetto-trace
stable=0
prev=0
while [ "$stable" -lt 3 ]; do
size=$(stat -c %s "$path" 2>/dev/null || echo 0)
if [ "$size" -gt 0 ] && [ "$size" = "$prev" ]; then
stable=$((stable + 1))
else
stable=0
fi
prev=$size
sleep 1
done
'

adb pull /data/misc/perfetto-traces/field.perfetto-trace

如果脚本能控制两端,可以在发 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"event_id": "evt-20260504-153012-001",
"trigger_name": "app_ui_jank",
"trigger_source": "app_jankstats",
"page": "Feed",
"action": "scroll",
"reason": "slow_frame",
"threshold_ms": 50,
"observed_value_ms": 83.4,
"window_start_elapsed_ns": 123456700000000,
"window_end_elapsed_ns": 123456789000000,
"dedupe_key": "Feed:scroll:slow_frame:202605041530",
"sample_decision": "record",
"app_state": "foreground"
}

服务端或分析脚本用 event_id 和时间窗口把外部 trigger 记录、trace 内 Track Event、FrameTimeline 慢帧对上。对不上时,这份 trace 只能当系统侧上下文,不能直接归因到页面或业务动作。

测试环境可以直接激活 trigger:

1
adb shell /system/bin/trigger_perfetto app_ui_jank

也可以用一段只包含 activate_triggers 的配置激活同名 trigger:

1
2
echo 'activate_triggers: "app_ui_jank"' | \
adb shell perfetto --txt -c -

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
2
3
4
5
6
unique_session_name: "thermal_snapshot"

buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}

启动后,按需克隆当前 Ring Buffer。原 session 会继续运行:

1
2
3
4
adb shell perfetto --clone-by-name thermal_snapshot \
-o /data/misc/perfetto-traces/thermal_snap_1.pftrace

adb pull /data/misc/perfetto-traces/thermal_snap_1.pftrace

snapshot 不是问题现场的唯一方案。它适合“反复观察趋势”的实验,不适合需要一次性封存事故现场的触发型问题。

detached mode 少用

--detach 会让 tracing session 脱离 perfetto 命令生命周期,由 traced 服务继续持有 session。官方文档明确不推荐常规使用,因为它容易泄漏 session,让设备长时间处在 tracing 状态。

必须用 detached mode 时,配置里要打开 write_into_file,并准备检查和停止命令:

1
2
3
4
5
6
adb shell perfetto --detach=field_session --txt \
-c /data/misc/perfetto-configs/field.pbtxt \
-o /data/misc/perfetto-traces/field.perfetto-trace

adb shell perfetto --is_detached=field_session
adb shell perfetto --attach=field_session --stop

工程脚本里要有兜底清理:启动前检查旧 session,测试结束后停止 session,超过 TTL 自动停止。

Bugreport 是现场兜底入口

现场问题经常由用户、测试或运维先触发 bugreport。需要跟 bugreport 合包的长 trace,要在 TraceConfig 里显式声明:

1
2
3
4
5
6
7
8
unique_session_name: "field_ui_jank_bugreport"
bugreport_score: 100
bugreport_filename: "field-ui-jank.pftrace"

buffers {
size_kb: 65536
fill_policy: RING_BUFFER
}

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
2
adb shell perfetto --save-for-bugreport
adb shell perfetto --save-all-for-bugreport

Android S/T 上,bugreport 保存会取走 trace 内容并提前停止 session;Android U+ 上,系统会创建只读 snapshot,原 session 可以继续运行。报告里要写清 Android 版本和验证命令输出,不要把 bugreport 合包当成普通后台 trace 的等价路径。

现场证据包里应该有什么

只拿 .perfetto-trace 不够。现场证据包至少要能回答“这是什么场景、什么时候触发、文件是否完整”。

1
2
3
4
5
6
7
case-20260504-153012/
trace.perfetto-trace
config.pbtxt
metadata.json
stats.sql.txt
log_excerpt.txt
summary.json

metadata.json 建议包含这些字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"case_id": "case-20260504-153012",
"trace_config": "ui_jank_flight_recorder_v3",
"trigger": "app_ui_jank",
"trigger_source": "app_jankstats",
"trigger_elapsed_realtime_ns": 123456789000000,
"trigger_wall_time": "2026-05-04T15:30:12+08:00",
"trace_start_elapsed_realtime_ns": 123456730000000,
"trace_end_elapsed_realtime_ns": 123457790000000,
"boot_id": "device_boot_id",
"device": "device_name",
"build_fingerprint": "fingerprint",
"app_version": "1.2.3",
"battery_percent": 78,
"thermal_state": "nominal",
"upload_reason": "slow_frame"
}

这里的 metadata 不替代 trace,它给排查者定位问题窗口、过滤版本、判断环境差异。第 13 篇已经讲过 App 侧如何补页面、业务阶段和用户操作;现场证据包里的 metadata 要负责把这些业务 marker 对上。

summary.json 则是自动化入口,字段要能直接驱动 triage、上传和留存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"case_id": "case-20260504-153012",
"preset_id": "ui_jank_flight_recorder_v3",
"trigger_name": "app_ui_jank",
"trigger_source": "app_jankstats",
"trigger_ts_elapsed_ns": 123456789000000,
"trace_start_ns": 123456730000000,
"trace_end_ns": 123457790000000,
"pre_trigger_window_ms": 59000,
"post_trigger_window_ms": 1000,
"trigger_matched": true,
"file_complete": true,
"stats_status": "warning",
"affected_sources": ["linux.ftrace", "android.surfaceflinger.frametimeline"],
"usable_metrics": ["frame_timeline", "main_thread_sched"],
"evidence_grade": "B",
"privacy_mode": "log_tag_whitelist",
"upload_mode": "summary_first",
"conclusion_level": "probable",
"next_action": "collect_short_irq_preset",
"sampling_policy_version": "2026-05-04",
"sample_rate": 1.0,
"skip_probability": 0.0,
"sample_decision": "record",
"quota_bucket": "ui_jank_daily",
"quota_remaining": 8,
"upload_allowed": true,
"upload_block_reason": "",
"retention_until": "2026-05-07T15:30:12+08:00"
}

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
2
3
SELECT
trace_start() / 1e9 AS trace_start_s,
trace_dur() / 1e9 AS trace_dur_s;

如果时长不符合预期,先查 trigger_timeout_msmax_file_size_bytes、后台进程是否被杀、文件是否还没 close、duration_ms 是否按 suspend 语义计算。数据不完整时,报告要写明哪些判断不能下。

再跑全局质量检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
severity IN ('error', 'data_loss')
OR name IN (
'traced_buf_chunks_discarded',
'ftrace_setup_errors',
'ftrace_cpu_has_data_loss',
'clock_sync_failure',
'invalid_clock_snapshots'
)
OR name GLOB 'traced_buf_*packet_loss'
OR name GLOB 'ftrace_cpu_*overrun*'
OR name GLOB 'ftrace_cpu_*dropped*'
)
ORDER BY name, idx;

再跑 per-buffer 检查。idx 对应 TraceConfig 里 buffer 的下标,用来判断是高频 ftrace buffer、语义 buffer,还是写文件路径出问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
name GLOB 'traced_buf_*'
OR name IN (
'traced_flushes_failed',
'traced_final_flush_failed',
'config_write_into_file_no_flush',
'config_write_into_file_discard'
)
)
ORDER BY idx, name;

RING_BUFFER 下的 traced_buf_chunks_overwritten 不一定是错误,它通常表示窗口外的旧数据被覆盖。要单独看它是否把 trigger 前根因覆盖掉:

1
2
3
4
5
SELECT name, idx, severity, source, value
FROM stats
WHERE name = 'traced_buf_chunks_overwritten'
AND value != 0
ORDER BY idx;

如果现场打开 ftrace,还要记录 ftrace clock、可用事件和 setup error。跨 trace 比较时,clock 或事件集不同,会直接影响 sched/freq 结论可信度:

1
2
adb shell cat /sys/kernel/tracing/trace_clock
adb shell cat /sys/kernel/tracing/available_events | head

把偶发问题变成证据

现场长 Trace 要提前定义窗口、触发、完整性和上传边界。Ring Buffer 保留最近窗口,trigger 封存现场,write_into_file 控制长录制,--background-wait 和文件 close 检查保证落盘,stats 和 metadata 说明这份证据还能信多少。

第 13 篇讲 App 侧 Track Event,本文讲现场窗口。两者合起来,才是慢帧、ANR 前兆、业务 timeout 这类问题能进入同一个问题包的基础。

参考文档

  1. Trace configuration
  2. Buffers and dataflow
  3. Tracing in Background
  4. Running perfetto in detached mode
  5. Perfetto command-line reference
  6. TraceConfig proto reference
  7. Android boot tracing
  8. FrameTimeline
  9. ATrace
  10. Android Log
  11. Clock synchronization
  12. Trace Processor Stats

关于我 && 博客

欢迎关注 Android Performance

CATALOG
  1. 1. 长 Trace 先设计证据窗口
  2. 2. 现场采集先受设备和权限限制
  3. 3. Boot Trace 是特殊现场窗口
  4. 4. 三种现场长 Trace 模式
  5. 5. Ring Buffer 的基线配置
  6. 6. 长录制要周期写文件
  7. 7. 后台运行和安全停止
  8. 8. trigger 怎么用
  9. 9. snapshot 适合调参
  10. 10. detached mode 少用
  11. 11. Bugreport 是现场兜底入口
  12. 12. 现场证据包里应该有什么
  13. 13. 裁剪优先发生在采集侧
  14. 14. 上传和清理策略
  15. 15. 分析前固定检查
  16. 16. 把偶发问题变成证据
  17. 17. 参考文档
  18. 18. 关于我 && 博客