火焰图

火焰图有什么用?

火焰图是一种可视化函数中 CPU 时间花费的方式。它们可以帮助您找出在同步操作上花费过多时间的位置。

如何创建火焰图

您可能听说过为 Node.js 创建火焰图很困难,但事实并非如此(现在)。火焰图不再需要 Solaris 虚拟机!

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

使用预打包的工具

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

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

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

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

如果您想更好地理解每个步骤,请查看以下部分,我们将详细介绍。

现在开始工作。

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

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

  3. 运行启用 perf 的节点(请参阅perf 输出问题,了解特定于 Node.js 版本的提示)

    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

示例

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