Perfetto追踪
Perfetto 分析最容易被误解成“打开 UI 看时间线”。真正决定一条事件能否出现在时间线上的,是 事件产生端、启用状态、缓冲区策略、trace session 生命周期和最终消费方式。Android 17 源码中, 这条链至少有两种入口:旧的 ATrace_*/atrace_* 标记,以及 Java android.tracing.perfetto 自定义 DataSource。它们都能被同一个系统 tracing 结果消费,但 owner、丢弃策略和状态对象不同。
本文面向已经读过 lunch目标与编译变体、 ADB安装与权限管理 的读者。需要理解进程、线程、 ftrace/atrace 基本概念和 protobuf;不展开未随本 checkout 提供的 external/perfetto 服务端、 浏览器 UI 或 Trace Processor 内部实现。读完后,应能从一处 ATrace_beginSection 或 DataSource.trace 追到数据如何产生、何时生效、何时丢弃,以及如何用固定测试验证结果。
1. 问题边界
本篇把“Perfetto”拆成三个可验证对象:
| 对象 | Android 17 源码入口 | owner | 本文能证明什么 |
|---|---|---|---|
| ATrace 标记 | system/core/libcutils/trace-container.cpp | 进程内 libcutils | 标记格式、启用状态和写入目标 |
| Java DataSource | frameworks/base/core/java/android/tracing/perfetto | producer 与 DataSource 实例 | 注册、start/flush/stop、状态和 packet 产生 |
| Metric spec | packages/SystemUI/metrics/perfetto/metrics_specs | metric 测试与 Trace Processor | SQL/spec 输入、聚合和断言输出 |
没有 checkout 的 external/perfetto 源码不能被当作本文证据。因此“traced 如何实现共享内存”、 “UI 如何绘制轨道”等问题只作为外部消费边界,不在正文伪造调用链。
2. ATrace 标记
2.1 入口与启用
公开 native 入口 frameworks/base/native/android/trace.cpp 只是把 NDK ATrace_* 函数转发到 cutils/trace.h:
源码文件:frameworks/base/native/android/trace.cpp
bool ATrace_isEnabled() {
return atrace_is_tag_enabled(ATRACE_TAG_APP);
}
void ATrace_beginSection(const char* sectionName) {
atrace_begin(ATRACE_TAG_APP, sectionName);
}
void ATrace_endSection() {
atrace_end(ATRACE_TAG_APP);
}
void ATrace_beginAsyncSection(const char* sectionName, int32_t cookie) {
atrace_async_begin(ATRACE_TAG_APP, sectionName, cookie);
}
void ATrace_endAsyncSection(const char* sectionName, int32_t cookie) {
atrace_async_end(ATRACE_TAG_APP, sectionName, cookie);
}
void ATrace_setCounter(const char* counterName, int64_t counterValue) {
atrace_int64(ATRACE_TAG_APP, counterName, counterValue);
}ATrace_isEnabled 是调用方可见的快速判断;它不是 session 是否已经把该事件写入文件的证明。 启用状态由 libcutils 的全局原子值和 tag 属性共同影响。进程尚未完成 atrace_setup 或 marker fd 打开失败时,事件可能被静默丢弃。
2.2 写入路径
Android 17 的 trace-container.cpp 支持两种写入目标:能访问 tracefs 时写 /sys/kernel/tracing/trace_marker(旧路径也尝试 debugfs);容器环境不能打开时,切换到 container socket。atrace_begin_body 的事件格式由 WRITE_MSG 或 WRITE_MSG_IN_CONTAINER 组装。
源码文件:system/core/libcutils/trace-container.cpp
相关函数/类型:atrace_init_once
源码文件:system/core/libcutils/trace-container.cpp
static void atrace_init_once() {
atrace_marker_fd = open("/sys/kernel/tracing/trace_marker", O_WRONLY | O_CLOEXEC);
if (atrace_marker_fd < 0) {
atrace_marker_fd = open("/sys/kernel/debug/tracing/trace_marker",
O_WRONLY | O_CLOEXEC);
if (atrace_marker_fd < 0) {
atrace_use_container_sock = true;
if (atomic_load_explicit(&atrace_is_enabled, memory_order_acquire)) {
atrace_init_container_sock();
}
}
}
atrace_enabled_tags = atrace_get_property();
atomic_store_explicit(&atrace_is_ready, true, memory_order_release);
}
void atrace_begin_body(const char* name) {
if (CC_LIKELY(atrace_use_container_sock)) {
WRITE_MSG_IN_CONTAINER("B", "|", "%s", "", name, "");
return;
}
if (atrace_marker_fd < 0) return;
WRITE_MSG("B|%d|", "%s", "", name, "");
}
void atrace_end_body() {
if (CC_LIKELY(atrace_use_container_sock)) {
WRITE_MSG_IN_CONTAINER("E", "", "%s", "", "", "");
return;
}
if (atrace_marker_fd < 0) return;
WRITE_MSG("E|%d", "%s", "", "", "");
}B/E 是同一线程上的嵌套区间;异步区间使用 S/F 与 cookie;counter 使用 C。这不是 Perfetto UI 的显示协议,而是 ATrace 写入 trace marker 的输入格式,后续解析器才会把它解释成 slice、async track 或 counter。
3. 采集配置
采集配置决定“哪些 producer 有机会写入哪个 buffer”。Android 17 的多用户性能测试配置 frameworks/base/apct-tests/perftests/multiuser/trace_configs/trace_config_multi_user.textproto 展示了真实的 file flush、userspace buffer、ftrace buffer 和 data source 关系:
源码文件:frameworks/base/apct-tests/perftests/multiuser/trace_configs/trace_config_multi_user.textproto
write_into_file: true
file_write_period_ms: 1000
flush_period_ms: 10000
buffers {
size_kb: 32768
fill_policy: RING_BUFFER
}
buffers {
size_kb: 8192
fill_policy: RING_BUFFER
}
data_sources {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
buffer_size_kb: 16384
drain_period_ms: 250
compact_sched { enabled: true }
ftrace_events: "task/task_newtask"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "rss_stat"
atrace_apps: "*"
atrace_categories: "am"
atrace_categories: "binder_driver"
atrace_categories: "view"
atrace_categories: "wm"
}
}
}
data_sources {
config {
name: "linux.process_stats"
target_buffer: 1
process_stats_config { proc_stats_poll_ms: 10000 }
}
}这里存在三层 buffer,不能混为一谈:ftrace 自己的 kernel buffer 由 buffer_size_kb 管理; producer drain 后进入 target_buffer 指向的 userspace buffer;write_into_file 再按周期把 userspace 数据写入文件。RING_BUFFER 意味着空间不足时覆盖旧数据,因此“查询结果为零”既可能 是没有事件,也可能是目标时间段已经被覆盖。
atrace_categories 决定系统 category,atrace_apps 决定应用标记范围;只调用 ATrace_beginSection 而配置没有选择相应 app/tag,marker 不会自然成为最终 trace 证据。
4. Java Producer
4.1 初始化与注册
frameworks/base/core/java/android/tracing/perfetto/Producer.java 只负责把后端位掩码和共享内存 提示值传给 native:
源码文件:frameworks/base/core/java/android/tracing/perfetto/Producer.java
public static void init(InitArguments args) {
nativePerfettoProducerInit(args.backends, args.shmemSizeHintKb);
}InitArguments.DEFAULTS 选择 system backend,TESTING 选择 in-process backend。这个差异影响 连接哪个 producer backend,不能被写成“测试和生产只是文件输出不同”。共享内存提示值只是 hint: 源码明确允许 backend 忽略过大或非 4KB 对齐的值。
自定义 DataSource 构造时调用 nativeCreate(this, name),但只有 register(DataSourceParams) 之后,后端才会收到 setup/start/stop 通知。注册后不能注销,这是 DataSource 生命周期的硬边界。
4.2 trace 回调
DataSource.trace 的 owner 是 DataSource 对象;一次调用可能遍历多个并发 tracing instance。 它先用 native iterator 判断是否有活跃实例,然后为每个实例创建 TracingContext,执行调用方 lambda,最后把 pending packet 转成 byte 数组交给 native。
源码文件:frameworks/base/core/java/android/tracing/perfetto/DataSource.java
public final void trace(
TraceFunction<DataSourceInstanceType, TlsStateType, IncrementalStateType> fun) {
boolean startedIterator = nativePerfettoDsTraceIterateBegin(mNativeObj);
if (!startedIterator) {
return;
}
try {
do {
int instanceIndex = nativeGetPerfettoDsInstanceIndex(mNativeObj);
TracingContext<DataSourceInstanceType, TlsStateType, IncrementalStateType> ctx =
new TracingContext<>(this, instanceIndex);
fun.trace(ctx);
nativeWritePackets(mNativeObj, ctx.getAndClearAllPendingTracePackets());
} while (nativePerfettoDsTraceIterateNext(mNativeObj));
} finally {
nativePerfettoDsTraceIterateBreak(mNativeObj);
}
}因此 lambda 的同步语义是条件性的:没有活跃 session 时,它不会执行;有两个 session 时,同一 次 trace 可能执行两次。finally 中的 break 是取消、异常和正常完成都必须经过的清理点, 否则 native iterator 状态无法结束。
4.3 Packet
TracingContext.newTracePacket 创建 ProtoOutputStream 并把它放入当前 context 的列表。lambda 返回后,getAndClearAllPendingTracePackets 读取每个 stream 的 bytes 并清空列表;这意味着 packet 对象不能跨 trace 调用继续复用。
源码文件:frameworks/base/core/java/android/tracing/perfetto/TracingContext.java
public ProtoOutputStream newTracePacket(int estimatedPacketSizeInBytes) {
final ProtoOutputStream os = new ProtoOutputStream(estimatedPacketSizeInBytes);
mTracePackets.add(os);
return os;
}
protected byte[][] getAndClearAllPendingTracePackets() {
byte[][] res = new byte[mTracePackets.size()][];
for (int i = 0; i < mTracePackets.size(); i++) {
res[i] = mTracePackets.get(i).getBytes();
}
mTracePackets.clear();
return res;
}5. Instance 状态
5.1 配置与 start
后端为每个配置实例调用 DataSource.createInstance(ProtoInputStream, instanceIndex)。实例可以 读取自定义 config;DataSourceInstance.onStart 在 Perfetto internal thread 执行并阻塞该线程, 源码因此要求回调快速完成。
源码文件:frameworks/base/core/java/android/tracing/perfetto/DataSourceInstance.java
protected void onStart(StartCallbackArguments args) {
synchronized (getDataSource().mRunningInstances) {
getDataSource().mRunningInstances.add(getInstanceIndex());
for (DataSource.TracingInstanceStartCallback callback
: getDataSource().mOnStartCallbacks) {
callback.onTracingInstanceStart(getInstanceIndex());
}
}
}
protected void onStop(StopCallbackArguments args) {
synchronized (getDataSource().mRunningInstances) {
getDataSource().mRunningInstances.remove(getInstanceIndex());
for (DataSource.TracingInstanceStopCallback callback
: getDataSource().mOnStopCallbacks) {
callback.onTracingInstanceStop(getInstanceIndex());
}
}
}mRunningInstances 是 DataSource 的共享状态,实例 start/stop 通过同一把锁更新。stop 回调完成 不等于 native stop 已完成:如果注册参数设置 postponeStop,调用方必须显式调用 DataSourceInstance.stopDone()。
5.2 状态对象
TracingContext 提供两种状态:TLS 是每个 tracing thread 和 instance 的状态;incremental state 用于跨 packet 的增量编码,但在 incremental state 被清除时重新创建。两者都通过 native 指针关联 Java 对象。
源码文件:frameworks/base/core/java/android/tracing/perfetto/TracingContext.java
public TlsStateType getCustomTlsState() {
TlsStateType state = (TlsStateType) nativeGetCustomTls(mDataSource.mNativeObj);
if (state == null) {
state = mDataSource.createTlsState(
new CreateTlsStateArgs<>(mDataSource, mInstanceIndex));
nativeSetCustomTls(mDataSource.mNativeObj, state);
}
return state;
}
public IncrementalStateType getIncrementalState() {
IncrementalStateType state =
(IncrementalStateType) nativeGetIncrementalState(mDataSource.mNativeObj);
if (state == null) {
state = mDataSource.createIncrementalState(
new CreateIncrementalStateArgs<>(mDataSource, mInstanceIndex));
nativeSetIncrementalState(mDataSource.mNativeObj, state);
}
return state;
}TLS 不应被理解成“整个进程一个全局对象”。Android 17 测试分别验证了每个 instance 有独立 TLS、 每个 thread 有独立 TLS;incremental state 则验证了超时清除后不会保留旧值。
5.3 Buffer/stop
DataSourceParams 把 buffer exhausted、stop notification、flush 和 postponed stop 分开:
| 参数 | Android 17 语义 | 风险边界 |
|---|---|---|
DROP | 空间不足时丢弃数据 | trace 不完整但 producer 不阻塞 |
STALL_AND_ABORT | 等待后中止写入 | 可能影响 producer 进度 |
STALL_AND_DROP | 等待后丢弃 | 仍可能延迟 producer |
willNotifyOnStop | producer 是否确认 stop | 冻结进程不能选择需要快速 ack 的路径 |
noFlush | 不发送 flush 请求 | 适合不能及时响应的 producer |
postponeStop | 由实例稍后 stopDone | 忘记调用会让 session 停止不完整 |
6. 两类 producer
两者不能简单互换。ATrace 是轻量标记接口,事件格式固定、状态主要在 libcutils;DataSource 是 可配置 protobuf producer,调用方可以写自定义 packet、配置和状态。ATrace 测试能证明字符串 写入格式,不能证明自定义 DataSource packet;DataSource 测试能证明 packet 和 state 生命周期, 不能证明所有 atrace category 都被 session 选中。
| 维度 | ATrace | Java DataSource |
|---|---|---|
| 输入 | name、cookie、counter | ProtoOutputStream packet |
| 状态 | enabled tags、marker fd | instance、TLS、incremental state |
| 开始时机 | API 调用时尝试写入 | 仅 active tracing instance 执行 lambda |
| 失败/丢弃 | fd 不可用或 tag 未启用 | buffer policy、instance 创建失败、stop ack |
| 清理 | fd/socket 生命周期 | iterator break、packet clear、instance release |
7. Metric 消费
SystemUI 的 metric spec 展示了另一种可复现证据:不是截一张 UI 图片,而是把 trace 中的 slice 通过 SQL/模板转换成可断言的结果。android_sf_critical_work_main_thread.textproto 先定义 CUJ 与 SurfaceFlinger CriticalWorkload slice,再用 interval_intersect 限定区间, 最后按 slice_name、cuj_name 聚合 max/mean/count。
源码文件:frameworks/base/packages/SystemUI/metrics/perfetto/metrics_specs/android_sf_critical_work_main_thread.textproto
query: {
id: "sf_critical_work_slices"
simple_slices: {
track_name_glob: "CriticalWorkload"
process_name_glob: "/system/bin/surfaceflinger"
}
filters: {
column_name: "slice_name"
op: EQUAL
string_rhs: "Commit"
string_rhs: "Composition"
string_rhs: "Post Composition"
string_rhs: "Transaction Handling"
string_rhs: "Refresh Rate Selection"
}
}
metric_template_spec: {
id_prefix: "android_sf_critical_work_main_thread_cuj"
dimensions: "cuj_name"
dimensions: "slice_name"
value_columns: "max_dur"
value_columns: "avg_dur"
value_columns: "count"
}这段 spec 的 owner 是 metric 定义,不是 UI;消费者是测试中的 Trace Processor。它证明的是 “给定 trace proto,查询和聚合得到预期摘要”,不证明采集时一定产生了目标 slice。
另一个 metric 使用 SQL window function 计算 CUJ 内连续丢帧段:先 LAG 找状态变化,再用窗口 SUM 生成 streak ID,最后按 CUJ 分组取最大连续长度。这个结构比“看到 jank 就计数”更严格, 也说明分析脚本本身需要逐句核对。
-- packages/SystemUI/metrics/perfetto/metrics_specs/
-- android_consecutive_missed_frames_per_cuj_metric.textproto 中的 SQL
WITH frames_during_cuj AS (
SELECT fts.ts, fts.jank_type, cuj.cuj_name,
android_is_missed_frame_type(fts.jank_type) AS is_jank
FROM actual_frame_timeline_slice AS fts
JOIN android_sysui_jank_cujs AS cuj
ON fts.upid = cuj.upid
AND fts.ts >= cuj.ts
AND fts.ts < cuj.ts + cuj.dur
), state_changes AS (
SELECT *, CASE WHEN is_jank != LAG(is_jank)
OVER (PARTITION BY cuj_name ORDER BY ts)
THEN 1 ELSE 0 END AS jank_state_changed
FROM frames_during_cuj
), streak_identification AS (
SELECT *, SUM(jank_state_changed)
OVER (PARTITION BY cuj_name ORDER BY ts) AS streak_id
FROM state_changes
), all_streak_lengths AS (
SELECT *, COUNT(*)
OVER (PARTITION BY cuj_name, streak_id) AS streak_length
FROM streak_identification
)
SELECT cuj_name,
MAX(CASE WHEN is_jank = 1 THEN streak_length ELSE 0 END)
FROM all_streak_lengths
GROUP BY cuj_name;8. 反向验证
8.1 DataSource
frameworks/base/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java 的 canTraceData arrange 阶段初始化 producer、注册自定义 DataSource 并启动 PerfettoTraceMonitor; action 阶段在 trace lambda 中写入 FOR_TESTING.PAYLOAD.SINGLE_INT = 10;assert 阶段解析结果 proto,过滤 hasForTesting 的 packet,并断言匹配值只有一个。这证明 packet 从 Java context 进入 trace 文件,不证明生产系统的所有 backend 都同样可用。
同一测试还覆盖:
eachInstanceHasOwnTlsState:两个 session 的 TLS 值互不污染;eachThreadHasOwnTlsState:两个线程的 TLS 值分别保留;incrementalStateIsReset:等待清除后,incremental state 不再是旧值;getInstanceConfigOnCreateInstance:配置 proto 到达createInstance;multipleTraceInstances:一次 trace 可以遍历多个 instance。
8.2 ATrace验证
system/core/libcutils/trace-dev_test.cpp 使用临时 fd 注入 atrace_marker_fd,调用 atrace_begin_body、async、instant 和 counter 函数,再从 fd 读取字符串断言格式与长度。 *_truncated 测试证明名称过长时会截断或丢弃,不能把 trace marker 当作无限长度日志。
8.3 Metric验证
packages/SystemUI/metrics/perfetto/metrics_specs/tests/metrics_v2_test.py 用 TraceProtoBuilder 构造最小 trace proto,再通过 TraceProcessor.trace_summary 执行 spec, 最后与 tests/data/*_output.txt 做完整字符串断言。输入、SQL、metric id 和期望文本四者共同 构成证据;仅打开 UI 观察颜色不能替代这个断言。
9. 失败与恢复
| 阶段 | 失败 | 结果 | 恢复 |
|---|---|---|---|
| ATrace 初始化 | tracefs 和 container socket 都不可用 | enabled tags 清零或写入直接返回 | 修复 tracing backend/权限后重新启用 |
| ATrace 写入 | 名称超长 | 截断或丢弃消息 | 缩短 marker 名称并重新采集 |
| DataSource 注册 | instance 创建异常/返回 null | onInstanceCreateFailed 路径 | 修复 config 或 createInstance,不重复注册旧对象 |
| DataSource trace | 无 active instance | lambda 不执行 | 先启动匹配 datasource 的 session |
| buffer | 空间耗尽 | 按 policy drop/stall/abort | 调整 buffer 或降低事件量,不能把缺包当真实零值 |
| stop | postponeStop 后未 stopDone | tracing instance 停止未完成 | 在清理路径调用 stopDone |
| metric | trace 缺少目标表/字段 | SQL/summary 失败或空结果 | 先验证 data source 和 trace 内容,再修 spec |
取消采集和异常退出时,DataSource 的 trace 仍通过 finally 结束 native iterator;packet 列表 在发送后清空。停止流程若启用了 postponeStop,则必须由业务代码完成最终 ack。恢复时不能把 上一份 trace 的 packet 或 state 当作当前 session 的证据。
10. 可执行验证
10.1 源码定位
git -C system/core show android-17.0.0_r1:libcutils/trace-container.cpp \
| rg -n -C 4 'atrace_init_once|atrace_begin_body|atrace_end_body|trace_marker'
git -C frameworks/base show android-17.0.0_r1:core/java/android/tracing/perfetto/DataSource.java \
| rg -n -C 5 'register\(|trace\(|nativeWritePackets|nativePerfettoDsTraceIterate'这些命令验证的是固定 tag 的入口、状态和清理分支,不验证当前设备是否开放 tracefs。
10.2 事件闭环
在设备上选择一个短操作,使用已安装的 perfetto/atrace 工具采集后,检查:
- marker 名称是否出现在 trace 中;
- begin/end 是否成对,async cookie 是否一致;
- DataSource session 是否执行 start/stop,是否出现目标 packet;
- metric 查询的表和字段是否真的存在,再比较摘要输出。
一个“UI 上看不到 slice”的结论不能直接归因于 UI:可能是 tag 未启用、没有 active instance、 buffer 丢弃、stop 未完成,或 SQL 的 track/process/filter 条件不匹配。
11. 源码导航
frameworks/base/native/android/trace.cpp:NDK ATrace 入口。system/core/libcutils/trace-container.cpp:marker/socket 写入、启用和截断。frameworks/base/core/java/android/tracing/perfetto/Producer.java:producer 初始化。frameworks/base/core/java/android/tracing/perfetto/DataSource.java:注册、trace iterator 和 packet 发送。frameworks/base/core/java/android/tracing/perfetto/DataSourceInstance.java:start/flush/stop 与实例锁。frameworks/base/core/java/android/tracing/perfetto/TracingContext.java:packet、TLS、incremental state。frameworks/base/core/java/android/tracing/perfetto/DataSourceParams.java:缓冲区和停止策略。frameworks/base/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java:可执行生命周期证据。packages/SystemUI/metrics/perfetto/metrics_specs:spec、SQL、合成 trace 和结果断言。
沿这条顺序阅读,读者会先得到“事件由谁产生、由谁拥有状态、谁消费结果”的主线,再使用 UI 做观察,而不是把截图当成源码证明。
