EN

Android Perfetto 系列 12:用 stats 判断一份 Trace 还能信多少

Word count: 5.8kReading time: 23 min
2026/05/04
loading

打开一份 Trace 后,最麻烦的情况是:文件里看起来有数据,但中间丢过。UI 里还能看到轨道,SQL 也能查出一些行,可线程状态少了一段、进程名对不上、某些 Track Event 消失了。继续分析下去,结论会越来越像猜。

SQL 只能分析 Trace 里还存在的证据。这个问题不处理好,后面的查询写得再漂亮,也只是把一份缺证据的 Trace 包装成表格。

02 已经讲过怎么抓,11 已经讲过怎么查 SQL;第 12 篇只做一件事:判断 Trace 在哪里丢过数据、影响哪些结论、下一次抓取该怎么改配置。

文章会顺着数据从内核走到 Trace 文件的链路,逐段定位丢点:先用 stats 表给 Trace 做体检;ftrace 跟不上时怎么看 lost events;producer 与 TraceWriter packet loss 要先定位再归因;central buffer 在 Ring 模式下覆盖旧数据、在 Discard 模式下丢新数据,对应短 Trace 和长 Trace 两种处理思路;增量状态丢失会让事件还在但名字和上下文缺失;长 Trace 还要额外关注 flush。最后给一个在分析报告里写”这份 Trace 还能信多少”的模版。

Trace 有数据,不代表证据完整

Perfetto 不是无损录像机。它的写入路径为了低开销做了异步化:producer 先写自己的共享内存;共享内存页满、Trace 结束或 flush 时,再提交给 traced 的 central buffer;之后在 Trace 结束、streaming 或周期写文件时落到输出文件。

任意一段跟不上,都会产生 data loss。flush 解决的是 producer 把未提交数据交给 tracing service,不等同于直接写文件。

分析 Trace 前,建议固定做一次“验收”:

1
2
3
4
5
6
打开 Trace
-> 查 stats
-> 定位丢失在 ftrace、central buffer 还是解析阶段
-> 标记哪些指标只能参考
-> 决定这份 Trace 能回答哪些问题
-> 修改下一次抓取配置

这一步不花太多时间,却能避免后面半小时都在分析一份有缺口的 Trace。

一个常见现场是这样的:Perfetto UI 里能找到卡顿帧,也能看到主线程切片,但 stats 里同时出现 ftrace_cpu_has_data_loss。这时要先分清证据来源。

Android 12+ 且启用 android.surfaceflinger.frametimeline 时,如果目标窗口内 expected/actual FrameTimeline slices 存在,并且没有 parser/pairing 异常,FrameTimeline 仍可作为入口。App SDK Track Event 也可能仍能帮助定位业务阶段。

但来自 atrace/ftrace 的 Choreographer、HWUI、RenderThread、SurfaceFlinger 切片,以及 Runnable/wakeup 结论,都要随 ftrace loss 降级。“没有看到 Runnable 等待”不能写成“没有调度问题”。

数据从哪里走到 Trace 文件

Perfetto 的数据路径可以拆成四段:

阶段 发生在哪里 常见问题
数据源产生事件 traced_probes、App 进程、native 进程、heapprofd 事件开太多,突发写入太密
producer 共享内存 每个 producer 与 traced 之间的共享内存 producer 写得太快,traced 没来得及搬
central buffer(tracing service 的中央缓冲区) TraceConfig 里的 buffers Ring 覆盖旧数据,Discard 丢新数据
输出文件与解析 trace 文件、Trace Processor 长 Trace 写文件不及时,导入排序或 clock 同步异常

增量状态是跨阶段问题,不是独立传输路径。Track Event 的 track descriptor、interned strings,以及进程/线程元数据这类上下文 packet 一旦被覆盖或缺包,后续事件可能只剩 ID,或者被 Trace Processor 跳过。

linux.ftrace 还多一段内核 per-CPU buffer。内核事件先进入每个 CPU 的 ftrace per-CPU buffer,再由 traced_probes 周期读取。打开 sched_switchsched_waking、Binder、IRQ 这类 ftrace tracepoint,以及部分 Camera/GPU vendor ftrace 事件后,这一段最容易出问题。

