2. 火焰图原理:基本构成与可视化逻辑

火焰图(Flame Graph)是一种基于调用栈采样的性能可视化工具,由 Brendan Gregg 于 2011 年发明。它将海量、离散的采样点聚合为一张直观的“火焰”形状图形,使工程师能够快速定位 CPU 热点、锁竞争、I/O 等待等性能瓶颈。理解火焰图的基本构成与堆叠逻辑,是正确解读性能数据的前提。

2.1 火焰图的基本构成

一张标准的火焰图由四个核心视觉元素组成:x 轴、y 轴、矩形宽度和颜色。每个元素都承载着特定的性能语义。

视觉元素 含义 关键规则
x 轴 采样空间(非时间序列) 从左到右按函数名称字母序排列,不表示时间先后
y 轴 调用栈深度(栈帧层级) 底部为根(0 层),顶部为叶子(最深层),每层代表一个栈帧
矩形宽度 该函数在采样中的出现频率(CPU 占用比例) 宽度越大,表示该函数在采样周期内被捕获的次数越多,CPU 开销越高
颜色 函数类型或属性标识 暖色(橙/红)通常表示用户态代码,冷色(蓝/青)表示内核态代码,绿色表示空闲或 I/O 等待
关键理解:火焰图不是时间序列图。x 轴上的相邻矩形之间没有时间顺序关系,它们只是按照函数名排序后堆叠在一起。火焰图展示的是“在采样周期内,CPU 正在执行哪些调用栈”,而不是“CPU 先执行 A 再执行 B”。

2.2 调用栈的堆叠与可视化逻辑

火焰图的核心可视化逻辑是调用栈的聚合与堆叠。其生成过程可以概括为三个步骤:

  1. 采样:以固定频率(如 99 Hz)对目标进程或系统进行栈回溯,每次采样记录当前 CPU 正在执行的完整调用栈。
  2. 聚合:将所有采样点中的相同调用栈路径合并,统计每条路径出现的次数。
  3. 堆叠绘制:将聚合后的调用栈按深度从底向上堆叠,同一深度的函数按名称字母序从左到右排列,矩形宽度正比于该路径的采样次数。

下面的 SVG 图展示了三个采样点如何聚合为火焰图中的一个“塔”状结构:

调用栈采样 → 聚合 → 堆叠可视化 采样点 1 sys_read do_sync_read ext4_file_read generic_file_read 采样点 2 sys_read do_sync_read ext4_file_read generic_file_read 采样点 3 sys_read do_sync_read xfs_file_read 聚合统计 sys_read: 3 次 do_sync_read: 3 次 ext4_file_read: 2 次 generic_file_read: 2 次 xfs_file_read: 1 次 火焰图堆叠 sys_read (100%) do_sync_read (100%) ext4_file_read (66.7%) xfs_file_read (33.3%) generic_file_read (66.7%) 矩形宽度 ∝ 采样次数占比 y 轴深度 = 调用栈层数
堆叠规则总结:
  • 自底向上:底部是调用栈的根(如系统调用入口),顶部是叶子函数(如具体的驱动或文件系统操作)。
  • 宽度等比:每个矩形的宽度与它在所有采样中出现的次数成正比。如果一个函数出现在 60% 的采样中,它的宽度就占整个底边的 60%。
  • 子栈继承:上层矩形的宽度不会超过其直接父矩形的宽度。子函数只能出现在父函数“之上”的对应宽度范围内。
  • 字母序排列:同一深度的函数按名称字母序从左到右排列,避免每次生成时位置抖动。

2.3 颜色编码方案

火焰图的颜色主要用于快速区分函数所属的执行域。不同的火焰图工具(如 perf、FlameGraph 脚本、Brendan Gregg 的原始实现)可能使用略有差异的调色板,但业界通用的语义如下:

颜色 典型含义 常见场景
橙红 / 红色 用户态代码(User-space) 应用程序逻辑、libc 库函数、JIT 编译代码
蓝色 / 青色 内核态代码(Kernel-space) 系统调用、驱动、文件系统、网络协议栈
绿色 空闲 / 等待 / I/O CPU idle 线程、I/O 等待、锁阻塞
紫色 动态追踪 / 插桩 BPF 程序、kprobe/uprobe 处理函数
黄色 / 橙色 中间层 / 库函数 glibc 封装、vdso、语言运行时
注意:颜色只是辅助标识,永远不要仅凭颜色判断性能瓶颈。瓶颈的判断依据是矩形的宽度(即 CPU 占用比例),而不是颜色。一个很宽的蓝色内核函数可能比一个窄的红色用户函数更需要优化。

2.4 火焰图的“地形”解读

通过观察火焰图的整体形状,可以快速形成对性能问题的初步判断:

  • “平顶山”地形:顶部有大量宽度相近的矩形,说明 CPU 时间分散在多个不同的叶子函数上,可能是代码路径过于发散或存在大量短任务。
  • “尖塔”地形:某个调用栈路径的矩形宽度明显大于其他路径,形成一座“高塔”,说明 CPU 时间高度集中在该路径上,这是最值得优化的热点。
  • “高原”地形:中间层出现一个非常宽的矩形,其上层子函数宽度较窄,说明该函数本身(而非其子函数)消耗了大量 CPU,可能是自旋等待、计算密集型循环或锁竞争。
  • “断崖”地形:调用栈在某一层突然中断,所有上层矩形宽度骤降为 0,说明采样时栈回溯不完整(常见于缺少符号表或帧指针优化导致栈展开失败)。

下面的 SVG 图展示了四种典型地形:

火焰图四种典型地形 平顶山 CPU 分散在多个路径 尖塔 单一路径深度占用 高原 中间层函数自身消耗大 断崖 栈回溯不完整 / 符号缺失

2.5 火焰图与调用栈的关系

火焰图的本质是调用栈的频率分布直方图。每个矩形对应一个栈帧(函数调用),矩形的堆叠顺序严格遵循调用栈的父子关系。理解这一点,就能明白为什么火焰图可以快速定位“热点路径”:

  • 如果 do_sync_read 的矩形很宽,说明 CPU 经常停留在该函数内部或其子函数中。
  • 如果 ext4_file_read 的矩形很宽,但它的子函数 generic_file_read 很窄,说明 CPU 时间主要消耗在 ext4_file_read 函数体本身(而非其调用的子函数),这可能是一个优化点。
  • 如果火焰图顶部出现大量窄矩形(“头发丝”),说明采样频率过高或调用栈深度过深,可以考虑降低采样频率或使用折叠模式。
实战要点:
  • 解读火焰图时,始终从顶部最宽的矩形开始看,它代表采样周期内 CPU 最频繁执行的代码路径。
  • 点击(或悬停)某个矩形,可以查看该函数的完整调用栈路径,以及它在所有采样中的出现次数和占比。
  • 如果火焰图底部(第 0 层)出现多个不连续的宽矩形,说明系统中有多个不相关的热点进程或线程在竞争 CPU。
  • 火焰图无法直接显示 I/O 等待时间或锁等待时间,它只显示 CPU 在采样时刻正在执行的代码。对于 I/O 密集型瓶颈,需要结合 Off-CPU 火焰图或 Wakeup 火焰图进行分析。