一份 Perfetto Trace 里混合了多种数据。内核产生 sched_switch、sched_wakeup、CPU 频率等事件;Android Framework 和 App 通过 Trace API 记录代码区间;SurfaceFlinger 提供 FrameTimeline;process_stats、sys_stats 等数据源还会采集进程、CPU 和内存统计。这些数据来自不同的 Producer,采集前先由 TraceConfig 决定启用哪些 Data Source。
Data Source 启动后,通过 TraceWriter 把记录编码成 protobuf 格式的 TracePacket,再写入 Producer 和 Tracing Service 共享的内存。Tracing Service 收取已提交的数据,复制到 central buffer,最后输出 .pftrace 文件。因此,Trace 文件的原始结构是一系列 TracePacket;packet 中常见时间戳、sequence ID 和具体 payload,文件内部没有 Perfetto UI 中的轨道,也没有 slice 这样的 SQL 表。
打开 Trace 时,Trace Processor 才开始解析 packet,对齐不同时钟,建立进程、线程和轨道的身份关系,再将数据整理为 process、thread、slice、sched、counter 等可查询对象。Perfetto UI 和 PerfettoSQL 使用的都是这层解析结果。
Trace Processor 是 Perfetto 的 C++ 解析和分析引擎,不是 Android 设备上负责采集的后台服务。在 ui.perfetto.dev 中打开 Trace 时,它通常以 WebAssembly 形式运行在本地浏览器中;使用 trace_processor 命令行或 Python API 时,则由电脑上的原生进程完成解析。slice 等表存在于当前 Trace Processor 实例的内存中,不会回写到 .pftrace 文件。

上图已经是 Trace Processor 整理后的界面。CPU Scheduling、Expected Timeline、Actual Timeline 和主线程 Slice 共用一条时间轴;采集时,它们分别来自不同的 Data Source,使用不同的 payload,加载后也会进入不同的表。
这一篇只处理数据本身:数据由谁产生,以什么形式写入,怎样经过 Buffer 形成 Trace 文件,文件内部的 packet 是什么结构,加载后又如何变成轨道和表。
TraceConfig 决定采什么
一次采集从 TraceConfig 开始。它负责设置采集时长、central buffer,以及需要启用的数据源。data_sources.config.name 选择数据源,target_buffer 把该数据源的输出写入指定 central buffer,数据源自己的配置则放在对应的私有字段中。