Binder 证据需要显式启用 binder_driver 或对应 binder ftrace events,并核对 ftrace_setup_errors;可用 category 和事件会随 Android/内核版本变化。

1
2
3
SELECT *
FROM metadata
WHERE name = 'ftrace_setup_errors';

Buffers and dataflow 文档里有一句很实用的估算:如果总写入速率是 2 MB/s,16 MB central buffer 大约只能保留 8 秒。抓 30 秒、60 秒甚至几分钟时,单靠一个小 central buffer 不够。

这也是很多“偶现问题 Trace 只有前半段能用”的来源。录制时间写了 60 秒,不代表每个 buffer 都保存了 60 秒数据。Trace 文件里的时间轴长度、buffer 能保留的窗口、某类数据源的实际覆盖范围,是三件事。

用 stats 给 Trace 做体检

第 11 篇已经讲过 Trace Processor。这里直接用它跑一条 SQL。这条 SQL 适合放进每个分析脚本的开头,它不只查 error / data_loss,也把 central buffer、ftrace setup、FrameTimeline parser/pairing、flush/clock 这类会影响结论的 info 项一起拉出来。

注意:官方 stats 参考表里的类型写作 kErrorkDataLosskInfo,但 SQL 里的 severity 值是小写的 errordata_lossinfo

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
./trace_processor query trace.perfetto-trace "
SELECT
name,
idx,
severity,
source,
value
FROM stats
WHERE value != 0
AND (
severity IN ('error', 'data_loss')
OR name IN (
'traced_buf_chunks_overwritten',
'traced_buf_chunks_discarded',
'ftrace_setup_errors',
'frame_timeline_event_parser_errors',
'frame_timeline_unpaired_end_event',
'config_write_into_file_no_flush',
'config_write_into_file_discard',
'traced_flushes_failed',
'traced_final_flush_failed'
)
OR name GLOB 'clock_sync_failure*'
OR name GLOB 'perf_*'
OR name GLOB 'heapprofd_*'
OR name GLOB 'meminfo_*'
OR name GLOB 'rss_stat_*'
OR name GLOB 'proc_stat_*'
OR name GLOB 'memory_snapshot_*'
)
ORDER BY name, idx;
"

读结果时不要只看“有没有非零”。先把常见项映射到处置决策:

stats / 现象 影响的数据源 还能回答的问题 不能回答的问题 处理决定
ftrace_cpu_has_data_loss schedthread_state、wakeup、Binder kernel 事件 已经留下来的正向证据可以作为线索 不能写“没有 Runnable 等待”“没有 wakeup 延迟”“没有调度阻塞” 调度归因降级,必要时复抓
traced_buf_chunks_overwritten / discarded 命中同一个 buffer 的数据源 未被覆盖窗口里的正向证据 被覆盖窗口里的 App marker、FrameTimeline、process/thread metadata idx 找 buffer 和 target_buffer,决定复抓或降级
FrameTimeline 表有行但目标窗口覆盖不完整 FrameTimeline 可说明残留帧事实 不能证明目标窗口帧指标完整 输出 frame_metrics_partial
clock_sync_failure* 时间戳转换 部分非时间类元数据 不能做精确时序、duration、跨线程排序 先按时间语义不可信处理
perf_* / heapprofd_* / meminfo_* / rss_stat_* CPU profiling、native allocation、RSS/PSS、系统内存 依赖未受影响的数据源的结论 对应 profile 或内存口径 指标级降级

报告字段也要固定,否则每次还是靠人写判断:

1
2
trace_quality_status,affected_sources,affected_metrics,usable_metrics,unusable_metrics,evidence_grade,fallback_used,recapture_required,recapture_reason,next_trace_config_change
partial,ftrace;sched,Runnable;wakeup,FrameTimeline;AppTrackEvent,RunnableNegativeConclusion,weak,false,true,ftrace_cpu_has_data_loss,reduce_ftrace_events+increase_ftrace_buffer

如果要专门排查 buffer 压力,可以再按 buffer 维度看覆盖和丢弃。有些版本里这些计数会被归类为 info,只按 data_loss 过滤会漏掉线索:

1
2
3
4
5
6
7
8
9
10
11
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
severity IN ('error', 'data_loss')
OR name IN (
'traced_buf_chunks_overwritten',
'traced_buf_chunks_discarded'
)
)
ORDER BY name, idx;

如果要确认帧指标能不能用,再查 FrameTimeline。它不是 ftrace 的直接产物,但也会受 data source 是否启用、Android 版本、central buffer 覆盖和 parser/pairing 异常影响:

1
2
3
4
5
SELECT 'actual' AS table_name, COUNT(*) AS row_count
FROM actual_frame_timeline_slice
UNION ALL
SELECT 'expected' AS table_name, COUNT(*) AS row_count
FROM expected_frame_timeline_slice;

只查全表行数还不够。目标窗口里 expected/actual 都有覆盖,min/max ts 能包住窗口,且 FrameTimeline 所在数据源没有命中同源 buffer loss,才能写“帧事实可信”。如果只是全表有行,报告应写“FrameTimeline 可作为入口,但目标窗口完整性仍需确认”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
INCLUDE PERFETTO MODULE time.conversion;

WITH target_window(start_ts, end_ts) AS (
VALUES (123000000000, 123500000000)
)
SELECT
'actual' AS table_name,
COUNT(*) AS row_count,
time_to_ms(MIN(a.ts)) AS min_ts_ms,
time_to_ms(MAX(a.ts + a.dur)) AS max_end_ms
FROM actual_frame_timeline_slice a
JOIN target_window w
WHERE a.ts < w.end_ts
AND a.ts + a.dur > w.start_ts
UNION ALL
SELECT
'expected' AS table_name,
COUNT(*) AS row_count,
time_to_ms(MIN(e.ts)) AS min_ts_ms,
time_to_ms(MAX(e.ts + e.dur)) AS max_end_ms
FROM expected_frame_timeline_slice e
JOIN target_window w
WHERE e.ts < w.end_ts
AND e.ts + e.dur > w.start_ts;

如果要做更宽的采集配置复盘,可以再看所有非零统计项:

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

source 也要看。source = 'trace' 通常指采集或 trace 内容本身的问题,优先回到 TraceConfig、producer 或设备能力;source = 'analysis' 更偏 Trace Processor 导入、解析、建模阶段,优先看导入参数、解析器版本、producer 写法或 trace 格式问题。看到 parser error 时,不要默认只靠加 buffer 解决。

stats 的读法不要停在“有没有值”。要继续问三件事:

  • 丢在哪里:是 ftrace、producer 共享内存、central buffer,还是解析阶段。
  • 影响什么:是调度事件不可信,还是进程名/线程名可能缺失,还是 App 自定义事件、FrameTimeline 或 log 缺段。
  • 怎么补救:下一次抓取是减事件、加 buffer、分 buffer、写文件,还是缩短窗口。

ftrace 丢失:内核事件跟不上

如果 stats 里出现 ftrace_cpu_has_data_loss,说明内核 ftrace per-CPU buffer 到用户态读取这一段发生了丢失。典型场景是事件开得太多,或者设备压力大时 traced_probes 没能及时读取每个 CPU 的 buffer。

ftrace_cpu_overrun_* 这类计数可以辅助判断哪个 CPU 发生过 overrun,但不能精确还原 Trace 时间轴里的丢失阶段。要定位更具体的时间点,需要 FtraceEventBundle.lost_events 或原始 packet/UI 证据;没有这些证据时,只能降级为“该 CPU 的 ftrace/sched 证据不可靠”。

ftrace 专项检查不要只依赖通用入口。overrun 和 dropped event 相关统计在不同版本里可能是 info,需要专门拉出来:

1
2
3
4
5
6
7
8
9
10
11
12
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
name IN (
'ftrace_cpu_has_data_loss',
'ftrace_setup_errors'
)
OR name GLOB 'ftrace_cpu_overrun_*'
OR name GLOB 'ftrace_cpu_dropped_events_*'
)
ORDER BY name, idx;

