火焰图

火焰图有什么用?

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

如何创建火焰图

你可能听说过为 Node.js 创建火焰图很困难,但这已不再是事实。生成火焰图不再需要 Solaris 虚拟机!

火焰图是根据 perf 的输出生成的,它不是一个特定于 Node.js 的工具。虽然这是可视化 CPU 时间消耗最强大的方法,但它可能在 Node.js 8 及以上版本中对 JavaScript 代码的优化方式存在问题。请参阅下文的 perf 输出问题部分。

使用预打包的工具

如果你想要一步就在本地生成火焰图,可以试试 0x

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

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

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

如果你想更好地理解每一步,请查看接下来的部分,我们将进行更详细的介绍。

现在让我们开始工作吧。

  1. 安装 perf(如果尚未安装,通常可通过 linux-tools-common 包获得)

  2. 尝试运行 perf - 它可能会抱怨缺少内核模块,也请一并安装

  3. 在启用 perf 的情况下运行 Node.js(有关特定于 Node.js 版本的提示,请参阅 perf 输出问题

    perf record -e cycles:u -g -- node --perf-basic-prof --interpreted-frames-native-stack app.js
    
  4. 忽略警告,除非它们是说你因缺少软件包而无法运行 perf;你可能会收到一些关于无法访问内核模块样本的警告,但你本来也不需要它们。

  5. 运行 perf script > perfs.out 来生成你稍后将要可视化的数据文件。进行一些清理以获得更易读的图表会很有帮助

  6. 克隆 Brendan Gregg 的 FlameGraph 工具:https://github.com/brendangregg/FlameGraph

  7. 运行 cat perfs.out | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl --colors=js > profile.svg

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

值得一提的是 - 如果你点击火焰图的一个元素,它会放大你点击的部分。

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

这对于从一个你不想中断的正在运行的进程中记录火焰图数据非常有用。想象一下一个生产环境中的进程,其问题难以重现。

perf record -F99 -p `pgrep -n node` -g -- 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 文件

sed -i -r \
  -e "/( __libc_start| LazyCompile | v8::internal::| Builtin:| Stub:| LoadIC:|\[unknown\]| LoadPolymorphicIC:)/d" \
  -e 's/ LazyCompile:[*~]?/ /' \
  perfs.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。

火焰图中标签损坏

如果你看到的标签是这样的

node`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPNS0_8ZoneListIPNS0_9StatementEEE

这意味着你使用的 Linux perf 编译时没有包含 demangle 支持,例如请参见 https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1396654

示例

通过火焰图练习来自己练习捕获火焰图吧!