使用开源perfetto工具实现问题可视化排查
0.前言
最近学到了蛮多新东西,也琢磨着怎么和我的技术栈结合在一起,打通整个技术栈。AI时代到来,对知识体系的要求越来越全面了,不仅要有命令行,还要会可视化。感觉可视化这块的能力是真的薄弱,嵌入式做多了,excel分析,txt分析,以前的这种分析方法效率还是蛮低的。
用新的工具,对以前的分析方法实施重构,同样的问题场景,会变得不一样。
1. 什么是 Perfetto
Perfetto 是 Google 开源的一套系统级追踪(Tracing)工具,最初为 Android 和 ChromeOS 设计,现已广泛用于各类性能分析场景。
核心能力:
- 可视化时间线
:以甘特图形式展示进程、线程、任务的执行时序 - 多轨道并行展示
:同时观察多个核心/线程的运行状态 - 流事件箭头
:可视化任务之间的因果关系和激活链路 - 跨进程/跨芯片分析
:在统一时间轴上关联不同硬件模块的行为 - SQL 查询引擎
:支持用 SQL 对 trace 数据进行精确查询
使用方式:
打开 https://ui.perfetto.dev/,将 JSON 文件拖入即可可视化。无需安装任何软件。
2. Trace Event Format 是什么
Trace Event Format(也称 Chrome Trace Event Format)是 Chrome DevTools(chrome://tracing)定义的一种 JSON 格式标准,用于描述系统中发生的各类事件。
Perfetto 完全兼容此格式。其核心思想是:
用一个 JSON 数组记录所有事件,每个事件是一个带时间戳的对象,通过
ph(phase)字段区分事件类型。
JSON 顶层结构:
{ "displayTimeUnit": "ms", "traceEvents": [ { "ph": "M", ... }, { "ph": "X", ... }, { "ph": "s", ... }, { "ph": "f", ... } ]}
|
|
|
|---|---|
displayTimeUnit |
"ms" 或 "μs",控制 UI 显示单位 |
traceEvents |
|
3. Perfetto JSON 的作用——能做什么
|
|
|
|---|---|
| 任务排布可视化 |
|
| 激活链路追踪 |
|
| 多芯片联合分析 |
|
| 调度优化验证 |
|
| 抢占检测 |
|
| 性能瓶颈定位 |
|
4. Trace Event Format 字段详解
4.1 事件类型总览(ph 字段)
Chrome Trace Event Format 共定义了以下事件类型,Perfetto 对其中大部分提供了支持:
|
|
|
|
|
|---|---|---|---|
"B" |
|
"E" 配对) |
|
"E" |
|
"B" 配对) |
|
"X" |
|
|
|
"i"
"I" |
|
|
|
"C" |
|
|
|
"b" |
|
"e" 配对) |
|
"n" |
|
|
|
"e" |
|
"b" 配对) |
|
"S" |
|
"F" 配对) |
|
"T" |
|
|
|
"F" |
|
"S" 配对) |
|
"s" |
|
|
|
"t" |
|
|
|
"f" |
|
|
|
"P" |
|
|
|
"N" |
|
|
|
"O" |
|
|
|
"D" |
|
|
|
"M" |
|
|
|
"V" |
|
|
|
"v" |
|
|
|
"R" |
|
|
|
"c" |
|
|
|
下面逐一给出每种事件类型的完整 demo JSON 和说明。
4.1.1 Duration Events — "B" (Begin) / "E" (End)
用途:标记一个区间的开始和结束,必须成对出现在同一 pid/tid 上。支持嵌套(内层区间在 Perfetto 中显示为子行)。
适用场景:函数调用跟踪、任务被抢占后分段记录。
非常推荐用。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MyApp"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "MainThread"} }, { "name": "OuterFunction", "cat": "func", "ph": "B", "ts": 1000, "pid": 1, "tid": 1}, { "name": "InnerFunction", "cat": "func", "ph": "B", "ts": 1200, "pid": 1, "tid": 1}, { "name": "InnerFunction", "cat": "func", "ph": "E", "ts": 1800, "pid": 1, "tid": 1}, { "name": "OuterFunction", "cat": "func", "ph": "E", "ts": 2500, "pid": 1, "tid": 1} ]}
字段说明:
|
|
|
|
|---|---|---|
name |
|
|
ts |
|
|
args |
|
|
Perfetto 显示效果:OuterFunction 显示为一条长条,InnerFunction 在其下方缩进显示为子区间。
4.1.2 Complete Event — "X"
用途:用一条事件完整描述一个区间的起止,等价于一对 B/E,是最常用、最简洁的方式。
适用场景:任务调度记录、每个 Task 的执行区间。
非常推荐用
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "Core0"} }, { "name": "Task0", "cat": "task", "ph": "X", "ts": 0, "dur": 1000, "pid": 1, "tid": 1, "args": { "priority": 5, "cpu_usage": "12%"}}, { "name": "Task1", "cat": "task", "ph": "X", "ts": 1100, "dur": 1000, "pid": 1, "tid": 1} ]}
字段说明:
|
|
|
|
|---|---|---|
ts |
|
|
dur |
|
|
tdur |
|
|
4.1.3 Instant Event — "i" / "I"
用途:标记一个无持续时间的瞬时点,如中断触发、标志位变化、特定条件满足。
适用场景:ISR 触发、信号量释放、watchdog 喂狗、错误发生时刻。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "Core0"} }, { "name": "ISR_Triggered", "cat": "interrupt", "ph": "i", "ts": 5000, "pid": 1, "tid": 1, "s": "t", "args": { "irq_number": 42, "source": "Timer3"}}, { "name": "Watchdog_Feed", "cat": "system", "ph": "i", "ts": 8000, "pid": 1, "tid": 1, "s": "p", "args": { "timeout_ms": 100}}, { "name": "SystemBoot", "cat": "lifecycle", "ph": "i", "ts": 0, "pid": 1, "tid": 1, "s": "g"} ]}
s 字段(scope)控制显示范围:
|
|
|
|
|---|---|---|
"g" |
|
|
"p" |
|
|
"t" |
|
|
注意:
"I"(大写)是旧版写法,行为相同,建议统一用"i"(小写)。
4.1.4 Counter Event — "C"
用途:记录数值随时间变化的趋势,Perfetto 会绘制折线图/面积图。
适用场景:CPU 占用率、内存使用、队列深度、任务优先级变化、温度监控。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "name": "CPU_Usage", "ph": "C", "ts": 0, "pid": 1, "args": { "percent": 15} }, { "name": "CPU_Usage", "ph": "C", "ts": 2000, "pid": 1, "args": { "percent": 78} }, { "name": "CPU_Usage", "ph": "C", "ts": 4000, "pid": 1, "args": { "percent": 45} }, { "name": "CPU_Usage", "ph": "C", "ts": 6000, "pid": 1, "args": { "percent": 92} }, { "name": "CPU_Usage", "ph": "C", "ts": 8000, "pid": 1, "args": { "percent": 30} }, { "name": "Queue_Depth", "ph": "C", "ts": 0, "pid": 1, "args": { "rx": 0, "tx": 2} }, { "name": "Queue_Depth", "ph": "C", "ts": 3000, "pid": 1, "args": { "rx": 5, "tx": 1} }, { "name": "Queue_Depth", "ph": "C", "ts": 6000, "pid": 1, "args": { "rx": 2, "tx": 8} } ]}
关键说明:
name
相同的 Counter 事件归为同一条折线 args
中的每个键值对会作为独立的系列绘制(上例 Queue_Depth会画 rx 和 tx 两条线)-
不需要 tid字段(Counter 是进程级的) -
两个采样点之间,值保持为前一个采样点的值(阶梯状)
4.1.5 Async Events — "b" (Begin) / "n" (Instant) / "e" (End)
用途:记录不绑定到特定线程的异步操作,如 DMA 传输、网络请求、定时器回调。异步事件在 Perfetto 中显示为独立轨道,不占用任何线程轨道。
适用场景:DMA 传输、I2C/SPI 总线操作、网络请求/响应。
与 Duration (B/E) 的区别:Duration 事件在线程轨道上显示且支持嵌套;Async 事件显示在进程下方的独立异步轨道上,通过 id 区分不同实例。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "name": "DMA_Transfer", "cat": "dma", "ph": "b", "id": "dma_001", "ts": 1000, "pid": 1, "args": { "channel": 3, "src": "0x20000000", "dst": "0x40001000", "size": 256}}, { "name": "DMA_Transfer", "cat": "dma", "ph": "n", "id": "dma_001", "ts": 1500, "pid": 1, "args": { "event": "half_transfer_complete"}}, { "name": "DMA_Transfer", "cat": "dma", "ph": "e", "id": "dma_001", "ts": 2000, "pid": 1, "args": { "status": "ok", "bytes_transferred": 256}}, { "name": "SPI_Transaction", "cat": "bus", "ph": "b", "id": "spi_042", "ts": 800, "pid": 1}, { "name": "SPI_Transaction", "cat": "bus", "ph": "e", "id": "spi_042", "ts": 1300, "pid": 1} ]}
字段说明:
|
|
|
|---|---|
id |
|
scope |
|
"n"
|
|
4.1.6 Legacy Async Events — "S" (Start) / "T" (Step) / "F" (Finish)
用途:旧版异步事件格式,功能与 b/n/e 类似。仍被 Perfetto 支持,但新代码建议用小写版本。
适用场景:兼容旧版 trace 数据。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "LegacyApp"} }, { "name": "HTTPRequest", "cat": "network", "ph": "S", "id": 200, "ts": 0, "pid": 1, "tid": 1, "args": { "url": "/api/data"}}, { "name": "HTTPRequest", "cat": "network", "ph": "T", "id": 200, "ts": 3000, "pid": 1, "tid": 1, "args": { "step": "dns_resolved"}}, { "name": "HTTPRequest", "cat": "network", "ph": "T", "id": 200, "ts": 5000, "pid": 1, "tid": 1, "args": { "step": "connected"}}, { "name": "HTTPRequest", "cat": "network", "ph": "F", "id": 200, "ts": 8000, "pid": 1, "tid": 1, "args": { "status": 200}} ]}
与新版 (b/n/e) 的对应:S → b,T → n,F → e。
4.1.7 Flow Events — "s" (Start) / "t" (Step) / "f" (End)
用途:在两个 slice 之间绘制箭头,表示因果关系、数据传递、激活触发。
适用场景:任务激活链、生产者-消费者关系、跨核/跨芯片数据流。
非常推荐用这个字段。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "Producer"} }, { "ph": "M", "pid": 2, "name": "process_name", "args": { "name": "Consumer"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "Sender"} }, { "ph": "M", "pid": 1, "tid": 2, "name": "thread_name", "args": { "name": "Relay"} }, { "ph": "M", "pid": 2, "tid": 1, "name": "thread_name", "args": { "name": "Receiver"} }, { "name": "Encode", "ph": "X", "ts": 1000, "dur": 500, "pid": 1, "tid": 1}, { "name": "Forward", "ph": "X", "ts": 1600, "dur": 300, "pid": 1, "tid": 2}, { "name": "Decode", "ph": "X", "ts": 2000, "dur": 400, "pid": 2, "tid": 1}, { "name": "data_flow", "cat": "flow", "ph": "s", "id": 1, "ts": 1500, "pid": 1, "tid": 1, "bp": "e"}, { "name": "data_flow", "cat": "flow", "ph": "t", "id": 1, "ts": 1600, "pid": 1, "tid": 2, "bp": "e"}, { "name": "data_flow", "cat": "flow", "ph": "f", "id": 1, "ts": 2000, "pid": 2, "tid": 1, "bp": "e"} ]}
字段说明:
|
|
|
|---|---|
id |
|
bp |
"e" = 绑在该时刻结束的 slice 尾部 |
"t"
|
|
Perfetto 显示效果:两段箭头,从 Encode 尾部 → Forward 头部 → Decode 头部。
4.1.8 Sample Event — "P"
用途:记录 CPU 采样数据,用于性能分析中的调用栈采样(类似 perf 的采样记录)。
适用场景:CPU profiling、热点函数识别。
实测一直显示不出来,一般也用不到。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MyApp"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "WorkerThread"} }, { "name": "", "cat": "disabled-by-default-cpu_profiler", "ph": "P", "ts": 5000, "pid": 1, "tid": 1, "args": { "frames": "main\nprocess_data\ncompute_hash\n"}}, { "name": "", "cat": "disabled-by-default-cpu_profiler", "ph": "P", "ts": 6000, "pid": 1, "tid": 1, "args": { "frames": "main\nprocess_data\ncompress_block\n"}} ]}
说明:
|
|
|
|---|---|
ts |
|
args.frames |
\n 分隔,从外到内 |
4.1.9 Object Events — "N" (Created) / "O" (Snapshot) / "D" (Destroyed)
用途:跟踪对象的完整生命周期——创建、状态快照、销毁。
适用场景:连接池管理、缓冲区分配/释放、会话生命周期追踪。实测一直显示不出来,一般也用不到。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "BufferManager"} }, { "name": "Buffer", "ph": "N", "id": "buf_01", "ts": 1000, "pid": 1, "tid": 1, "args": { "size": 4096, "type": "DMA"}}, { "name": "Buffer", "ph": "O", "id": "buf_01", "ts": 3000, "pid": 1, "tid": 1, "args": { "snapshot": { "used": 2048, "free": 2048, "owner": "SPI_Driver"}}}, { "name": "Buffer", "ph": "O", "id": "buf_01", "ts": 5000, "pid": 1, "tid": 1, "args": { "snapshot": { "used": 4096, "free": 0, "owner": "DMA_Engine"}}}, { "name": "Buffer", "ph": "D", "id": "buf_01", "ts": 7000, "pid": 1, "tid": 1} ]}
字段说明:
|
|
|
|
|---|---|---|
|
|
"N" |
args 记录初始属性 |
|
|
"O" |
args.snapshot 记录详细状态 |
|
|
"D" |
|
所有事件通过 id 关联到同一对象实例。
4.1.10 Metadata Event — "M"
用途:为进程和线程设置显示名称、排序优先级等元信息。不会在时间轴上显示,仅影响 UI 展示。
适用场景:所有 trace 文件的开头,用于定义轨道名称。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "ph": "M", "pid": 2, "name": "process_name", "args": { "name": "SoC"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "MCU_Core0"} }, { "ph": "M", "pid": 1, "tid": 2, "name": "thread_name", "args": { "name": "MCU_Core1"} }, { "ph": "M", "pid": 1, "name": "process_sort_index", "args": { "sort_index": 0} }, { "ph": "M", "pid": 2, "name": "process_sort_index", "args": { "sort_index": 1} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_sort_index", "args": { "sort_index": 0} }, { "ph": "M", "pid": 1, "tid": 2, "name": "thread_sort_index", "args": { "sort_index": 1} } ]}
支持的 Metadata name 类型:
name
|
|
|---|---|
"process_name" |
|
"thread_name" |
|
"process_sort_index" |
|
"thread_sort_index" |
|
"process_labels" |
|
"num_cpus" |
|
4.1.11 Memory Dump Events — "V" (Global) / "v" (Process)
用途:记录内存使用快照,用于内存泄漏分析和使用趋势追踪。
适用场景:嵌入式系统内存监控、堆分析。实测一直显示不出来,一般也用不到。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "ph": "V", "ts": 5000, "pid": 1, "id": "dump_001", "args": { "dumps": { "level_of_detail": "light", "process_totals": { "resident_set_bytes": "1048576"}}}}, { "ph": "v", "ts": 5000, "pid": 1, "id": "dump_001", "args": { "dumps": { "allocators": { "heap": { "attrs": { "size": { "type": "scalar", "units": "bytes", "value": "524288"}, "count": { "type": "scalar", "units": "objects", "value": "1200"}}}, "stack": { "attrs": { "size": { "type": "scalar", "units": "bytes", "value": "8192"}}}}}}} ]}
字段说明:
|
|
|
|
|---|---|---|
|
|
"V" |
|
|
|
"v" |
|
同一时刻的 "V" 和 "v" 通过相同的 id 关联。
4.1.12 Mark Event — "R"
用途:在时间轴上放置一个命名标记,类似书签,方便定位关键时刻。
适用场景:版本发布点、测试用例起始、系统重启时刻、关键阈值触发。
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "System"} }, { "name": "TestCase_Start", "cat": "benchmark", "ph": "R", "ts": 0, "pid": 1, "tid": 1, "args": { "test_name": "latency_test_001"}}, { "name": "Threshold_Exceeded", "cat": "alert", "ph": "R", "ts": 15000, "pid": 1, "tid": 1, "args": { "metric": "cpu_temp", "value": 95, "limit": 90}}, { "name": "TestCase_End", "cat": "benchmark", "ph": "R", "ts": 50000, "pid": 1, "tid": 1, "args": { "test_name": "latency_test_001", "result": "PASS"}} ]}
与 Instant Event 的区别: Mark ("R") 默认在时间轴顶部全局可见,更适合标注关键里程碑;Instant ("i") 绑定到特定轨道。
4.1.13 Context Event — "c" (Legacy)
用途:旧版上下文事件,用于关联不同线程上的事件。已被 Flow Events 取代,不建议新代码使用。
适用场景:仅用于兼容遗留 trace 数据。
{ "traceEvents": [ { "name": "context", "cat": "legacy", "ph": "c", "ts": 1000, "pid": 1, "tid": 1, "id": "ctx_01", "args": { "note": "deprecated - use flow events instead"}} ]}
建议:如需关联不同轨道的事件,请使用 Flow Events (
s/t/f)。
4.1.14 综合示例:一个文件包含所有常用事件类型
以下 JSON 将最常用的事件类型组合在一个文件中,可直接拖入 Perfetto 查看效果:备注:存在R或者i事件会导致activate连线异常。
{ "displayTimeUnit": "ms", "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "ph": "M", "pid": 2, "name": "process_name", "args": { "name": "SoC"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "Core0"} }, { "ph": "M", "pid": 2, "tid": 1, "name": "thread_name", "args": { "name": "Core0"} }, { "ph": "M", "pid": 1, "name": "process_sort_index", "args": { "sort_index": 0} }, { "ph": "M", "pid": 2, "name": "process_sort_index", "args": { "sort_index": 1} }, { "name": "Task0", "cat": "task", "ph": "X", "ts": 0, "dur": 1000, "pid": 1, "tid": 1}, { "name": "Task1", "cat": "task", "ph": "X", "ts": 1100, "dur": 1000, "pid": 1, "tid": 1}, { "name": "activate", "ph": "s", "id": 1, "ts": 1000, "pid": 1, "tid": 1, "bp": "e"}, { "name": "Outer", "ph": "B", "ts": 0, "pid": 2, "tid": 1}, { "name": "Inner", "ph": "B", "ts": 200, "pid": 2, "tid": 1}, { "name": "Inner", "ph": "E", "ts": 800, "pid": 2, "tid": 1}, { "name": "Outer", "ph": "E", "ts": 1500, "pid": 2, "tid": 1}, { "name": "activate", "ph": "f", "id": 1, "ts": 1100, "pid": 2, "tid": 1, "bp": "e"}, { "name": "DMA_Copy", "ph": "b", "id": "d1", "ts": 500, "pid": 1, "cat": "dma"}, { "name": "DMA_Copy", "ph": "e", "id": "d1", "ts": 1800, "pid": 1, "cat": "dma"}, { "name": "CPU_Load", "ph": "C", "ts": 0, "pid": 1, "args": { "percent": 20} }, { "name": "CPU_Load", "ph": "C", "ts": 1000, "pid": 1, "args": { "percent": 85} }, { "name": "CPU_Load", "ph": "C", "ts": 2000, "pid": 1, "args": { "percent": 40} }, { "name": "Obj", "ph": "N", "id": "o1", "ts": 100, "pid": 1, "tid": 1}, { "name": "Obj", "ph": "O", "id": "o1", "ts": 1000, "pid": 1, "tid": 1, "args": { "snapshot": { "state": "active"} } }, { "name": "Obj", "ph": "D", "id": "o1", "ts": 2000, "pid": 1, "tid": 1} ]}
4.2 元数据事件(ph: “M”)
用于给 Perfetto 中的轨道命名,让可视化界面更友好。
{ "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }{ "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "MCU_Core0"} }
|
|
|
|---|---|
pid |
|
tid |
|
name |
"process_name" 或 "thread_name" |
args.name |
|
4.3 完整区间事件(ph: “X”)
最常用的事件类型——一条事件描述一个任务的完整执行过程。
{ "name": "Task0", "cat": "task", "ph": "X", "ts": 0, "dur": 1000, "pid": 1, "tid": 1, "args": { "start_ms": 0, "dur_ms": 1.0}}
|
|
|
|
|---|---|---|
name |
|
|
cat |
|
|
ph |
|
"X" |
ts |
|
起始时间戳,单位:微秒(μs) |
dur |
|
持续时长,单位:微秒(μs) |
pid |
|
|
tid |
|
|
args |
|
|
4.4 时间戳详解
这是最容易出错的地方。 Trace Event Format 中所有时间戳(ts、dur)的单位统一为微秒(μs),即使设置了 displayTimeUnit: "ms",底层数据仍以微秒为准。
换算规则:
|
|
|
|
|---|---|---|
|
|
|
ts: 1500 |
|
|
|
ts: 1000 |
|
|
|
ts: 1500 |
示例: 一个从 2.1ms 开始、持续 0.5ms 的任务:
{ "ph": "X", "ts": 2100, "dur": 500, "pid": 2, "tid": 1, "name": "Task0"}
多芯片场景下的时间戳对齐:当 MCU 和 SoC 使用各自的本地时钟时,必须先完成时间同步(如 PTP/NTP),将所有时间戳转换到统一基准后再写入 JSON。在 JSON 中,不同 pid 的事件共享同一条时间轴,因此时间戳必须可比较。
4.5 流事件详解(ph: “s” / “f”)
流事件用于在 Perfetto 中绘制从一个 slice 指向另一个 slice 的箭头,直观表示任务之间的因果关系。
一条流由两个事件组成,通过相同的 id 关联:
|
|
|
|
|
|---|---|---|---|
|
|
"s" |
|
|
|
|
"f" |
|
|
关键字段:
|
|
|
|---|---|
id |
|
bp |
"e" = 绑在 slice 末端,"s" = 绑在 slice 首端 |
完整示例——MCU Core0 Task5 结束激活 Core1 Task0:
// Core0 上的 Task5:0~1000μs{ "name": "Task5", "ph": "X", "ts": 0, "dur": 1000, "pid": 1, "tid": 1 },// 流起点:绑在 Task5 结束处 (ts=1000){ "name": "activate", "cat": "flow", "ph": "s", "id": "0", "ts": 1000, "pid": 1, "tid": 1, "bp": "e" },// Core1 上的 Task0:1100~2100μs{ "name": "Task0", "ph": "X", "ts": 1100, "dur": 1000, "pid": 1, "tid": 2 },// 流终点:绑在 Task0 开始处 (ts=1100){ "name": "activate", "cat": "flow", "ph": "f", "id": "0", "ts": 1100, "pid": 1, "tid": 2, "bp": "e" }
在 Perfetto UI 中,开启 Flow 显示后,会看到一条从 Core0 Task5 末端指向 Core1 Task0 首端的箭头。
多级流事件(ph: “t”):
如果一个激活链跨越多个中间环节(A → B → C),可以用 "t"(flow step)连接中间节点,所有步骤共享同一个 id:
{ "ph": "s", "id": 42, "ts": 1000, "pid": 1, "tid": 1 },{ "ph": "t", "id": 42, "ts": 2000, "pid": 1, "tid": 2 },{ "ph": "f", "id": 42, "ts": 3000, "pid": 2, "tid": 1 }
5. 场景示例
场景 1:单核顺序任务
一个核上有 3 个任务依次执行(1ms/task,0.1ms 间隔):
{ "displayTimeUnit": "ms", "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "Core0"} }, { "name": "Task0", "ph": "X", "ts": 0, "dur": 1000, "pid": 1, "tid": 1}, { "name": "Task1", "ph": "X", "ts": 1100, "dur": 1000, "pid": 1, "tid": 1}, { "name": "Task2", "ph": "X", "ts": 2200, "dur": 1000, "pid": 1, "tid": 1} ]}
场景 2:跨核激活(带 Flow)
Core0 的 Task5 完成后激活 Core1 的 Task0:
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "ph": "M", "pid": 1, "tid": 1, "name": "thread_name", "args": { "name": "Core0"} }, { "ph": "M", "pid": 1, "tid": 2, "name": "thread_name", "args": { "name": "Core1"} }, { "name": "Task5", "ph": "X", "ts": 0, "dur": 1000, "pid": 1, "tid": 1}, { "name": "Task0", "ph": "X", "ts": 1100, "dur": 1000, "pid": 1, "tid": 2}, { "name": "activate", "ph": "s", "id": "1", "ts": 1000, "pid": 1, "tid": 1, "bp": "e"}, { "name": "activate", "ph": "f", "id": "1", "ts": 1100, "pid": 1, "tid": 2, "bp": "e"} ]}
场景 3:跨芯片激活(MCU → SoC)
MCU Core5 的 Task0 完成后激活 SoC Core0 的 Task0,使用不同的 pid 区分芯片:
{ "traceEvents": [ { "ph": "M", "pid": 1, "name": "process_name", "args": { "name": "MCU"} }, { "ph": "M", "pid": 2, "name": "process_name", "args": { "name": "SoC"} }, { "ph": "M", "pid": 1, "tid": 6, "name": "thread_name", "args": { "name": "MCU_Core5"} }, { "ph": "M", "pid": 2, "tid": 1, "name": "thread_name", "args": { "name": "SoC_Core0"} }, { "name": "Task0", "ph": "X", "ts": 1100, "dur": 1000, "pid": 1, "tid": 6}, { "name": "Task0", "ph": "X", "ts": 2100, "dur": 500, "pid": 2, "tid": 1}, { "name": "activate", "ph": "s", "id": "5", "ts": 2100, "pid": 1, "tid": 6, "bp": "e"}, { "name": "activate", "ph": "f", "id": "5", "ts": 2100, "pid": 2, "tid": 1, "bp": "e"} ]}
箭头会跨越 MCU 和 SoC 两个进程轨道区域,直观展示跨芯片的激活关系。

