乐于分享
好东西不私藏

使用开源perfetto工具实现问题可视化排查

使用开源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 的作用——能做什么

应用场景
说明
任务排布可视化
将每个核上 Task 的起止时间以甘特图展示,一目了然
激活链路追踪
用 Flow Events 的箭头表示”A 完成后触发 B”的因果关系
多芯片联合分析
将 MCU、SoC 等不同芯片的任务放在同一时间轴对齐观察
调度优化验证
直观对比不同调度方案的时序差异和关键路径长度
抢占检测
分析任务是否在预期时间窗口内被延迟或抢占
性能瓶颈定位
识别空闲间隔、依赖等待、关键路径上的慢任务

4. Trace Event Format 字段详解

4.1 事件类型总览(ph 字段)

Chrome Trace Event Format 共定义了以下事件类型,Perfetto 对其中大部分提供了支持:

ph 值
类型
含义
Perfetto 支持
"B"
Duration Begin
区间开始(与 "E" 配对)
支持
"E"
Duration End
区间结束(与 "B" 配对)
支持
"X"
Complete
完整区间(一条事件包含起止)
支持
"i"

 / "I"
Instant
瞬时事件(无持续时间)
支持
"C"
Counter
计数器(绘制折线图/面积图)
支持
"b"
Async Begin
异步区间开始(与 "e" 配对)
支持
"n"
Async Instant
异步瞬时事件
支持
"e"
Async End
异步区间结束(与 "b" 配对)
支持
"S"
Async Start (legacy)
旧版异步开始(与 "F" 配对)
支持
"T"
Async Step (legacy)
旧版异步步骤
支持
"F"
Async Finish (legacy)
旧版异步结束(与 "S" 配对)
支持
"s"
Flow Start
流事件起点(箭头尾部)
支持
"t"
Flow Step
流事件中间步骤
支持
"f"
Flow End
流事件终点(箭头头部)
支持
"P"
Sample
采样事件(CPU profiling)
支持
"N"
Object Created
对象生命周期:创建
支持
"O"
Object Snapshot
对象生命周期:快照
支持
"D"
Object Destroyed
对象生命周期:销毁
支持
"M"
Metadata
元数据(进程名/线程名等)
支持
"V"
Memory Dump (global)
全局内存快照
支持
"v"
Memory Dump (process)
进程级内存快照
支持
"R"
Mark
时间标记(在时间轴上标注)
支持
"c"
Context (legacy)
上下文事件(已废弃)
有限

下面逐一给出每种事件类型的完整 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}  ]}

字段说明:

字段
B 事件
E 事件
name
必填,区间名称
可选(省略则自动匹配最近的 B)
ts
区间开始时间 (μs)
区间结束时间 (μs)
args
可选,附加数据
可选,会与 B 的 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
number
开始时间 (μs)
dur
number
持续时长 (μs)
tdur
number
可选,线程实际 CPU 时间 (μs),区别于墙钟时间

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"
Global
竖线贯穿所有轨道
"p"
Process
竖线贯穿当前进程的所有轨道
"t"
Thread
仅在当前线程轨道上显示标记

注意:"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
必填。区分同名异步操作的不同实例(如多通道 DMA 并发)
scope
可选。对 id 进行命名空间隔离,避免不同类别 id 冲突
"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 → bT → nF → 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
必填。同一流的 s/t/f 必须用相同 id
bp
绑定点。"e" = 绑在该时刻结束的 slice 尾部
"t"

 (Step)
可选。用于多级传递,箭头会分段:Encode → Forward → Decode

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
采样时刻 (μs)
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}  ]}

字段说明:

事件
ph
说明
Created
"N"
对象诞生,args 记录初始属性
Snapshot
"O"
对象当前状态快照,args.snapshot 记录详细状态
Destroyed
"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"
控制进程在 UI 中的显示顺序(数值越小越靠上)
"thread_sort_index"
控制线程在 UI 中的显示顺序
"process_labels"
为进程添加额外标签
"num_cpus"
记录 CPU 核心数

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"}}}}}}}  ]}

字段说明:

事件
ph
说明
Global Dump
"V"
全局内存快照,包含所有进程的总体信息
Process Dump
"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
进程 ID,可用于区分不同芯片(如 MCU=1, SoC=2)
tid
线程 ID,可用于区分不同核心(建议从 1 开始,tid=0 在某些解析器中有特殊含义)
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
string
任务名称,显示在区间块上
cat
string
分类标签,可用于过滤
ph
string
固定为 "X"
ts
number
起始时间戳,单位:微秒(μs)
dur
number
持续时长,单位:微秒(μs)
pid
number
进程 ID
tid
number
线程 ID
args
object
可选,附加信息(鼠标悬停时显示)