这类丢失会影响 CPU 调度、Runnable 时间、唤醒关系、Binder 内核事件等判断。已经观察到的 sched rows 可以作为残留线索,负向结论要全部降级:不能写“没有 Runnable 等待”“没有 wakeup 延迟”“没有调度阻塞”。要讨论 CPU busy,还要在同一窗口内检查 schedthread_state、CPU idle、CPU frequency 或 sys_stats cpufreq 覆盖。

下一次抓取可以从这段配置开始调:

1
2
3
4
5
6
7
8
9
10
11
12
data_sources {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
buffer_size_kb: 32768 # 增大内核每 CPU buffer
drain_period_ms: 100 # 更频繁地从内核读取
}
}
}

还要删掉当前问题用不到的 ftrace 事件。不要把所有 checkbox 都打开后再靠 buffer 硬扛。滑动 jank 先保留 sched/sched_switchsched/sched_waking、FrameTimeline 和 App marker,先删无关 IRQ、Camera、GPU vendor 事件;Binder 慢才打开 binder events;Camera open 才加 Camera/GPU 相关事件。高频 ftrace 事件会挤掉同一层 ftrace per-CPU buffer 里的低频 kernel tracepoints;process stats、log、power、FrameTimeline 这类数据被挤掉,则属于后面的 central buffer 混放问题。

buffer_size_kbdrain_period_ms 属于本地调参项,官方参考也提醒它们在并发 ftrace session 下不保证完全按配置生效。

公开 preset 先减事件。只有需要非默认 ftrace buffer,且要兼容不同 Perfetto 版本时,再考虑 buffer_size_kb + buffer_size_lower_bound;Perfetto v43+ 的多数配置可以保持 unset。写公开 preset 时,不要只给一个数字;要同时写清楚设备范围、Android/Perfetto 版本、事件集合和文件增长速度。

producer / TraceWriter packet loss:先定位再归因

如果出现 traced_buf_trace_writer_packet_loss,先把它当成 packet loss 总信号,不要直接归因为 producer shared memory。它可能来自 producer 共享内存突发,也可能和 RING_BUFFER 起点前、DISCARD 结束后的 packet loss、sequence 缺包、central buffer policy 有关。归因要结合 traced_buf_chunks_overwrittentraced_buf_chunks_discardedtraced_buf_sequence_packet_losstraced_buf_patches_failed、buffer idx 和 fill policy。

Android 常见的 android.os.Trace / androidx.tracing 走 atrace/ftrace 路径,优先看 ftrace loss 和 ftrace_setup_errors。Perfetto SDK Track Event、自定义 DataSource、大块数据包、heapprofd 采样这类 producer,才更直接对应 shared memory 和 producer 侧写入策略。

这类丢失的判断方式很直接:如果你的 App 自定义事件正好用于划分业务阶段,那么业务阶段耗时就可能缺段;如果丢的是 heapprofd 或 CPU profiling 相关数据,采样结论也要降低可信度。

报告里要把缺失 marker 写成字段,而不是只写一句“业务事件可能丢了”。例如 checkout_submitfirst_frame_present 之间缺少 network_done marker,只能报告端到端耗时,不能拆分网络/渲染阶段:fallback_used=truemissing_marker=network_donerecapture_reason=track_event_marker_missing

共享内存还有一个容易漏掉的数字:默认大小是 256KB,官方经验范围大概是 128-512KB。这个大小应付普通切片通常够用,但扛不住“大包突发”。官方文档举过截图类数据源的例子:平均写入速率看起来不高,但一次性写入 2MB 时,瞬时写入速度会远超 traced 搬运速度。

处理顺序建议这样排:

  • 降低事件密度。热循环里不要每次迭代都打 Track Event。
  • 拆小超大事件。单个大包容易撑爆共享内存。
  • Perfetto SDK producer 可以评估 shmem_size_hint_kb 或 producer shared memory 大小。适用条件是确认瓶颈来自 producer 侧突发,而不是 central buffer 覆盖。
  • BufferExhaustedPolicy::kStall 可以避免直接丢包,但会让 producer 在 buffer 耗尽时等待。只有确认这类阻塞开销可接受时,才适合用它保护关键事件。

这部分会在 App 侧现场 Trace 文章里展开。业务埋点越密,越需要控制事件密度、单包大小和 producer 侧 backpressure 策略。

central buffer 丢失:Ring 覆盖或 Discard 丢新数据