类似如图所示的效果。
6. 多芯片联合分析
6.1 前提:时间同步
多芯片联合分析的基础是所有芯片共享统一的时间基准。常见方案:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
时间同步完成后,所有事件的 ts 字段使用同一时间基准。
大家是不是忘记了,多芯片通信时的链路延时,偶发丢帧,到底是任务跑的不准还是数据到来的不准,一直在争论,现在有这个工具就会方便很多。
-
非SoC芯片上需要自己采用类似gliwa方案打日志,环形buffer需要大一点,数据量尽量压缩 -
SoC芯片尽量采用异步日志的方式 -
多个时钟源需要打日志对齐运行时刻的时间戳 -
所有日志后期采用脚本处理生成JSON
6.2 用 pid 区分芯片
在 Trace Event Format 中,pid(Process ID)是天然的分组维度。将每个芯片映射为一个独立的 pid:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Perfetto UI 会将同一 pid 的所有轨道归为一组,并以 process_name 元数据作为组标题。
6.3 跨芯片 Flow Events
流事件天然支持跨 pid 连接。只需保证 "s" 和 "f" 事件中的 id 匹配、pid / tid 正确指向源和目标芯片/核心即可。
在 Perfetto 中,这些跨芯片箭头会跨越不同进程区域绘制,清晰展示数据或控制流的跨芯片传递。
6.4 实际应用:MCU + SoC 联合调度
以 上图所示的场景为例(MCU 6核 + SoC 12核),完整的激活链为:
MCU Core0.Task5 ──→ MCU Core1.Task0MCU Core1.Task5 ──→ MCU Core2.Task0 ...MCU Core4.Task5 ──→ MCU Core5.Task0MCU Core5.Task0 ──→ SoC Core0.Task0 ← 跨芯片SoC Core0.Task1 ──→ SoC Core1.Task0 ...SoC Core10.Task1 ──→ SoC Core11.Task0
在 Perfetto 中,这条完整的激活链以箭头序列的形式展现,可以直接观察到:– 整条链路的端到端延迟– 哪个环节是瓶颈(箭头间距最长的部分)– 跨芯片传输的时间开销
7. 基于 Perfetto 分析任务抢占
7.1 什么是抢占
在实时系统中,高优先级任务可以中断正在运行的低优先级任务。在 Perfetto 时间线上表现为:
-
一个 Task 的区间被意外中断(出现空洞或被分割成多段) -
某个 Task 的实际开始时间晚于预期(被其他任务抢占了 CPU) -
某个 Task 的实际执行时长大于其标称运行时间(中间被打断)
7.2 检测方法
方法 1:区间分割法
将被抢占的任务拆为多段 B/E 事件,中间插入抢占者的区间:
{ "name": "LowPriTask", "ph": "B", "ts": 1000, "pid": 1, "tid": 1 },{ "name": "LowPriTask", "ph": "E", "ts": 1500, "pid": 1, "tid": 1 },{ "name": "HighPriISR", "ph": "X", "ts": 1500, "dur": 200, "pid": 1, "tid": 1 },{ "name": "LowPriTask", "ph": "B", "ts": 1700, "pid": 1, "tid": 1 },{ "name": "LowPriTask", "ph": "E", "ts": 2200, "pid": 1, "tid": 1 }
在 Perfetto 中会看到 LowPriTask 被 HighPriISR 打断。
方法 2:嵌套层级法
利用 B/E 事件的嵌套特性,抢占者显示为被抢占任务内部的子区间:
{ "name": "TaskA", "ph": "B", "ts": 1000, "pid": 1, "tid": 1 }, { "name": "ISR_Timer", "ph": "B", "ts": 1500, "pid": 1, "tid": 1 }, { "name": "ISR_Timer", "ph": "E", "ts": 1700, "pid": 1, "tid": 1 },{ "name": "TaskA", "ph": "E", "ts": 2500, "pid": 1, "tid": 1 }