示意图:TraceConfig 选择 Data Source,并将输出映射到目标 Central Buffer。
下面是一份面向 Android 12 及以上设备的 10 秒短时采集示例,不是对所有设备的兼容性保证。实际可用的数据源、ftrace event、ATrace category 和 FrameTimeline 取决于设备、内核、系统构建配置及 tracing 权限。示例把高频的 ftrace、ATrace 和 FrameTimeline 写入 32 MB buffer;进程与系统统计写入另一个 8 MB buffer。两个 buffer 都采用 RING_BUFFER。
textproto
buffers {
size_kb: 32768
fill_policy: RING_BUFFER
}
buffers {
size_kb: 8192
fill_policy: RING_BUFFER
}
duration_ms: 10000
data_sources {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
ftrace_events: "binder/binder_transaction"
ftrace_events: "binder/binder_transaction_received"
atrace_categories: "gfx"
atrace_categories: "view"
atrace_categories: "input"
atrace_categories: "wm"
atrace_categories: "am"
atrace_categories: "aidl"
atrace_apps: "com.example.wechatfriendforperformance"
compact_sched {
enabled: true
}
}
}
}
data_sources {
config {
name: "android.surfaceflinger.frametimeline"
target_buffer: 0
}
}
data_sources {
config {
name: "linux.process_stats"
target_buffer: 1
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 1000
}
}
}
data_sources {
config {
name: "linux.sys_stats"
target_buffer: 1
sys_stats_config {
stat_period_ms: 1000
stat_counters: STAT_CPU_TIMES
meminfo_period_ms: 1000
meminfo_counters: MEMINFO_MEM_TOTAL
meminfo_counters: MEMINFO_MEM_FREE
meminfo_counters: MEMINFO_MEM_AVAILABLE
cpufreq_period_ms: 500
}
}
}
这几个配置项各管一件事:
buffers定义 Tracing Service 持有的 central buffer。size_kb是容量,fill_policy是写满后的处理方式。duration_ms控制采集时长,这里是10000 ms。data_sources.config.name决定由哪个 Producer 响应。例如linux.ftrace采集内核 ftrace 与 ATrace,android.surfaceflinger.frametimeline记录帧时间线。target_buffer是buffers的零基索引。值0指向 32 MB buffer,值1指向 8 MB buffer。ftrace_config、process_stats_config、sys_stats_config是数据源私有配置。它们决定具体事件、类别和轮询周期。
配置没有启用的数据不会写入 Trace。采集结束后再打开更多 UI 轨道或加载 SQL 模块,也无法补回当时没有记录的 sched_switch、CPU 频率或 FrameTimeline。
ATrace 和 TrackEvent 是两条写入路径
ATrace 和 TrackEvent 都能记录 Slice 和 Counter,它们进入 Trace 的方式不同。平时在 Android App 中调用的 android.os.Trace.beginSection() 走 ATrace 路径;使用 Perfetto SDK 的 TRACE_EVENT() 则属于 TrackEvent 路径。
| 常见调用 | 归属 | 进入 Trace 的路径 | 常见解析结果 |
|---|---|---|---|
android.os.Trace.beginSection()/endSection() |
ATrace | trace_marker -> ftrace ring buffer -> linux.ftrace -> ftrace_events |
slice |
androidx.tracing.trace("name") {} |
ATrace | Jetpack 封装最终调用 Trace.beginSection()/endSection() |
slice |
NDK ATrace_beginSection()/ATrace_endSection() |
ATrace | Native ATrace 事件进入 ftrace ring buffer | slice |
Perfetto SDK TRACE_EVENT() |
TrackEvent | TraceWriter -> shared memory -> track_event |
slice、counter、flow 等 |
ATrace 是 Android 4.3 开始提供的系统 Trace 插桩机制,时间早于 Perfetto。Java/Kotlin 使用 android.os.Trace,NDK 使用 ATrace_*,Android 平台内部则有 ATRACE_BEGIN() 等宏。这些入口最终把事件送入内核 ftrace ring buffer,Perfetto 通过 linux.ftrace Data Source 采集。要记录某个 App 的 ATrace,ftrace_config 中还要配置对应包名:
textproto
ftrace_config {
atrace_apps: "com.example.wechatfriendforperformance"
}
TrackEvent 属于 Perfetto Tracing SDK。SDK 中的 TRACE_EVENT()、TRACE_EVENT_BEGIN()、TRACE_COUNTER() 可以定义 Category、Track、参数和 Flow。它们不经过 ftrace trace_marker,由 Perfetto SDK 通过 TraceWriter 写入 track_event payload;采集配置需要启用 track_event Data Source。
AndroidX Tracing 2.0 又提供了 Tracer.global、TraceEvent 和 tracing-wire 等新 API。这套 API 使用 Perfetto packet 格式和可替换的 driver/sink,不再是传统 androidx.tracing.trace("name") {} 的 ATrace 封装。因此,判断一段 Trace API 属于哪条路径时,需要看具体调用的类和方法,不能只看依赖名是否叫 androidx.tracing。
八种常见写入形态
"Data Source""数据形态""TracePacket payload""SQL 对象"是四个不同维度。一个 Data Source 可以产生多种 payload;一种 payload 可以贡献给多个表;一个逻辑事件也可能由多个 packet 共同表达。下表只给出常见主路径,不表示严格的一对一映射。
| 写入形态 | 常见 Producer | 表达的内容 | 常见 TracePacket payload | 主要解析表族 |
|---|---|---|---|---|
| kernel event | Linux ftrace | sched_switch、唤醒、CPU 频率等内核事件 |
ftrace_events |
sched、thread_state、CPU/counter 相关对象 |
| interval / Slice | Android framework/应用 ATrace;Perfetto SDK TrackEvent instrumentation | 一段有开始和结束时间的工作 | ATrace 经 linux.ftrace 写入 ftrace_events;SDK TrackEvent instrumentation 写入 track_event |
slice 与各类 track |
| instant event | TrackEvent instrumentation | 某一时刻发生的点事件 | track_event |
常见归入 slice,以零时长事件呈现 |
| Counter | ftrace、sys stats、TrackEvent | 某个时间点上的数值 | ftrace_events、track_event 或数据源专用 payload |
counter 与 counter track |
| process/thread metadata | linux.process_stats、Perfetto SDK instrumentation |
进程、线程及其身份信息 | process_tree 或 track_descriptor |
process、thread、process_track、thread_track |
| FrameTimeline | Android FrameTimeline Producer | Expected/Actual frame 时间线及帧属性 | 本文不对具体 payload 与字段作逐项映射 | expected_frame_timeline_slice、actual_frame_timeline_slice |
| Profiling | heapprofd、perf sampling 等 Producer | 栈采样、堆分析及调用关系 | profile_packet 等 profiling payload |
stack_profile_callsite、stack_profile_frame 等 |
| metadata | Producer、Tracing Service | 时钟、trace 状态及其他描述信息 | clock_snapshot、trace_stats 等 metadata payload |
时钟、统计与参数相关对象,取决于 payload |
以同步 Slice 为例,开始与结束可能分开写入,Trace Processor 解析后才形成带 ts 和 dur 的区间。Counter 没有区间语义,它记录某个时间点上的值。调度数据虽然也能形成持续区间,但 sched 和 thread_state 属于专用调度对象,不经普通 track event 的轨道访问。
Perfetto UI 中的 Choreographer#doFrame 会显示为主线程上的长条。只看这条 UI Slice 或最终的 slice 记录,无法反推出唯一的 packet 类型:它可能来自经 linux.ftrace 写入 ftrace_events 的 ATrace,也可能来自写入 track_event 的 SDK instrumentation。Trace Processor 会把两条路径规范化为 slice,再根据 descriptor 与身份数据关联线程和进程。
从 Producer 到文件
Data Source 不直接写最终文件。Producer 侧的 Data Source 获得 TraceWriter,先把 packet 写入 Producer 与 Tracing Service 之间的 shared memory。Shared memory 按 page 管理,TraceWriter 在 page 内申请 chunk 并顺序写入 packet;提交协议用 chunk 标识已完成的写入范围。正常写入时,已填满或完成写入的 chunk 会被提交给 Tracing Service;执行 flush 或结束采集时,仍未填满但已经写入数据的 chunk 也会提交。Tracing Service 随后把已提交的数据复制到 central buffer,采集停止或执行流式输出时再进入 trace 文件。

