Skip to content
master
Go to file
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

diat

npm npm npm test

[English Doc]

diat 是基于 inspector 模块(提供: cpuprofile, heapsnapshot, debug 等能力)用于协助 Node.js 进程进行问题诊断的 CLI 工具。可以将diat当成是具有更丰富特性的 node-inspect

索引

动机

在解决 Node.js 服务端应用中发生的问题的过程中,我们发现Node.js/V8 原生的 inspector 模块是解决各类问题最有效的工具(不考虑大量使用 addon 或排查其他底层 c/cpp 代码的情况),比如:用 cpuprofile 解决 cpu 使用率异常的问题;用 heapsnapshot 排查内存泄漏的问题等等;用Debugger 协议直接打 logpoint 甚至热更新代码来协助排查业务问题。

并且不少 Node.js 开发者都具有 web 开发的经验,也就是说开发者学习利用 Chrome Devtools 进行问题排查可能是成本的最低途径之一。

但在实践过程中仍然有一些问题困扰着我们,比如:

  • 有些线上问题偶发且难以追踪、复现,开启 inspector 重启应用后问题消失
  • 有些环境我们可以开启 inspector,但外网无法访问
  • 非业务性质的线上问题诊断本身是一个重要但低频的场景,相比之下要求各个业务线事先统一接入一套诊断工具的成本较高

因此我们期望 diat 针对线上问题诊断的场景,能作为一个开箱即用的工具,围绕 Node.js/V8 inspector 的能力缩短 V8 inspector 的使用成本。

node-inspect相比于其他工具有什么优点

相比于其他诊断工具,node-inspect 支持 node-inspect -p $PID 来对一个进程直接进行调试,这种模式的优点在于:

  • 开箱即用,无需应用事先接入。因为在使用时通常不需要重启进程,所以对于偶发或难以复现的问题排查会有所帮助。
  • 部分功能在进程的主线程阻塞时也可以工作,如V8 cpu profile。因为直接基于 inspector 协议,并且 Node.js 的 inspector server 是运行在独立的线程上。
  • 需要消耗额外资源的命令在工具退出后会关闭,从而尽可能少给进程带来额外的资源消耗。因此也更适合用在生产环境上。

而对于windows或是其他不能用 node-inspect -p $PID 的场合,则需要配合 --inspect 使用。

diat相比于node-inspect做了什么改动

diat 的代码本身就包含了一份改动过的 node-inspect 代码,通过 diat inspect -r 即可使用 node-inspect。相比于node-inspect,diat添加了更多功能和优化。一些与inspector server通信相关的优化包括:

  • 支持开启 worker_threads 的 inspector server 并进行调试。
  • 支持代理 inspector server 的服务到外部网络中,从而允许其他调试工具接入。
  • 退出后关闭 inspector server 释放9229端口,避免在有多个 Node.js 进程(或者说 V8 实例)的场景下 9229 端口被一个进程占用。

而基于node-inspect新增的特性则包括:

  • 除了生成 cpuprofile 和 heapsnapshot,还支持生成 heapprofile 和 heaptimeline 文件。
  • 支持 attachConsole 来输出主线程中 console 输出的内容。
  • 支持 setLogpoint() 来设置不会中断线程运行的 logpoint。logpoint 也是一种 breakpoint,所以可以通过 clearBreakpoint() 来清除。
  • 支持 getScripts() 返回scripts的数据(而不是打印出来),从而可以在通过js表达式筛选自己需要的脚本,用于进程的文件很多的情况。
  • 支持 source(scriptId) 返回一个js脚本完整的内容。

如果有适合放到 node-inspect 中的特性,我们会尝试提交PR到上游。

推荐的排查途径

  1. 如果环境允许外部网络访问,推荐直接开启 inspector server 并用 debugger 工具接入
  2. 否则再利用利用CLI提供的各类命令解决问题

安装

npm i diat -g && diat --help

Node.js版本支持

Node.js 8 Node.js 10 Node.js 12
版本支持 ⚠️ 基本支持,但会有些限制

使用场景介绍

你可以用下面的命令开启一个 Node.js 进程用于测试:

node -e "console.log(process.pid); setInterval(() => {}, 1000)"

inspect

inspect命令用来打开一个进程的 inspector 用于直接调试。通常如果你能打开 inspector 并访问到,大部分问题都可以通过 inspector 协议上的功能解决。

diat inspect -p <PID>

成功后会返回如下信息:

inspector service is listening on: 0.0.0.0:56324
or open the uri below on your Chrome to debug: devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=10.90.39.11:56324/408b7bca-1000-4c1f-a91e-de44d460e5ae
press ctrl/meta+c to exit

当看到这样的信息后,你可以用调试工具接入56324端口,注意0.0.0.0需要换成可访问到的公网 ip。你也可以直接用 Chrome 打开后面的devtools://url 用 Chrome devtools 连接进程的 inspector。