TraceConfig 里的 buffers 是 central buffer。这里有两种常见丢失,排查时建议直接看统计项名字,不要只依赖 severity 分类:

  • traced_buf_chunks_overwrittenRING_BUFFER 写满后覆盖旧数据。
  • traced_buf_chunks_discardedDISCARD 写满后丢掉新数据。

容量复盘要按 buffer idx 看。idxtraced_buf_* 通常是 buffer index,对 ftrace CPU stat 通常是 CPU id;报告里要写清 idx_semantics,否则很容易把 CPU 3 和 buffer 3 混在一起。

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

一个 central buffer 装所有数据时,高频 sched 事件可能把 process stats、log、power、FrameTimeline、App Track Event、SurfaceFlinger/Winscope 这类低频或关键数据挤出去。结果是 UI 里还能看到大量调度事件,但线程名、进程名、内存采样、log、帧指标只剩很少一部分。

android.log 还要看设备边界。官方 android.log datasource 只支持 userdebug;eng 或内部系统镜像也要按设备能力验证。生产 user build 上不要默认期待 android.log 数据源可用,可将外部 logcat、bugreport 或平台侧脱敏日志放进同一个 case 包。

Trace configuration 文档给过一个很贴近 Android 的例子:scheduler tracing 在普通手机上可能每秒写大约 10000 个事件、约 1MB;而内存统计 5 秒才写一次。两者放进同一个 4MB central buffer,内存快照可能只剩一条,甚至完全被挤掉。这种 Trace 打开后并不会报“内存采样没工作”,它只是没有留下足够证据。

短 Trace:先解决 buffer 混放

更稳的做法是把高频和低频数据分开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
buffers { size_kb: 131072 fill_policy: RING_BUFFER } # 0: 高频 ftrace
buffers { size_kb: 32768 fill_policy: RING_BUFFER } # 1: 低频元数据和日志

data_sources {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
}
}
}

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

关键图形数据源和 App SDK Track Event 也要检查 target_buffer。FrameTimeline、SurfaceFlinger/Winscope、App SDK Track Event 可以通过各自 data source 的 target_buffer 隔离;HWUI、Choreographer、RenderThread atrace 则跟随 linux.ftrace,要通过减少 ftrace 事件、增大 ftrace per-CPU buffer、增大或隔离 ftrace 的 central buffer 来保护。分 buffer 只能隔离 central buffer 竞争,不能修复 producer shared memory 已经写爆的问题。

确认“被挤掉的是不是本次分析依赖的数据”时,按依赖逐项查:目标窗口 FrameTimeline expected/actual 行数,App marker 数量,process/thread 名称完整性,log/power/memory 是否覆盖目标窗口,stats 里命中哪个 buffer idx。这些都对不上时,优先复抓;只影响解释变量时,报告降级即可。

长 Trace:再解决周期写文件

如果要抓长时间现场问题,只加大内存 buffer 不够。应该让 Perfetto 周期写文件:

1
2
3
4
5
6
7
8
9
10
duration_ms: 600000
write_into_file: true
file_write_period_ms: 2500
flush_period_ms: 10000
max_file_size_bytes: 2147483648

buffers {
size_kb: 32768
fill_policy: RING_BUFFER
}

file_write_period_ms 越短,内存压力越低,但 I/O 开销越高。长 Trace 还要显式考虑 flush:新版本可以结合 write_flush_mode 自动策略,跨版本公开配置优先显式写 flush_period_ms,并在 stats 里检查 config_write_into_file_no_flushconfig_write_into_file_discard。Trace configuration 文档提到 Android 常见 Trace 数据速率约 1-4 MB/s,这个数字只能做初始估算,专项配置要自己测一次文件增长速度。

如果问题有明确触发点,优先缩小采集窗口或使用触发式抓取,保留问题前后几十秒,比盲目长录更容易减少覆盖和 I/O 压力。

写文件还要看设备和启动方式。/data/misc/perfetto-traces/ 这类限制指的是 TraceConfig.output_path 字段在 Android R+ system traced 场景下的路径约束;普通 adb shell perfetto -o ... 和 bugreport clone/save 是另一条 consumer 路径。要进入 bugreport,配置 bugreport_score > 0,由 dumpstate 触发保存;Android S/T 和 U+ 的 session 处理方式还不同。公开 preset 里不要只贴 pbtxt,还要写清 Android/Perfetto 版本、执行命令和输出路径。