4.4 时间戳详解

这是最容易出错的地方。 Trace Event Format 中所有时间戳(tsdur)的单位统一为微秒(μs),即使设置了 displayTimeUnit: "ms",底层数据仍以微秒为准。

换算规则:

原始单位
转换到微秒
示例
毫秒 (ms)
× 1000
1.5ms → ts: 1500
秒 (s)
× 1,000,000
0.001s → ts: 1000
纳秒 (ns)
÷ 1000
1,500,000ns → 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 关联:

事件
ph
含义
ts 应设为
流起点
"s"
箭头尾部
源 slice 的结束时刻
流终点
"f"
箭头头部
目标 slice 的开始时刻

关键字段:

字段
说明
id
流的唯一标识,开始和结束必须相同(数字或字符串)
bp
绑定点(Binding Point):"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 前提:时间同步

多芯片联合分析的基础是所有芯片共享统一的时间基准。常见方案:

同步方式
精度
适用场景
PTP (IEEE 1588)
亚微秒级
以太网互联的多芯片系统
GPS 时钟
微秒级
分布式嵌入式系统
硬件同步信号
纳秒级
板级多芯片(共享晶振)
软件 NTP
毫秒级
精度要求不高的场景

时间同步完成后,所有事件的 ts 字段使用同一时间基准。

大家是不是忘记了,多芯片通信时的链路延时,偶发丢帧,到底是任务跑的不准还是数据到来的不准,一直在争论,现在有这个工具就会方便很多。

人工补充:
这边还有几个问题:
  • 非SoC芯片上需要自己采用类似gliwa方案打日志,环形buffer需要大一点,数据量尽量压缩
  • SoC芯片尽量采用异步日志的方式
  • 多个时钟源需要打日志对齐运行时刻的时间戳
  • 所有日志后期采用脚本处理生成JSON

6.2 用 pid 区分芯片

在 Trace Event Format 中,pid(Process ID)是天然的分组维度。将每个芯片映射为一个独立的 pid

芯片
pid
说明
MCU
1
6 个核 → tid 1~6
SoC
2
12 个核 → tid 1~12
DSP
3
可继续扩展
GPU
4
可继续扩展

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,可以追踪:

  1. 上游延迟传播
    :MCU 上某个 Task 被 ISR 抢占导致延迟 → 通过 Flow 箭头传递到 SoC → SoC 上的后续任务也全部延后
  2. 关键路径影响
    :在激活链上任何一个节点的抢占都会影响整条链路的端到端时延
  3. 抢占频率统计
    :通过 Perfetto 的 SQL 查询功能,统计每个 Task 被抢占的次数和总时长:

7.4 实践建议

人工补充

  1. 使用X,s,f,b,e这几个事件就足够了,其他事件乱用会影响流事件连线。

  2. 时间戳需要多芯片对齐

  3. 异步记录,异步处理日志


8. 快速参考卡

必要字段速查

事件类型
ph
必填字段
可选字段
Metadata
M ph

pidnameargs.name
tid
Duration Begin
B ph

tspidtidname
cat

args
Duration End
E ph

tspidtid
name

args
Complete
X ph

tsdurpidtidname
cat

argstdur
Instant
i ph

tspidtidname
s

(scope), catargs
Counter
C ph

tspidnameargs.{key}
tid
Async Begin
b ph

tspididname
cat

argsscope
Async Instant
n ph

tspididname
cat

args
Async End
e ph

tspididname
cat

args
Legacy Async Start
S ph

tspidtididname
cat

args
Legacy Async Step
T ph

tspidtididname
cat

args
Legacy Async Finish
F ph

tspidtididname
cat

args
Flow Start
s ph

tspidtidid
name

catbp
Flow Step
t ph

tspidtidid
name

catbp
Flow End
f ph

tspidtidid
name

catbp
Sample
P ph

tspidtid
name

args.frames
Object Created
N ph

tspididname
tid

args
Object Snapshot
O ph

tspididname
tid

args.snapshot
Object Destroyed
D ph

tspididname
tid
Memory Dump Global
V ph

tspidid
args.dumps
Memory Dump Process
v ph

tspidid
args.dumps
Mark
R ph

tspidname
tid

catargs

时间转换函数(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
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 使用开源perfetto工具实现问题可视化排查

猜你喜欢

  • 暂无文章