inspect命令是通过发送 SIGUSR1 信号让 Node.js 内置的代码开启 inspector 端口,详情见文档。但出于安全考虑 Node.js 是让 inspector 监听127.0.0.1ip 地址,也就是外网无法访问。diat 在这基础之上做了个 tcp 代理让外网可以访问到进程 inspector,也就是存在被恶意访问 inspector 的风险。因此需要仔细斟酌你的使用场景是否适用

排查结束用ctrl/meta+c退出 diat 进程后,diat 会关闭业务进程中的 inspector。如果 diat 进程异常退出没能关闭进程的 inspector 的话,因为 inspector 默认监听的是127.0.0.1端口,一般风险也不大。

关闭inspector

你可以通过下列命令手动关闭一个端口上的inspector server,从而释放对应的端口:

diat inspectstop -a 127.0.0.1:9229

inspectworker

目前社区缺少对 worker_threads 开启的线程进行调试的支持(ndb支持)。inspectworker命令可以用来打开线程的 inspector 进行调试:

diat inspectworker -p <PID>

进程可以通过 worker_threads 打开多个线程,所以接入成功后首先要选择我们想要 inspect 的线程:

? Choose a worker to inspect (Use arrow keys)
❯ Worker 2(id: 1) [file:///diat/packages/diat/
__tests__/test_process/thread_worker.js]
  Worker 1(id: 2) [file:///diat/packages/diat/
__tests__/test_process/thread_worker.js]

选择相应的线程后,diat 会打开对应线程的 inspector,后续使用方式同inspect命令,可以参照inspect中的描述。

因为目前 Node.js 对 worker_threads 中的 inspector 的支持有所缺失(或者说未来 worker_threads 的调试方式不一定是以 inspector 为主),所以目前 diat 打开线程中的 inspector 后无法关闭。

repl

前面介绍了用 inspectinspectworker 打开 inspector 的方式,但在一些环境中我们并不能用外部 debugger 接入,比如:网络隔离的情况。这种情况下我们可以利用 -r 配置在命令行上进行调试,如:

diat inspect -p <PID> -r

成功后输入 help 查看 node-inspect 支持的命令。关于 node-inspect 的详细信息可以查看文档:

metric

metric命令用于查看进程占用的资源:

diat metric -p <PID>

开启后会展示 cpu、memory 和 uv 相关的一些基础数据:

[cpu] load(user): 0.00032 load(system): 0.000068
[memory] rss: 29.78MB heapTotal: 4.18MB heapUsed: 2.33MB external: 873.74KB
[uv] handle: 3, request: 0, latency: 5ms

数据每隔 2s 进行一次更新。

cpuprofile

cpuprofile命令用于让进程进行 cpu prfile,从而生成.cpuprofile 文件记录一段时间内 js 中的函数执行情况。cpu profile 可以帮助我们排查 cpu 使用率过高的问题,或是用于协助进行性能分析:

diat cpuprofile -p <PID>

当.cpuprofile 文件生成成功后,diat 会返回文件所在的位置:

profiling...
cpuprofile generated at: /diat_90504_1584018222518.cpuprofile

你可以在 Chrome Devtools 中的 Profiler 面板中打开.cpuprofile 文件进行分析。关于 cpu profile 的使用说明可以参考 Chrome Devtools 的官方文档

cpuprofile 支持配置如下参数:

  • --duration 表示采样的时间,默认为 5000ms。
  • --interval 表示采样间隔,默认为 1000us。采样间隔越小,则 cpuprofile 越准确,但需要进程额外消耗的资源越多。

cpuprofile 默认的文件格式是:./diat_$PID_$TS.cpuprofile,可通过--file改变指定生成文件的名称。

heapsnapshot

heapsnapshot命令用于生成.heapsnapshot 文件(堆快照)。heap snapshot 可以让我们了解进程中的内存占用细节,可以用来帮助我们排查内存泄漏问题:

diat heapsnapshot -p <PID>

你可以在 Chrome Devtools 中的 Memory 面板中打开.heapsnapshot 文件进行分析。关于 heap snapshot 的使用说明可以参考 Chrome Devtools 的官方文档

注意: 生成 heap snapshot 可能导致内存占用比较高的进程退出。因为没有指定参数的话,Node.js 进程在 64bit 机器上的 max-old-space-size 是 1.4GB 左右(Node.js 12 上的某个版本开始不再做这个默认的限制),而 heap snapshot 在生成的过程中会额外占用不少内存。此时继续增大内存占用会导致 V8 abort 或系统 OOM killer 关闭业务进程。对于这个问题暂时可能没有什么好的办法处理。

heapsnapshot 文件的默认格式是:./diat_$PID_$TS.heapsnapshot,可通过--file改变指定生成文件的名称。

其他V8内存相关的profile

除了 heapsnapshot,还可以直接通过diat生成 heapprofile 文件:

diat heapprofile -p <PID> -d 5000

和 heaptimeline 文件:

diat heaptimeline -p <PID> -d 5000