I/O 相关 tracepoint、syscall、pagefault 也要谨慎。长 Trace 写文件可能和被测 workload 竞争同一存储路径;stats 干净只能说明 trace 数据较完整,不能证明采集对现场没有扰动。报告里要区分 trace_quality_statuscollection_perturbation_risk

估算时可以按这条粗线算:

1
2
3
4
central buffer 至少需要的大小
= 采集写入速率 MB/s
* file_write_period_ms / 1000
* 安全系数

安全系数通常不要低于 2。因为写入速率不是匀速的,滑动、启动、Camera open、音频路由切换都会制造短时间突发。

增量状态丢失:事件还在,名字和上下文缺了

Perfetto 为了降低体积,会复用上下文信息。Track Event 的 track descriptor、interned strings,以及进程/线程元数据这类上下文 packet 一旦被覆盖或缺包,后续事件就可能只剩 ID,或者无法完整解析。

更隐蔽的表现通常是:

  • UI 里只有 tid,看不到进程名。
  • Track Event 轨道名字缺失或解析异常。
  • stats 出现 sequence、incremental state、tokenizer、interning、track hierarchy 相关统计项。
  • Trace Processor 跳过引用了缺失描述包的 packet。

优先看的统计项是这几个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SELECT name, idx, severity, source, value
FROM stats
WHERE value != 0
AND (
name IN (
'traced_buf_trace_writer_packet_loss',
'traced_buf_sequence_packet_loss',
'traced_buf_incremental_sequences_dropped',
'interned_data_tokenizer_errors',
'track_event_tokenizer_errors',
'track_hierarchy_missing_uuid',
'tokenizer_skipped_packets'
)
OR name GLOB 'track_descriptor_*'
OR name GLOB 'track_event_*missing*'
OR name GLOB 'track_event_*invalid*'
)
ORDER BY name, idx;

tokenizer、interning、track hierarchy 相关统计项更像解析后果或辅助信号。它们能提示“名字和上下文缺了”,但根因通常还要回到 sequence 缺包、Ring 覆盖或 producer 写入突发。报告里不要只写“Track Event 未见 packet loss”,还要拆出 track_event_packet_loss_counttrack_event_sequence_loss_counttrack_event_tokenizer_error_countinterning_error_counttrack_hierarchy_error_counttrack_event_invalid_end_count,再给 track_event_trust_level

处理方式有两类:

1
2
3
incremental_state_config {
clear_period_ms: 5000
}

clear_period_ms 会让使用增量状态的数据源周期性重新发描述信息,适合 Ring buffer 场景。另一个办法是用 target_buffer 把低频元数据放进更不容易被覆盖的 buffer。

这个配置只会通知声明支持 incremental state clear 的数据源,不是对所有数据源强行生效。Buffers and dataflow 文档给的口径更严格:clear_period_ms 最好比 central buffer 估算可保留窗口低一个数量级。比如某个 buffer 大约只能留 60 秒数据,5 秒清一次描述信息比 30 秒清一次稳得多。

长 Trace 还要关注 flush

长时间抓取里,有些数据源事件很少,可能在共享内存里待很久才提交。UI 里会表现成时间轴异常拉长,或者 Trace Processor 导入时出现 out-of-order 一类问题。

可以加周期 flush:

1
flush_period_ms: 10000

这个配置会让 tracing service 周期性请求数据源提交未写满的共享内存页。Buffers and dataflow 文档把 10-30 秒作为长 Trace 的常见范围;具体行为还要看设备上的 Android/Perfetto 版本,以及 write_into_filewrite_flush_mode 等配置。

实战里优先确认 write_into_filefile_write_period_ms、I/O 压力和导入错误,再决定是否显式设置 flush_period_ms;过高频 flush 会增加开销。

如果 stats 里出现 traced_flushes_failedtraced_final_flush_failed,先在报告里写清可信度边界,再检查长 Trace 的 file write、flush/write flush 配置和 I/O 压力。它不应被当成普通 parser 噪声。