示意图:Producer Shared Memory 是暂存区,Central Buffer 才是输出文件前的主要保存位置。
完整路径可以写成:
Data Source -> TraceWriter -> producer shared memory -> chunk commit -> Tracing Service -> central buffer -> trace file
Linux ftrace 还多一层内核缓冲。内核事件先进入各 CPU 的 per-CPU ring buffer,再由 ftrace Data Source 读取并写入 Perfetto 的 Producer shared memory。内核 ring buffer、Producer shared memory 和 central buffer 处于不同阶段,容量与丢失原因也不同。
Central Buffer 写满后,两种策略留下的证据不同:
RING_BUFFER覆盖最旧数据,保留采集结束前的最近时间窗口。长时间采集若容量不足,起始阶段可能已经消失。DISCARD保留已有数据并丢弃新数据。它能保住采集开头,buffer 满后的事件不会进入文件。
因此,"Trace 文件成功生成"不等于所需时间段完整存在。采集前要按数据速率和目标时间窗口分配 buffer;采集后还要检查丢包、覆盖与 trace stats。
文件里是 TracePacket
.pftrace 或 .perfetto-trace 文件使用 protobuf 格式。根消息 Trace 包含重复的 TracePacket,文件可以理解为一组线性排列的 packet,不是 SQLite 数据库。

