diff --git a/locale/zh-cn/docs/guides/diagnostics/index.md b/locale/zh-cn/docs/guides/diagnostics/index.md new file mode 100644 index 0000000000000..c85a09834a526 --- /dev/null +++ b/locale/zh-cn/docs/guides/diagnostics/index.md @@ -0,0 +1,15 @@ +--- +title: Diagnostics Guide +layout: docs.hbs +--- + +# 诊断指南 + +这些指南在[诊断工作小组](https://github.com/nodejs/diagnostics)中得以创建, +其目的旨在诊断一个程序中的相关问题时能给予相应的指导。 +本文档基于用户亲历组织而成。这些历程是一系列互相关联、一步步的措施。 +用户应当遵从他们,以便于根据用户上报的事件锁定问题。 + +以下即是相关的系列诊断指南: + +* [内存诊断相关](/zh-cn/docs/guides/diagnostics/memory) diff --git a/locale/zh-cn/docs/guides/diagnostics/memory/index.md b/locale/zh-cn/docs/guides/diagnostics/memory/index.md new file mode 100644 index 0000000000000..6aff6a7d11d91 --- /dev/null +++ b/locale/zh-cn/docs/guides/diagnostics/memory/index.md @@ -0,0 +1,67 @@ +--- +title: Memory Diagnostics +layout: docs.hbs +--- + +# 内存诊断相关 + +在本文档中,你讲学到如何调试与内存相关的一系列问题。 + +* [内存诊断相关](#memory) + * [内存溢出](#my-process-runs-out-of-memory) + * [相关症状](#symptoms) + * [副作用](#side-effects) + * [Debugging](#debugging) + * [My process utilizes memory inefficiently](#my-process-utilizes-memory-inefficiently) + * [Symptoms](#symptoms-1) + * [Side Effects](#side-effects-1) + * [Debugging](#debugging-1) + +## 内存耗尽 + +Node.js _(基于 JavaScript)_ 是一个带垃圾回收功能的语言,故内存泄露是可能是 +大量占用引起。通常 Node.js 的应用程序都是多终端、关键业务,以及长时间运行。因 +此如能提供一个行之有效的找出内存泄露原因的方法是必须的。 + +### 相关症状 + +用户观察到内存占用持续增长 _(或快活慢,持续数天乃至数周不等)_,然后发现进程崩溃并 +通过进程管理器重启进程。进程或许比之前运行得慢,重启也使得一些特定的请求失败 +_(负载均衡返回 502)_。 + +### 副作用 + +* 因为内存耗尽,故重启进程;相关请求也被丢弃。 +* 增长的 GC 活动导致 CPU 使用率越来越高,响应速度越来越慢。 + * GC 把事件循环机制阻塞住导致了速度变慢。 +* 增长的内存交换(由 GC 活动引起)使得进程变慢。 +* 没有足够的内存空间来存储一个堆快照。 + +### 调试 + +调试一个内存泄露的问题,我们需要看特定的对象占用了多少内存空间,以及什么变量占有了 +他们从而使得垃圾回收。为了使我们有效地调试,我们同时也需要了解变量随时间的分配模式。 + +* [使用堆剖析器](/en/docs/guides/diagnostics/memory/using-heap-profiler/) +* [使用堆快照](/en/docs/guides/diagnostics/memory/using-heap-snapshot/) +* [GC 跟踪](/en/docs/guides/diagnostics/memory/using-gc-traces) + +## 低效率内存使用 + +### 相关症状 + +应用程序占用的内存与我们的预期不符,我们观察到垃圾回收器的活动有所提升。 + +### 副作用 + +* 分页错误数持续增长。 +* 较高的 GC 活动以及 CPU 使用率。 + +### 调试 + +调试一个内存泄露的问题,我们需要看特定的对象占用了多少内存空间,以及什么变量占有了 +他们从而使得垃圾回收。为了使我们有效地调试,我们同时也需要了解变量随时间的分配模式。 + +* [使用堆剖析器](/en/docs/guides/diagnostics/memory/using-heap-profiler/) +* [使用堆快照](/en/docs/guides/diagnostics/memory/using-heap-snapshot/) +* [GC 跟踪](/en/docs/guides/diagnostics/memory/using-gc-traces) diff --git a/locale/zh-cn/docs/guides/diagnostics/memory/using-gc-traces.md b/locale/zh-cn/docs/guides/diagnostics/memory/using-gc-traces.md new file mode 100644 index 0000000000000..fbb5a0f326a6d --- /dev/null +++ b/locale/zh-cn/docs/guides/diagnostics/memory/using-gc-traces.md @@ -0,0 +1,201 @@ +--- +title: Memory Diagnostics - Using GC Trace +layout: docs.hbs +--- + +# 追踪垃圾回收 + +垃圾回收是如何工作的,实在有太多的东西需要学习。但有一点必须清楚:那便是当 GC 运行 +的时候,你的代码是不工作的。 + +或许你想知道垃圾回收运行的频率,以及需要运行多久,以及结果是什么。 + +## 如何运行垃圾回收追踪 + +你可以借助 `--trace_gc` 在控制台输出中看到垃圾回收追踪的信息情况。 + +```console +$ node --trace_gc app.js +``` + +或许你不想追踪运行在服务器上的整个进程生命周期里的那些信息,如果是这样的话,请从进程 +内部设定把它关闭,这样就不会再追踪了。 + +以下是如何追踪并打印持续一分钟的 GC 信息示例: + +```js +const v8 = require('v8'); +v8.setFlagsFromString('--trace_gc'); +setTimeout(() => { v8.setFlagsFromString('--notrace_gc'); }, 60e3); +``` + +### 使用 `--trace_gc` 检查追踪 + +你得到的垃圾收集器追踪信息看上去如以下样子: + +``` +[19278:0x5408db0] 44 ms: Scavenge 2.3 (3.0) -> 1.9 (4.0) MB, 1.2 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure + +[23521:0x10268b000] 120 ms: Mark-sweep 100.7 (122.7) -> 100.6 (122.7) MB, 0.15 / 0.0 ms (average mu = 0.132, current mu = 0.137) deserialize GC in old space requested +``` + +这里给出如何解析这些信息(以第二行数据举例): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
得到的数据对应解析含义
23521正在运行的进程号
0x10268db0独立内存地址 (JS 堆实例)
120自开始运行的时间(毫秒)
Mark-sweep类型 / GC 阶段
100.7GC 运行前占有内存(MB)
122.7GC 运行前总占有内存(MB)
100.6GC 运行后占有内存(MB)
122.7GC 运行后总占有内存(MB)
0.15 / 0.0
+ (average mu = 0.132, current mu = 0.137)
GC 所耗费时间(毫秒)
deserialize GC in old space requestedGC 原因
+ +## 使用“performance hooks” 追踪你的垃圾回收信息 + +在 Node.js,你可以使用[performance hooks][] 来跟踪你的垃圾回收信息。 + +```js +const { PerformanceObserver } = require('perf_hooks'); + +// Create a performance observer +const obs = new PerformanceObserver((list) => { + const entry = list.getEntries()[0]; + /* + The entry would be an instance of PerformanceEntry containing + metrics of garbage collection. + For example: + PerformanceEntry { + name: 'gc', + entryType: 'gc', + startTime: 2820.567669, + duration: 1.315709, + kind: 1 + } + */ +}); + +// Subscribe notifications of GCs +obs.observe({ entryTypes: ['gc'] }); + +// Stop subscription +obs.disconnect(); +``` + +### 借助“performance hooks”检查追踪信息 + +你可以在 [PerformanceObserver][] 的回调函数里得到诸如 [PerformanceEntry][] 的 GC +数据。举例说明: + +```ts +PerformanceEntry { + name: 'gc', + entryType: 'gc', + startTime: 2820.567669, + duration: 1.315709, + kind: 1 +} +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
属性名称对应解释
name进程名称。
entryType类型。
startTime进程的启动时间(高精度毫秒表示)。
duration持续运行时间(毫秒)。
kind垃圾回收的类型。
flags垃圾回收的其余信息。
+ +欲知更多详情,请查阅 +[performance hooks API文档][performance hooks]. + +## 使用追踪选项诊断内存问题的示例: + +A. 如何获取糟糕的内存分配的上下文信息? +1. 假定我们观察到旧内存持续增长。 +2. 但根据 GC 的负重来看,堆的最大值并未达到,但进程慢。 +3. 回看跟踪的数据,找出在 GC 前后总的堆占用量。 +4. 使用 `--max-old-space-size` 降低内存,使得总的内存堆更接近于极限值。 +5. 再次运行程序,达到内存耗尽。 +6. 该过程的日志将显示失败的上下文信息。 + +B. 如何确定在堆增长之时,存在内存泄露现象? +1. 假定我们观察到旧内存持续增长。 +2. 但根据 GC 的负重来看,堆的最大值并未达到,但进程慢。 +3. 回看跟踪的数据,找出在 GC 前后总的堆占用量。 +4. 使用 `--max-old-space-size` 降低内存,使得总的内存堆更接近于极限值。 +5. 再次运行程序,观察是否内存耗尽。 +6. 如果发生内存耗尽,尝试每次提升 10% 的堆内存,反复数次。 +如果之前的现象复现被观察到,这就能证明存在内存泄露。 +7. 如果不存在内存耗尽,就把内存堆固定在那个值——紧凑的堆减少了内存占用以及对内存压缩的延迟。 + +C. 如何断定是否存在太多次的垃圾回收,或者因为太多次垃圾回收造成一定的开销? +1. 回顾跟踪数据,尤其关注持续不断的 GC 发生时间隔的一系列数据。 +2. 回顾跟踪数据,尤其关注持续不断的 GC 发生时时间消耗的数据。 +3. 如果两次 GC 间隙时间小于 GC 所话费的时间,证明程序正处于严重缺乏内存。 +4. 如果两次 GC 间隙时间和所话费的时间都非常高,证明该程序应该用一个更小点的堆。 +5. 如果两次 GC 的时间远大于 GC 运行的时间,应用程序则相对比较健康。 + +[performance hooks]: https://nodejs.org/api/perf_hooks.html +[PerformanceEntry]: https://nodejs.org/api/perf_hooks.html#perf_hooks_class_performanceentry +[PerformanceObserver]: https://nodejs.org/api/perf_hooks.html#perf_hooks_class_performanceobserver diff --git a/locale/zh-cn/docs/guides/diagnostics/memory/using-heap-profiler.md b/locale/zh-cn/docs/guides/diagnostics/memory/using-heap-profiler.md new file mode 100644 index 0000000000000..294809859d8f0 --- /dev/null +++ b/locale/zh-cn/docs/guides/diagnostics/memory/using-heap-profiler.md @@ -0,0 +1,108 @@ +--- +title: Memory Diagnostics - Using Heap Profiler +layout: docs.hbs +--- + +# 使用内存堆剖析器 + +为了调试一个与内存相关的问题,我们需要观察特定对象占有了多少内存空间,以及哪些对象占用 +了他们触发了垃圾回收。为了有效的调试,我们也需要知道连续时间下我们的变量是如何在内存里 +分配的。 + +内存堆剖析器处于 V8 的顶部,能持续对内存分配进行快照。在这篇文章里我们将设计到内存的 +剖析,将使用: + +1. 时间轴的分配 +2. 采样内存堆剖析器信息 + +不同于[使用堆快照][]中所涉及到的内容,使用实时堆剖析意在了解一段特定时间下内存分配 +情况。 + +## 堆剖析器 - 时间轴的分配 + +堆剖析器与堆采样分析器相似,不同处在于它会跟踪每次的内存分配状况,它的开销也就 +高于堆采样分析器,所以不建议在生产环境中使用。 + +> 你可借助 [@mmarchini/observe][] 通过编码方式实现。 + +### 怎么用堆剖析器? + +启动应用程序: + +```console +node --inspect index.js +``` + +> `--inspect-brk` 对于脚本而言是较好的选项。 + +在 Chrome 中连接开发工具实例,然后: + +* 选择 `memory` 选项卡 +* 选择 `Allocation instrumentation timeline` +* 开始剖析 + +![堆剖析器步骤 1][heap profiler tutorial 1] + +然后堆剖析就开始运行了,在此我们强烈建议您运行示例,这样便于确认内存相关的问题。 +拿本例子而言,我们将使用 `Apache Benchmark` 工具弄出应用程序中的负载。 + +> 在这个示例中,我们假定堆剖析基于 Web 应用程序。 + +```console +$ ab -n 1000 -c 5 http://localhost:3000 +``` + +当负载全部请求完毕之后,请按“停止”按钮。 + +![堆剖析器步骤 2][heap profiler tutorial 2] + +然后针对内存分配情况看一下快照。 + +![堆剖析器步骤 3][heap profiler tutorial 3] + +请查阅下列有助于你了解关于更多内存相关术语的[链接部分](#usefull-links) 。 + +## 堆剖析的采样 + +对堆剖析器的采样是在一定时间内持续跟踪内存份分配状况,以及后备内存。由于采样基于低 +开销进行,所以它可以用在生产环境。 + +> 你可以借助 [`heap-profiler`][] 模块,通过编程方式实现此功能。 + +### 如何对堆剖析进行采样? + +启动应用程序: + +```console +$ node --inspect index.js +``` + +> `--inspect-brk` 对于脚本而言是较好的选项。 + +在 Chrome 中连接开发工具的实例,然后: + +1. 选择 `memory` 选项卡 +2. 选择 `Allocation sampling` +3. 开始剖析 + +![堆剖析器步骤 4][heap profiler tutorial 4] + +在负载产生后停止剖析器,它会自动生成一个基于堆栈跟踪的内存分配总结。你可以查找某时间 +间隔内函数的内存堆分配状况,可以参照下面的例子: + +![堆剖析器步骤 5][heap profiler tutorial 5] + +## 有帮助的相关链接: + +* https://developer.chrome.com/docs/devtools/memory-problems/memory-101/ +* https://github.com/v8/sampling-heap-profiler +* https://developer.chrome.com/docs/devtools/memory-problems/allocation-profiler/ + +[使用堆快照]: /zh-cn/docs/guides/diagnostics/memory/using-heap-snapshot/ +[@mmarchini/observe]: https://www.npmjs.com/package/@mmarchini/observe +[`heap-profiler`]: https://www.npmjs.com/package/heap-profile +[heap profiler tutorial 1]: /static/images/docs/guides/diagnostics/heap-profiler-tutorial-1.png +[heap profiler tutorial 2]: /static/images/docs/guides/diagnostics/heap-profiler-tutorial-2.png +[heap profiler tutorial 3]: /static/images/docs/guides/diagnostics/heap-profiler-tutorial-3.png +[heap profiler tutorial 4]: /static/images/docs/guides/diagnostics/heap-profiler-tutorial-4.png +[heap profiler tutorial 5]: /static/images/docs/guides/diagnostics/heap-profiler-tutorial-5.png diff --git a/locale/zh-cn/docs/guides/diagnostics/memory/using-heap-snapshot.md b/locale/zh-cn/docs/guides/diagnostics/memory/using-heap-snapshot.md new file mode 100644 index 0000000000000..80746426b612f --- /dev/null +++ b/locale/zh-cn/docs/guides/diagnostics/memory/using-heap-snapshot.md @@ -0,0 +1,144 @@ +--- +title: Memory Diagnostics - Using Heap Snapshot +layout: docs.hbs +--- + +# 使用内存堆快照 + +你可以在程序运行的同时为程序生成一个内存堆快照,然后把它加载到 [Chrome 开发工具][]中 +检查特定的变量,或者是占用内存的大小。你也可以持续性地比较多个快照来观察他们之间的不同。 + +## 警告! + +在创建快照的同时其它主线程中的工作都会停止。根据堆的上下文情况,有可能这种快照将持续超 +过一分钟之久。内存快照在内存中创建,所以可能占用两倍的内存,导致整个内存都被填满而使得 +应用程序崩溃。 + +如果你想在生产环境生成内存快照,请确认被快照的那个进程崩溃不会对你程序的应用产生影响。 + +## 如何生成快照? + +### 获取内存快照 + +1. 通过检查器 +2. 通过外部信号以及命令行命令符 +3. 通过在进程内部调用 writeHeapSnapshot 函数 +4. 通过检查协议 + +#### 1. 在检查器中使用内存剖析工具 + +> 该方法适用于所有维护活跃版本的 Node.js + +使用 `--inspect` 命令符运行 node 程序并打开检查器。 +![打开检查器][open inspector image] + +最简便获取堆快照的方法就是把检查器和你本地正在运行的进程进行连接,切换到“内存”标签页, +选择采集内存快照。 + +![采集内存快照][take a heap snapshot image] + +#### 2. 使用 `--heapsnapshot-signal` 命令符 + +> 此方法在 Node.js v12.0.0 或之后版本使用 + +你可以使用命令行的命令符启动 node 使得对信号做出反应并开始创建内存快照。 + +``` +$ node --heapsnapshot-signal=SIGUSR2 index.js +``` + +``` +$ ps aux +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +node 1 5.5 6.1 787252 247004 ? Ssl 16:43 0:02 node --heapsnapshot-signal=SIGUSR2 index.js +$ kill -USR2 1 +$ ls +Heap.20190718.133405.15554.0.001.heapsnapshot +``` + +预知详情,请查阅最新关于[内存快照信号量标志符][]的文档。 + +#### 3. 使用 `writeHeapSnapshot` 函数 + +> Node.js 版本至少是 v11.13.0 或之后 +> 对于旧版本的 Node.js,可以借助 [heapdump 包][] 来使用 + +如果你想从一个正在运行的进程里提取快照,这如同在服务器上运行的程序一样,你可以这样做: + +```js +require('v8').writeHeapSnapshot(); +``` + +请查阅 [writeHeapSnapshot 文档][] 了解文件名的可用选项。 + +在不停掉进程的前提下你需要有一个方式来生成快照,建议在 http handler 里调用,亦或是从操作 +系统中对某个信号量做出反应。但你需小心一点:不要暴漏触发生成快照的 http 终端地址,它不应该 +被其他人直接访问。 + +对于 v11.13.0 之前的 Node.js 版本,请借助 [heapdump 包][]来实现。 + +#### 4. 借助检查器协议触发内存堆快照 + +“检查器协议” 可被用来从进程外部触发生成内存堆快照。 + +通过 Chromium 运行实际的检查器调用 API 并不是必要的方式。 + +以 bash 方式,借助 `websocat` 和 `jq`,给出触发内存快照的方式: + +```bash +#!/bin/bash +set -e + +kill -USR1 "$1" +rm -f fifo out +mkfifo ./fifo +websocat -B 10000000000 "$(curl -s http://localhost:9229/json | jq -r '.[0].webSocketDebuggerUrl')" < ./fifo > ./out & +exec 3>./fifo +echo '{"method": "HeapProfiler.enable", "id": 1}' > ./fifo +echo '{"method": "HeapProfiler.takeHeapSnapshot", "id": 2}' > ./fifo +while jq -e "[.id != 2, .result != {}] | all" < <(tail -n 1 ./out); do + sleep 1s + echo "Capturing Heap Snapshot..." +done + +echo -n "" > ./out.heapsnapshot +while read -r line; do + f="$(echo "$line" | jq -r '.params.chunk')" + echo -n "$f" >> out.heapsnapshot + i=$((i+1)) +done < <(cat out | tail -n +2 | head -n -1) + +exec 3>&- +``` + +以下是与检查器协议一起使用的内存分析工具的详尽列表: + +* [适用于 Node.js 的 OpenProfiling][openprofiling] + +## 如何借助内存堆快照发现内存泄露? + +为找到内存泄露,必须先对比两个快照。但必须先确保这些快照之间的区别未包含不需要的信息。 +遵循下列步骤,你就可以创建一个干净的快照。 + +1. 让进程加载完全部的资源,完成初始化启动。这最多只需要若干秒就可完成。 +2. 启动你所怀疑造成内存泄露的功能性部分,可能一开始它会造成一些非内存泄露的分配。 +3. 开始生成快照。 +4. 继续运行此功能性部分一段时间,最好在此期间不要运行其它任何东西。 +5. 再次生成另一次快照,两次生成快照之间的差别应该包含大部分内存泄露的情况。 +6. 打开 Chromium 或 Chrome 的开发工具,切换到 *内存* 标签。 +7. 先加载第一次(旧的)生成的内存快照,然后是第二次(较新的)。[工具中的加载按钮][load button image]部分。 +8. 选择第二次(较新)的快照,在下拉框中从 *总结* 切换至 *比较*。![比较下拉选项][comparison image] +9. 在最底部的面板中寻找他们之间较大的差异部分,浏览那些导致形成这些原因的相关引用。 + +你可以通过[内存堆快照练习][heapsnapshot exercise]来锻炼你捕获快照以及寻找内存泄露的能力。 + +[open inspector image]: /static/images/docs/guides/diagnostics/tools.png +[take a heap snapshot image]: /static/images/docs/guides/diagnostics/snapshot.png +[内存快照信号量标志符]: https://nodejs.org/api/cli.html#--heapsnapshot-signalsignal +[heapdump 包]: https://www.npmjs.com/package/heapdump +[writeHeapSnapshot 文档]: https://nodejs.org/api/v8.html#v8_v8_writeheapsnapshot_filename +[openprofiling]: https://github.com/vmarchaud/openprofiling-node +[load button image]: /static/images/docs/guides/diagnostics/load-snapshot.png +[comparison image]: /static/images/docs/guides/diagnostics/compare.png +[heapsnapshot exercise]: https://github.com/naugtur/node-example-heapdump +[Chrome 开发工具]: https://developer.chrome.com/docs/devtools/ diff --git a/locale/zh-cn/docs/guides/index.md b/locale/zh-cn/docs/guides/index.md index 283c9d6d97266..2f32ea8ab47a8 100644 --- a/locale/zh-cn/docs/guides/index.md +++ b/locale/zh-cn/docs/guides/index.md @@ -13,6 +13,7 @@ layout: docs.hbs * [诊断 - 火焰图](/zh-cn/docs/guides/diagnostics-flamegraph/) * [将 Node.js 网络应用安装到 Docker 中](/zh-cn/docs/guides/nodejs-docker-webapp/) * [迁移到安全的 Buffer 构造函数中](/zh-cn/docs/guides/buffer-constructor-deprecation/) +* [诊断 —— 用户亲历](/zh-cn/docs/guides/diagnostics/) ## Node.js 核心概念