如果已经拿到一份没有周期 flush 的长 Trace,导入时又遇到 out-of-order,一次性救急可以让 Trace Processor 用 --full-sort 导入。这会牺牲内存,把更多排序工作放到导入阶段;它适合离线分析,不适合作为采集配置的长期答案。

--full-sort 不能修 clock 问题。看到 clock_sync_failureclock_sync_failure_unknown_source_clockclock_sync_mixed_clock_sourcesclock_sync_failure_undeferrable_packet_loss 这类统计项时,要先按“时间戳/clock 语义不可信”处理,再结合 packet loss、ClockSnapshot、producer 写法和导入排序定位根因;不要只靠 --full-sort 或 flush 解释。

报告里怎么写可信度

有 data loss 的 Trace 不一定完全作废,但报告必须写边界。结构化字段先定下来:

1
2
3
stat_name,stat_idx,idx_semantics,severity,source,value,affected_evidence,affected_metrics,decision,next_config_action
ftrace_cpu_has_data_loss,3,cpu,data_loss,trace,1,sched/thread_state/wakeup,RunnableNegativeConclusion,degrade_or_recapture,reduce_ftrace_events+increase_ftrace_buffer
traced_buf_chunks_overwritten,0,buffer,data_loss,trace,8,buffer0_data_sources,AppTrackEvent;FrameTimeline,recapture_if_target_window_missing,split_buffers+increase_buffer0

自然语言模板可以这样写:

1
2
3
4
完整性检查:stats 表存在 ftrace_cpu_has_data_loss,CPU 3 value=1;ftrace_cpu_overrun_delta / dropped_events_delta 只作为辅助说明。
影响范围:sched/ftrace 事件可能缺失,线程状态占比和唤醒关系只能作为参考;不能写“没有 Runnable 等待”。
仍可使用:未发现 FrameTimeline parser/pairing 异常,目标窗口内 expected/actual slices 覆盖完整,且未命中同源 buffer loss;App SDK Track Event 未见 packet/sequence/tokenizer/interning/track hierarchy 异常。若这些数据与丢失 buffer 同源,仍需降级。
下一次配置:减少 ftrace 事件,ftrace buffer 调到 32768KB,drain_period_ms 调到 100ms。

这样写的好处是,读者知道哪些结论能信,哪些结论只是提示方向。性能分析最怕把“没有看到”写成“没有发生”。

总结

stats 是给后续每个结论标证据等级。Trace 有缺口时,继续写重结论只会放大误判;报告要写清哪些还能用、哪些只能参考、下一次怎么补采。

每个关键结论都应附上 证据来源 + 可信度 + 限制条件。例如:FrameTimeline 显示目标窗口有 3 个 jank frame,目标窗口覆盖完整,可信;Runnable 归因受 ftrace loss 影响,只能作为提示。第 11 篇解决“怎么把判断写成 SQL”。这一篇补上前置条件:SQL 只能度量 Trace 里还存在的证据。第 13 篇讲 App 侧 Track Event 时,还会继续碰到同一个问题:埋点不是越多越好,写得太密也会把证据自己挤掉。

参考文档

  1. Buffers and dataflow
  2. Trace configuration
  3. Trace Processor Stats
  4. Instrumenting the Linux kernel with ftrace

关于我 && 博客

欢迎关注 Android Performance

CATALOG
  1. 1. Trace 有数据,不代表证据完整
  2. 2. 数据从哪里走到 Trace 文件
  3. 3. 用 stats 给 Trace 做体检
  4. 4. ftrace 丢失:内核事件跟不上
  5. 5. producer / TraceWriter packet loss:先定位再归因
  6. 6. central buffer 丢失:Ring 覆盖或 Discard 丢新数据
    1. 6.1. 短 Trace:先解决 buffer 混放
    2. 6.2. 长 Trace:再解决周期写文件
  7. 7. 增量状态丢失:事件还在,名字和上下文缺了
  8. 8. 长 Trace 还要关注 flush
  9. 9. 报告里怎么写可信度
  10. 10. 总结
  11. 11. 参考文档
  12. 12. 关于我 && 博客