Skip to content

火焰图

火焰图有何作用?

火焰图是一种可视化函数中 CPU 时间消耗的方式。它们可以帮助你查明在哪里花费了过多的时间进行同步操作。

如何创建火焰图

你可能听说过为 Node.js 创建火焰图很困难,但事实并非如此(至少现在不是了)。不再需要 Solaris vms 来创建火焰图!

火焰图由 perf 输出生成,它不是 Node.js 特有的工具。虽然它是可视化 CPU 时间消耗最强大的方法,但在 Node.js 8 及更高版本中,它可能在 JavaScript 代码优化方面存在问题。请参见下面的 perf 输出问题 部分。

使用预打包工具

如果你想要一个本地生成火焰图的单步操作,请尝试使用 0x

对于诊断生产部署,请阅读以下说明:0x 生产服务器

使用系统 perf 工具创建火焰图

本指南的目的是展示创建火焰图所涉及的步骤,并让你掌控每个步骤。

如果你想更好地理解每个步骤,请查看后面的部分,我们将更详细地介绍。

现在开始工作吧。

  1. 安装 perf(如果尚未安装,通常可以通过 linux-tools-common 包安装)
  2. 尝试运行 perf - 它可能会抱怨缺少内核模块,也请安装它们
  3. 使用 perf 启用 node(有关 Node.js 版本的提示,请参见 perf 输出问题
bash
perf record -e cycles:u -g -- node --perf-basic-prof app.js
  1. 除非警告提示你由于缺少包而无法运行 perf,否则忽略警告;你可能会收到一些关于无法访问你不需要的内核模块样本的警告。
  2. 运行 perf script > perfs.out 以生成稍后可视化的数据文件。为了获得更易读的图表,应用一些清理操作很有用。
  3. 如果尚未安装 stackvis,请安装 npm i -g stackvis
  4. 运行 stackvis perf < perfs.out > flamegraph.htm

现在在您喜欢的浏览器中打开火焰图文件,观察它的燃烧情况。它是彩色编码的,因此你可以首先关注最饱和的橙色条。它们很可能代表 CPU 密集型函数。

值得一提的是 - 如果你点击火焰图的一个元素,它的周围环境的放大图将显示在图表上方。

使用 perf 采样正在运行的进程

这非常适合从不想中断的正在运行的进程中记录火焰图数据。想象一下,一个生产环境中的进程出现了一个难以重现的问题。

bash
perf record -F99 -p `pgrep -n node` -- sleep 3

sleep 3 是什么作用?它用于保持 perf 运行 - 尽管 -p 选项指向不同的 pid,但该命令需要在一个进程上执行并以它结束。perf 运行的时间取决于你传递给它的命令的生命周期,无论你是否真的在分析该命令。sleep 3 确保 perf 运行 3 秒。

为什么 -F(采样频率)设置为 99?这是一个合理的默认值。你可以根据需要调整。-F99 告诉 perf 每秒采集 99 个样本,为了更高的精度,可以增加该值。较低的值应该会产生较少的输出,精度也较低。所需的精度取决于你的 CPU 密集型函数实际运行的时间长短。如果你正在寻找明显减速的原因,每秒 99 帧应该绰绰有余。

获得 3 秒的 perf 记录后,继续使用上面最后两个步骤生成火焰图。

过滤掉 Node.js 内部函数

通常,你只想查看你的调用的性能,所以过滤掉 Node.js 和 V8 内部函数可以使图表更容易阅读。你可以用以下命令清理你的 perf 文件:

bash
sed -i -r \
    -e '/(_libc_start|LazyCompile) |v8::internal::BuiltIn|Stub|LoadIC:\\[\\[' \
    -e '/^$/d' \
    perf.data > perf.out

如果你阅读你的火焰图,并且它看起来很奇怪,好像在占用大部分时间的关键函数中缺少某些东西,请尝试在没有过滤器的情况下生成你的火焰图——也许你遇到了 Node.js 本身的问题的罕见情况。

Node.js 的性能分析选项

--perf-basic-prof-only-functions--perf-basic-prof 是这两个对调试你的 JavaScript 代码有用的选项。其他选项用于分析 Node.js 本身,这超出了本指南的范围。

--perf-basic-prof-only-functions 产生的输出较少,因此它是开销最小的选项。

为什么我需要它们?

如果没有这些选项,你仍然会得到一个火焰图,但是大多数条形图的标签都是 v8::Function::Call

Perf 输出问题

Node.js 8.x V8 管道更改

Node.js 8.x 及以上版本在 V8 引擎的 JavaScript 编译管道中引入了新的优化,这有时会使 perf 无法访问函数名称/引用。(它被称为 Turbofan)

结果是您可能无法在火焰图中正确获取函数名称。

您会注意到在预期函数名称的地方显示 ByteCodeHandler:

0x 有一些内置的缓解措施。

详情请参见:

Node.js 10+

Node.js 10.x 使用 --interpreted-frames-native-stack 标志解决了 Turbofan 的问题。

运行 node --interpreted-frames-native-stack --perf-basic-prof-only-functions 以在火焰图中获取函数名称,而不管 V8 使用哪个管道编译您的 JavaScript。

火焰图中损坏的标签

如果您看到的标签类似这样

bash
node`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPMS0_8Zone

这意味着您使用的 Linux perf 没有用反汇编支持进行编译,例如参见 https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1396654

示例

使用 火焰图练习 自行练习捕获火焰图!