火焰图
火焰图有何作用?
火焰图是一种可视化函数中 CPU 时间消耗的方式。它们可以帮助你查明在哪里花费了过多的时间进行同步操作。
如何创建火焰图
你可能听说过为 Node.js 创建火焰图很困难,但事实并非如此(至少现在不是了)。不再需要 Solaris vms 来创建火焰图!
火焰图由 perf
输出生成,它不是 Node.js 特有的工具。虽然它是可视化 CPU 时间消耗最强大的方法,但在 Node.js 8 及更高版本中,它可能在 JavaScript 代码优化方面存在问题。请参见下面的 perf 输出问题 部分。
使用预打包工具
如果你想要一个本地生成火焰图的单步操作,请尝试使用 0x
对于诊断生产部署,请阅读以下说明:0x 生产服务器。
使用系统 perf 工具创建火焰图
本指南的目的是展示创建火焰图所涉及的步骤,并让你掌控每个步骤。
如果你想更好地理解每个步骤,请查看后面的部分,我们将更详细地介绍。
现在开始工作吧。
- 安装
perf
(如果尚未安装,通常可以通过 linux-tools-common 包安装) - 尝试运行
perf
- 它可能会抱怨缺少内核模块,也请安装它们 - 使用 perf 启用 node(有关 Node.js 版本的提示,请参见 perf 输出问题)
perf record -e cycles:u -g -- node --perf-basic-prof app.js
- 除非警告提示你由于缺少包而无法运行 perf,否则忽略警告;你可能会收到一些关于无法访问你不需要的内核模块样本的警告。
- 运行
perf script > perfs.out
以生成稍后可视化的数据文件。为了获得更易读的图表,应用一些清理操作很有用。 - 如果尚未安装 stackvis,请安装
npm i -g stackvis
- 运行
stackvis perf < perfs.out > flamegraph.htm
现在在您喜欢的浏览器中打开火焰图文件,观察它的燃烧情况。它是彩色编码的,因此你可以首先关注最饱和的橙色条。它们很可能代表 CPU 密集型函数。
值得一提的是 - 如果你点击火焰图的一个元素,它的周围环境的放大图将显示在图表上方。
使用 perf
采样正在运行的进程
这非常适合从不想中断的正在运行的进程中记录火焰图数据。想象一下,一个生产环境中的进程出现了一个难以重现的问题。
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 文件:
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 有一些内置的缓解措施。
详情请参见:
- https://github.com/nodejs/benchmarking/issues/168
- https://github.com/nodejs/diagnostics/issues/148#issuecomment-369348961
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`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPMS0_8Zone
这意味着您使用的 Linux perf 没有用反汇编支持进行编译,例如参见 https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1396654
示例
使用 火焰图练习 自行练习捕获火焰图!