推荐方法2,其他方法AI写的,我觉得一般般,清楚的看出来此处存在任务抢占。确定性调度的问题排查更加清晰了。
方法 3:差值分析法
对比任务的标称运行时间和实际执行时间:
{ "name": "Task0", "ph": "X", "ts": 1000, "dur": 1500, "pid": 1, "tid": 1, "args": { "expected_dur_us": 1000, "actual_dur_us": 1500, "preempted": true, "preemption_time_us": 500}}
在 args 中记录期望时长和实际时长,鼠标悬停即可看到差异。如果 actual > expected,说明被抢占。
7.3 在多芯片场景下的抢占分析
结合跨芯片 Flow Events,可以追踪:
- 上游延迟传播
:MCU 上某个 Task 被 ISR 抢占导致延迟 → 通过 Flow 箭头传递到 SoC → SoC 上的后续任务也全部延后 - 关键路径影响
:在激活链上任何一个节点的抢占都会影响整条链路的端到端时延 - 抢占频率统计
:通过 Perfetto 的 SQL 查询功能,统计每个 Task 被抢占的次数和总时长:
7.4 实践建议
人工补充
-
使用X,s,f,b,e这几个事件就足够了,其他事件乱用会影响流事件连线。
-
时间戳需要多芯片对齐
-
异步记录,异步处理日志
8. 快速参考卡
必要字段速查
|
|
|
|
|
|---|---|---|---|
|
|
M |
ph
pid, name, args.name |
tid |
|
|
B |
ph
ts, pid, tid, name |
cat
args |
|
|
E |
ph
ts, pid, tid |
name
args |
|
|
X |
ph
ts, dur, pid, tid, name |
cat
args, tdur |
|
|
i |
ph
ts, pid, tid, name |
s
cat, args |
|
|
C |
ph
ts, pid, name, args.{key} |
tid |
|
|
b |
ph
ts, pid, id, name |
cat
args, scope |
|
|
n |
ph
ts, pid, id, name |
cat
args |
|
|
e |
ph
ts, pid, id, name |
cat
args |
|
|
S |
ph
ts, pid, tid, id, name |
cat
args |
|
|
T |
ph
ts, pid, tid, id, name |
cat
args |
|
|
F |
ph
ts, pid, tid, id, name |
cat
args |
|
|
s |
ph
ts, pid, tid, id |
name
cat, bp |
|
|
t |
ph
ts, pid, tid, id |
name
cat, bp |
|
|
f |
ph
ts, pid, tid, id |
name
cat, bp |
|
|
P |
ph
ts, pid, tid |
name
args.frames |
|
|
N |
ph
ts, pid, id, name |
tid
args |
|
|
O |
ph
ts, pid, id, name |
tid
args.snapshot |
|
|
D |
ph
ts, pid, id, name |
tid |
|
|
V |
ph
ts, pid, id |
args.dumps |
|
|
v |
ph
ts, pid, id |
args.dumps |
|
|
R |
ph
ts, pid, name |
tid
cat, args |
时间转换函数(Python)
defms_to_us(ms):"""将毫秒转为微秒(Perfetto 要求的单位)"""return round(ms * 1000)
生成流程
1. 定义元数据(process_name, thread_name)2. 生成任务区间事件(ph: "X")3. 生成流事件(ph: "s"/"f",id 配对)4. 包装为 {"traceEvents": [...]}5. 输出 JSON 文件6. 拖入 https://ui.perfetto.dev/ 查看
9. 参考资料
-
Perfetto 官方文档 – 外部格式导入 -
Chrome Trace Event Format 规范 -
Flow Events in chrome://tracing (Stack Overflow) -
Perfetto UI
夜雨聆风