Skip to content

Perfetto追踪

追踪 ATrace 标记、Java Perfetto DataSource 生命周期、trace packet 输出和 metric SQL 测试,建立从事件产生到结果验证的可执行分析路径。

基于android-17.0.0_r1
AndroidPerfettoTrace性能分析

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 DataSourceframeworks/base/core/java/android/tracing/perfettoproducer 与 DataSource 实例注册、start/flush/stop、状态和 packet 产生
Metric specpackages/SystemUI/metrics/perfetto/metrics_specsmetric 测试与 Trace ProcessorSQL/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

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

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

protobuf
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

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

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

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

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

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
willNotifyOnStopproducer 是否确认 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 选中。

维度ATraceJava DataSource
输入name、cookie、counterProtoOutputStream packet
状态enabled tags、marker fdinstance、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

text
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 就计数”更严格, 也说明分析脚本本身需要逐句核对。

sql
-- 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 创建异常/返回 nullonInstanceCreateFailed 路径修复 config 或 createInstance,不重复注册旧对象
DataSource trace无 active instancelambda 不执行先启动匹配 datasource 的 session
buffer空间耗尽按 policy drop/stall/abort调整 buffer 或降低事件量,不能把缺包当真实零值
stoppostponeStop 后未 stopDonetracing instance 停止未完成在清理路径调用 stopDone
metrictrace 缺少目标表/字段SQL/summary 失败或空结果先验证 data source 和 trace 内容,再修 spec

取消采集和异常退出时,DataSource 的 trace 仍通过 finally 结束 native iterator;packet 列表 在发送后清空。停止流程若启用了 postponeStop,则必须由业务代码完成最终 ack。恢复时不能把 上一份 trace 的 packet 或 state 当作当前 session 的证据。

10. 可执行验证 ​

10.1 源码定位 ​

bash
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 工具采集后,检查:

  1. marker 名称是否出现在 trace 中;
  2. begin/end 是否成对,async cookie 是否一致;
  3. DataSource session 是否执行 start/stop,是否出现目标 packet;
  4. metric 查询的表和字段是否真的存在,再比较摘要输出。

一个“UI 上看不到 slice”的结论不能直接归因于 UI:可能是 tag 未启用、没有 active instance、 buffer 丢弃、stop 未完成,或 SQL 的 track/process/filter 条件不匹配。

11. 源码导航 ​

  1. frameworks/base/native/android/trace.cpp:NDK ATrace 入口。
  2. system/core/libcutils/trace-container.cpp:marker/socket 写入、启用和截断。
  3. frameworks/base/core/java/android/tracing/perfetto/Producer.java:producer 初始化。
  4. frameworks/base/core/java/android/tracing/perfetto/DataSource.java:注册、trace iterator 和 packet 发送。
  5. frameworks/base/core/java/android/tracing/perfetto/DataSourceInstance.java:start/flush/stop 与实例锁。
  6. frameworks/base/core/java/android/tracing/perfetto/TracingContext.java:packet、TLS、incremental state。
  7. frameworks/base/core/java/android/tracing/perfetto/DataSourceParams.java:缓冲区和停止策略。
  8. frameworks/base/tests/Tracing/src/android/tracing/perfetto/DataSourceTest.java:可执行生命周期证据。
  9. packages/SystemUI/metrics/perfetto/metrics_specs:spec、SQL、合成 trace 和结果断言。

沿这条顺序阅读,读者会先得到“事件由谁产生、由谁拥有状态、谁消费结果”的主线,再使用 UI 做观察,而不是把截图当成源码证明。