
Cloudflare为Workers和Durable Objects推出按需CPU和内存性能分析火焰图
来源标题:Introducing on-demand CPU and memory profiling with flamegraphs for Workers and Durable Objects
Cloudflare宣布为Workers和Durable Objects提供按需CPU和内存性能分析功能,用户可在Workers Observability页面请求分析,通过交互式火焰图定位内存泄漏和性能瓶颈,并支持下载分析文件进一步分析。
以下正文由 AI 翻译自 Cloudflare Blog(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
了解为什么一个应用使用的资源(无论是 CPU 还是内存)超出预期,可能很有挑战性。日志和聚合指标能提供的帮助有限。好在还有更好的办法:CPU 或内存 profiling 可以准确显示出你的应用在哪个函数中消耗 CPU 或分配内存。
今天我们很高兴地宣布支持对 Workers 和 Durable Objects 进行 CPU 和内存 profiling。现在,你可以在 Workers Observability 页面按需对运行中的 Worker 发起 CPU 或内存 profile 请求,以交互式 flamegraph 的形式查看结果,并下载 profile 文件做进一步分析。
这种 profiling 方法能让你从实际运行环境的角度了解代码在做什么、以及如何改进。因为理解应用的最佳方式,就是在生产环境中对它进行 profile。
要在你的某个 Worker 上试用这个功能,你可以选择使用 CLI 或 Cloudflare Dashboard。
使用 CLI 时,请确保已安装 cf 包,然后直接运行:
使用 dashboard 时,前往 Cloudflare dashboard,通过 Build → Compute → Workers & Pages 导航进入你账户下的 Workers 列表。
选择你的 Worker,进入它的 Observability 标签页。然后使用下拉菜单选择“Flamegraph”:
接下来你就可以在这个页面上为你的 Worker 请求 CPU 和内存 profile。Duration 决定 profiler 在你的 Worker 上运行多长时间。你还可以选择要 profile 的 Worker 版本。你的 Worker 需要有足够的流量才能成功完成 profiling,因此请确保选择一个流量足够的版本。
点击 Capture Profile 按钮后,profiler 会按照你选择的时长运行,随后你会看到渲染出的 flamegraph。flamegraph 中的每个矩形代表一次函数调用,宽度代表该函数占用的 CPU 时间或内存量。你可以点击不同的函数来聚焦查看,悬停则可以查看更多详情:
不妨多抓几个 profile,然后找出最宽的矩形,来判断你的 Worker 中什么在消耗最多资源。你可能还会发现表格视图很有用,可以快速了解哪些函数在 profile 中出现得最频繁。
如果你的 Worker 是用 TypeScript 实现的,请确保项目启用了 source maps,否则你的 profile 可能会显示难以理解的混淆后的函数名。
我们已经在 Cloudflare 内部看到多个团队使用 CPU 和内存 profiling 来发现优化内存和 CPU 使用的机会、修复内存溢出(OOM)错误以及提升性能。以下是一些示例。
用 CPU profiling 找到优化点
让我们看一个真实的例子,了解如何用 CPU profiling 来优化你的 Workers。我们会看一个实现了 R2 binding 的 Worker 的 CPU profile。这个 Worker 接收大量流量,因此即使消除最小量的 CPU 周期浪费,也能对其性能产生巨大影响。
我们把 profiler 设置为捕获 CPU traces,Duration 设为 50 秒。拿到渲染后的 profile 后,我们看到了这样的结果:
如前所述,最宽的矩形就是占用最多 CPU 时间的部分。其中一些并不让人意外,例如 decryptBlock 是 R2 在输出对象数据时进行解密,fillResponse 只是搬运字节。这些函数没有明显的优化空间。
由于有些矩形不太容易看清,查看表格视图是个好主意。按 Samples 排序,我们注意到了 genericR2JsonReplacer:
这个函数占了超过 5% 的 CPU 时间,但引起我们注意的是它在递归调用自己。这看起来像是在做无用功,事实也确实如此。
尽管这个 replacer 会被 JSON.stringify 在 JSON 树的每个节点上调用,但 replacer 自身也在遍历这棵树。这意味着一个嵌套五层的值会被处理五次。修复这个问题后,我们消除了重复工作,使 genericR2JsonReplacer 函数快了 2.7 倍。
我们在 profile 中查看的第二个候选目标是对 metrics 的重复调用。
查看代码时,我们看到了类似这样的内容:
这个 metrics 调用足够重,仅仅一次调用就占了 profile 中 1% 的 CPU 时间。因此把第一次调用的结果存入变量、不再重复调用,为我们节省了相当一部分 CPU 时间。
我们如何在内部使用 Worker profiling 修复内存问题
在内部,我们有一个 Worker 存在内存问题。P999 内存约为 133 MB,而 Worker 的内存限制是 128 MB。这导致该 Worker 频繁被驱逐,并报出“Exceeded Memory”错误。
上图是“Errors by invocation status”图表的截图,可以看到“Exceeded Memory”错误的数量明显下降,这是通过 profiling 发现并修复问题后的结果。
困难之处在于找出这些错误的成因。错误和指标都指向内存是问题根源,但没有说明是代码的哪一部分导致的。
团队对生产环境中运行的一个 Worker 做了 heap profile,并用 pprof 打开。他们发现其中的 Prometheus 代码大约占 profile 中分配量的 66.7%。这段代码本应被禁用,但仍有相当一部分在运行,从而造成了问题。由于它对 Worker 中大量代码路径进行了埋点,最终占用了大量内存,尽管收集到的数据实际上从未离开过 Worker。
这段代码之所以没有被怀疑是内存问题的元凶,是因为大家以为它已经被禁用了。profiler 显示这段代码只是被部分禁用,内存开销仍按完全启用时一样在付出。
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。