火焰图
火焰图有什么用?
火焰图是一种可视化函数中花费的 CPU 时间的方式。它们可以帮助您确定您在同步操作上花费了太多时间的地方。
如何创建火焰图
您可能听说过为 Node.js 创建火焰图很困难,但事实并非如此(不再是)。不再需要 Solaris vms 来创建火焰图!
火焰图是从 `perf` 输出生成的,它不是特定于节点的工具。虽然它是可视化 CPU 时间花费的最强大的方式,但它可能在 Node.js 8 及更高版本中如何优化 JavaScript 代码方面存在问题。请参阅下面的 perf 输出问题 部分。
使用预打包的工具
如果您想要一个在本地生成火焰图的单一步骤,请尝试使用 0x
有关诊断生产部署的信息,请阅读以下说明:0x 生产服务器。
使用系统 perf 工具创建火焰图
本指南的目的是展示创建火焰图所涉及的步骤,并让您控制每个步骤。
如果您想更好地理解每个步骤,请查看以下部分,我们将详细介绍。
现在让我们开始工作。
-
安装 `perf`(通常通过 linux-tools-common 包提供,如果尚未安装)。
-
尝试运行 `perf` - 它可能会抱怨缺少内核模块,也请安装它们。
-
使用启用了 perf 的节点运行(有关特定于 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` -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 内置了一些缓解措施。
有关详细信息,请参阅
- 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`_ZN2v88internal11interpreter17BytecodeGenerator15VisitStatementsEPNS0_8ZoneListIPNS0_9StatementEEE
这意味着你使用的 Linux perf 没有用 demangle 支持编译,例如,请参阅 https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1396654
示例
使用 火焰图练习 自己练习捕获火焰图!