其中 heap profile 不会阻塞线程、对进程影响较小,而 heap timeline 则可以获取到生成对象所对应的代码。更多细节可查看官方文档

用perf生成火焰图

关于 perf + --perf-basic-prof 的用法也可以参考 diagnostics-flamegraph

cpu profile 对于排查 js 中与 cpu 相关的问题很有帮助。但是因为 cpu profile 是 V8 记录的 js 中的函数执行情况,所以对于 Node.js 底层代码中或 addon 代码中的函数调用情况,我们没办法通过 cpu profile 进行排查。如果发生这类问题我们需要 c/cpp 的 profile 进行排查。diat 对 Linux perf 方案提供额外的支持(可以参考node.js Flame Graphs on Linux)。

如果运行环境中已经安装了 perf 工具,则可以通过 perf 命令生成火焰图:

diat perf -p <PID>

最终会生成火焰图的svg文件(默认文件名称为: diat_perf.svg):

分步实现

diat perf 是对多个命令的封装,下面将分步介绍单个命令的使用方式。

首先通过perfbasicprof让 Node.js 进程生成.map 文件,.map 文件让 perf 能识别 js 的函数:

diat perfbasicprof -p <PID> -e true

接着让 perf 对进程进行 profile:

perf record -F 1000 -p <PID> -g -m 512B -- sleep 5

成功后我们会在当前文件下找到 perf.data 文件,文件中描述了这段时间内进程中的函数调用。用 perf 再次处理以获取可以直接读取的内容:

perf script > out.nodestacks01

操作结束后让 Node.js 停止生成.map 文件,减少资源消耗:

diat perfbasicprof -p <PID> -e false

如果我们想生成 Flame graph,可以perf2svg用做进一步处理生成 svg:

diat perf2svg -f out.nodestacks01

已知限制

1. 无法在 Windows 上直接传入 PID

因为 Windows 不支持给进程发送信号打开 inspector,所以也就没办法用-p选项传入 pid。可以考虑在启动 Node.js 时增加--inspect打开 inspector 并在 diat 的命令中用-a/--inspector_addr配置替代-p配置传入 inspector 的地址,比如:

node --inspect=9229 index.js

然后用 diat“打开”inspector(实际上做的事情只是在公共 ip 代理 inspector 服务):

diat inspect -a=127.0.0.1:9229

2. 9229 端口被占用后,无法通过 SIGUSR1 信号在默认的 9229 端口上打开 inspector

同样可以考虑在启动 Node.js 时增加--inspect=PORT指定一个可用的端口打开 inspector,并在 diat 的命令中用-a/--inspector_addr配置替代-p配置传入 inspector 的地址。

3. Node.js 8 版本中 inspector 的限制

Node.js 8 版本(目前已经退出 LTS)中的 inspector 有一些限制(这些问题不存在于 Node.js >= 10 的版本中),比如:

  1. 同一时间只能有一个inspector.Session接入

因为这个限制的存在,也就意味着如果已经打开并接入了进程的 inspector 端口,比如:用 Chrome Devtools 接入。那后续接入 inspector 的尝试都会失败,diat 也就没办法生效。而有些工具,比如pm2中的某些配置,比如:--max-memory-restart,也会打开进程的 inspector 并接入,所以这种情况下新的接入也会失败。

  1. 新版本的 node-inspect 使用了 Node.js 8 所不支持的api,如: require('url').fileURLToPath,会导致部分命令在 Node.js 8 中失败。

工作原理

基本工作原理

  1. 用 SIGUSR1 信号在 9229 端口上打开 inspector: https://nodejs.org/api/process.html#process_signal_events
  2. 利用 v8-inspector(node) 协议进行通信,可以执行对应的 inspector 功能,包括执行一段指定的代码: https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/

inspectworker 的工作原理

除了 v8-inspector(node) 的协议外,Node.js 内部还有定义一些协议用于 trace 和 worker_threads 等功能,定义见:https://github.com/nodejs/node/blob/master/src/inspector/node_protocol.pdl

这些协议中包括和线程中的 inspector 进行通信的部分。diat 通过该协议让线程打开 inspector,从而允许外部接入。

在Electron上使用

你可以对 Electron 的 Node.js 进程使用 diat。因为工作原理对 Node.js 进程是通用的,所以理论上 diat 对于这类应用都是生效的。

Contributing

项目使用 lerna 进行管理,git clone 项目后进行安装:

cd diat && npm install

packages 文件夹下的 linux-perf、node-inspect 和 stackvis-simplified 是对社区里面的项目进行了些改造的代码。diat 自身的代码主要在:

  • packages/diat:命令行工具的主要代码
  • packages/live-inspector:处理与 inspector 通信

提交代码前需要确保测试通过,并在 commit message 中描述对应的改动。测试可通过npm run test执行。

已知问题:目前因为 jest 在检测 worker_threads 开启的线程上似乎有些问题,可能导致测试无法自动退出。

License

MIT

You can’t perform that action at this time.