示意图:每个 TracePacket 带公共时序字段,并从 payload 的 oneof 成员中选择一个。
常见公共字段包括:
timestamp:packet 的时间戳。具体时钟域还需结合 trace 中的时钟信息解释。trusted_packet_sequence_id:标识 packet 所属的受信 packet sequence,供解析器维护该 sequence 的增量状态。- payload:
ftrace_events、track_event、process_tree、profile_packet等数据字段。图中用 oneof 关系表示单个 packet 选择一个对应 payload 成员,不表示一个 packet 同时写入所有类型。
顺序保证有明确边界。同一 TraceWriter sequence 的 packet 按写入顺序读取;不同 writer 的 packet 可以在文件中交错。时钟完成同步后,带有可比较 timestamp 的 packet 可以按时间先后比较或排序,但这不会产生唯一的全局顺序,也不能证明因果关系。时间戳相同或缺失时,歧义仍然存在。
这项边界会直接影响分析方法。不能根据两个不同数据源的 packet 在文件中的前后位置判断事件先后,也不能仅凭时间接近就断言一个事件导致了另一个事件。Trace Processor 会统一可比较的时间,因果关系仍需 flow、waker、Binder、父子 Slice 等关系数据支持。
SQL 表从哪里来
SQL 能查询 Trace,原因在 Trace Processor。它读取 protobuf payload,完成时钟归一化、进程与线程身份解析、轨道建立、Slice 配对和参数关联,然后向 PerfettoSQL 暴露 tables/views。UI 也使用这些解析后的对象,因此 UI 和 SQL 是同一分析层的两种入口。
采集和分析分属两条数据链:
- Android 设备上的
traced负责采集会话、central buffer 和文件输出,数据链是Producer -> traced -> .pftrace。 - Trace Processor 在文件加载后运行,数据链是
.pftrace -> Trace Processor -> tables/views -> UI/SQL。
使用 Perfetto 网页时,浏览器内置的 Trace Processor WebAssembly 会在本地读取文件。如果 Trace 过大,也可以在电脑上启动原生 trace_processor server,让 UI 连接这个本地进程。两种方式使用的解析和 SQL 能力相同,差别主要在运行位置、内存上限和执行性能。
描述这些对象时,需要保留两条分类轴。
对象层与查询可用性
| 层次 | 含义 | 例子 |
|---|---|---|
| intrinsic storage | Trace Processor 的内部存储层;本文不讨论其实现细节 | 不把内部存储名当成公共查询接口 |
| Prelude public tables/views | 自动加载,可直接查询的基础公开对象 | slice、sched、thread_state、process、thread、track、args、flow |
| ordinary SQL views | SQL 引擎登记的逻辑 view | 当前 trace 与 shell 中的 slice、sched、counter 等 |
| explicitly included stdlib modules | 使用 INCLUDE PERFETTO MODULE 后出现的高层对象和函数 |
slices.with_context、sched.with_context 等模块 |
对象类型描述查询实现,不描述数据语义。本文使用的 trace 与 Trace Processor shell 中,actual_frame_timeline_slice、expected_frame_timeline_slice、process_track、thread_track 登记为 table;args、counter、flow、process、sched、slice、thread、thread_state、track 登记为 view。这只是当前文件与当前 shell 的实测结果,版本和输入变化后可能不同。
数据语义族
| 语义族 | 主要对象 | 回答的问题 |
|---|---|---|
| 身份 | process、thread |
Trace 中有哪些进程和线程 |
| 轨道 | track、thread_track、process_track |
事件属于什么上下文分区 |
| Slice / 调度 | slice、sched、thread_state |
某段时间在做什么、线程何时运行或等待 |
| Counter | counter 与各类 counter track |
数值随时间怎样变化 |
| FrameTimeline | expected_frame_timeline_slice、actual_frame_timeline_slice |
预期帧窗口和实际帧记录 |
| args / flow | args、flow |
事件携带哪些参数,Slice 之间如何连接 |
| stack profile | stack_profile_callsite、stack_profile_frame 等 |
样本对应的调用栈结构 |
同一个对象可以同时出现在两条轴上。slice 在对象层属于自动加载的公开对象,在当前 shell 中登记为 view;在语义层,它始终表达时间区间。actual_frame_timeline_slice 当前登记为 table,在语义层属于 FrameTimeline。不能用 table/view 的字样判断数据来自内核、应用或帧系统。
表之间怎样关联
Trace Processor 会为同一份 Trace 内的实体分配稳定 ID。关联表时优先使用这些 ID,不把操作系统的 pid、tid 当成全程唯一身份,因为系统 ID 可能复用。

示意图:箭头从引用字段指向目标键;args.arg_set_id 是关联和分组键,不标作主键。
常用连接字段有六组:
upid:Trace 内的进程 ID。thread.upid可关联process.upid。utid:Trace 内的线程 ID。thread_track.utid、sched.utid、thread_state.utid都可回到thread.utid。track_id:事件所属轨道。slice.track_id关联某类 track 的id,counter.track_id关联 counter track。arg_set_id:事件与参数集合之间的关联键。多个参数行可以属于同一个参数集合,因此args.arg_set_id不应称为主键。id/parent_id:slice.id标识 Slice,slice.parent_id指向父 Slice,用于表达同轨道上的嵌套结构。flow:通过输入、输出 Slice ID 连接跨 Slice 的异步关系。它表达事件之间的边,不是单独占据时间轴的区间。
slice 的核心字段子集如下。当前实测 schema 还有其他列,因此这不是完整字段表。
| 字段 | 含义 |
|---|---|
id |
Trace 内的 Slice ID |
ts |
开始时间,单位为纳秒 |
dur |
持续时间,单位为纳秒 |
track_id |
所属轨道 ID |
name |
Slice 名称 |
depth |
同一轨道上的嵌套深度 |
parent_id |
父 Slice ID |
arg_set_id |
参数集合关联键 |
thread_ts |
线程时间戳;仅在采集到线程时间信息时有值 |
thread_dur |
线程时间持续量;仅在采集到线程时间信息时有值 |
ts 和 dur 是墙钟时间轴上的字段,thread_ts 和 thread_dur 依赖 TrackEvent 的 thread timestamp collection,不能假定每条 Slice 都有值。
把 UI 轨道映射回数据
UI 中的"轨道"是视觉组织方式,不与 Trace Processor 的一行 track 严格一一对应。一个 UI 轨道可能组合多个查询对象,也可能按进程、线程、CPU 或插件逻辑再次分组。读取截图时,应先辨认事件语义,再查对应表族。
进程、线程与 Slice

进程分组可回到 process,主线程身份来自 thread,承载用户态区间的线程轨道通常关联 thread_track,轨道里的彩色区间来自 slice。选中 Slice 后出现的名称、开始时间、持续时间、线程和进程,分别来自区间字段与身份关系。
CPU Scheduling 与线程状态

顶部 CPU 0 Scheduling 到 CPU 7 Scheduling 对应 sched 的调度区间;选中主线程 Slice 后,详情中的 Running、Runnable 等状态来自同一时间范围内的 thread_state。sched 与 thread_state 是专用调度对象,不能按普通 track_id 访问,需要经 utid、ucpu 等字段关联线程和 CPU。
Counter 曲线

CPU Frequency 轨道画的是数值曲线。截图中选中的样本显示 787,200 kHz。采样值进入 counter,再由 track_id 关联对应的 counter track。Counter 的 ts 表示采样时间,value 表示该时刻的值;相邻点之间画成阶梯或折线属于 UI 呈现,不会把它变成带 dur 的 Slice。
FrameTimeline

Expected Timeline 和 Actual Timeline 分别映射到 expected_frame_timeline_slice 与 actual_frame_timeline_slice。选中帧后可以看到 surface frame token、display frame token、进程、持续时间和 layer name。本文只确认这些专用表在当前 shell 中可查询,不把 UI 字段逐项绑定到某个 TracePacket payload。
第一次查询只看三列
完成上述数据链后,SQL 的来源已经明确:查询面对的是 Trace Processor 暴露的 slice,不是直接扫描 protobuf 文件。先取十行,只观察开始时间、持续时间和名称。

截图中的 ts 和 dur 单位都是纳秒。第一行 dur = 141094,换算后约为 0.141094 ms。name 保存 Slice 名称。返回十行只说明查询命中了十条区间记录,不提供线程归属、父子结构或性能结论。
sql
SELECT ts, dur, name FROM slice LIMIT 10;
打开一份 Trace 后检查什么
- 回看
TraceConfig,确认目标 Data Source、事件类别和应用包名确实启用。 - 核对 buffer 容量与策略,判断目标时间段是否可能被
RING_BUFFER覆盖,或因DISCARD丢失后续数据。 - 识别 UI 元素的数据语义,区分 Slice、调度区间、线程状态、Counter 和 FrameTimeline。
- 找到相关表之间的
upid、utid、track_id、arg_set_id、parent_id或 flow 关系,再开始跨对象分析。