Skip to content

Commit

Permalink
update: title & reference linking
Browse files Browse the repository at this point in the history
  • Loading branch information
nswbmw committed Mar 10, 2018
1 parent 79054c5 commit b645e02
Show file tree
Hide file tree
Showing 33 changed files with 200 additions and 150 deletions.
8 changes: 4 additions & 4 deletions 1.1 perf + FlameGraph.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- Mac OS X:DTrace and Instruments。
- Windows:Xperf.exe。

## perf
## 1.1.1 perf

[perf_events](http://www.brendangregg.com/linuxperf.html)(简称 perf)是 Linux Kernal 自带的系统性能分析工具,能够进行函数级与指令级的热点查找。它基于事件采样原理,以性能事件为基础,支持针对处理器相关性能指标与操作系统相关性能指标的性能剖析,常用于查找性能瓶颈及定位热点代码。

Expand Down Expand Up @@ -139,7 +139,7 @@ $ ~/FlameGraph/stackcollapse-perf.pl --kernel < ~/perf.stacks | ~/FlameGraph/fla
ab 压测用了 30s 左右,浏览器打开 flamegraph.svg,截取关键的部分如下图所示:
![](./assets/1.1.1.png)

## 理解火焰图
## 1.1.2 理解火焰图

火焰图含义:

Expand Down Expand Up @@ -189,7 +189,7 @@ svg 火焰图的其他小技巧如下:
1. 单击任意一个小块可以展开,即被单击的小块宽度变宽,它的子函数也按比例变宽,方便查看。
2. 可单击 svg 右上角的 search 按钮进行搜索,被搜索的关键词会高亮显示,在有目的查找某个函数时比较有用。

## 红蓝差分火焰图
## 1.1.3 红蓝差分火焰图

虽然我们有了火焰图,但要处理性能回退问题,还需要在修改代码前后的火焰图之间,不断切换和对比,来找出问题所在,很不方便。于是 [Brendan D. Gregg](http://www.brendangregg.com/index.html) 又发明了红蓝差分火焰图(Red/Blue Differential Flame Graphs)。

Expand Down Expand Up @@ -247,7 +247,7 @@ $ ./FlameGraph/difffolded.pl perf_after.folded perf_before.folded | ./FlameGraph

总之,红蓝差分火焰图可能只在代码变化不大的情况下使用时效果明显,代码变化多了效果可能就不明显了。

## 参考链接
## 1.1.4 参考链接

- https://yunong.io/2015/11/23/generating-node-js-flame-graphs/
- http://www.brendangregg.com/perf.html
Expand Down
10 changes: 5 additions & 5 deletions 1.2 v8-profiler.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
我们知道 Node.js 是基于 V8 引擎的,V8 暴露了一些 profiler API [v8-profiler](https://github.com/node-inspector/v8-profiler) 收集一些运行时数据(例如:CPU 和内存)。本节将介绍如何使用 v8-profiler 分析 CPU 的使用情况。

## v8-profiler
## 1.2.1 使用 v8-profiler

创建测试代码:

Expand Down Expand Up @@ -79,7 +79,7 @@ $ ab -c 20 -n 2000 "http://localhost:3000/encrypt?password=123456"

这个 JSON 对象记录了函数调用栈、路径、时间戳和其他一些信息,samples 节点数组与 timestamps 节点数组中的时间戳是一一对应的,并且 samples 节点数组中的每一个值,其实对应了 head 节点的深度优先遍历 ID。这里我们不深究每个字段的含义,先来看看如何可视化这些数据。

## 1. Chrome DevTools
## 1.2.2 方法 1——Chrome DevTools

Chrome 自带了分析 CPU profile 日志的工具。打开 Chrome -> 调出开发者工具(DevTools) -> 单击右上角三个点的按钮 -> More tools -> JavaScript Profiler -> Load,加载刚才生成的 cpuprofile 文件。左上角的下拉菜单可以选择如下三种模式:

Expand All @@ -99,7 +99,7 @@ Chrome 自带了分析 CPU profile 日志的工具。打开 Chrome -> 调出开

**可以看出**:我们定位到了 encryptRouter 这个路由,并且这个路由中 exports.pbkdf2Sync 占据了绝大部分 CPU 时间。

## 2. 火焰图
## 1.2.3 方法 2——火焰图

我们也可以用火焰图来展示 cpuprofile 数据。首先全局安装 flamegraph 这个模块:

Expand All @@ -119,7 +119,7 @@ $ flamegraph -t cpuprofile -f cpuprofile-xxx.cpuprofile -o cpuprofile.svg

**可以看出**:我们定位到了 app.js 的第 8 行,即 encryptRouter 这个路由,并且这个路由中 exports.pbkdf2Sync 占据了绝大部分 CPU 时间。

## 3. v8-analytics
## 1.2.4 方法 3——v8-analytics

[v8-analytics](https://github.com/hyj1991/v8-analytics) 是社区开源的一个解析 v8-profiler 和 heapdump 等模块生成的 CPU 和 heap-memory 日志的工具。它提供以下功能:

Expand Down Expand Up @@ -147,7 +147,7 @@ $ va timeout cpuprofile-xxx.cpuprofile 200 --only

**可以看出**:我们依然能够定位到 encryptRouter 和 exports.pbkdf2Sync。

## 参考链接
## 1.2.5 参考链接

- https://developers.google.com/web/tools/chrome-devtools/rendering-tools/js-execution
- http://www.ebaytechblog.com/2016/06/15/igniting-node-js-flames/
Expand Down
16 changes: 8 additions & 8 deletions 2.1 gcore + llnode.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Core & Core Dump
## 2.1.1 Core & Core Dump

在开始之前,我们先了解下什么是 Core 和 Core Dump。

Expand Down Expand Up @@ -35,7 +35,7 @@ $ ulimit -c unlimited

![](./assets/2.1.1.jpg)

## [gcore](http://man7.org/linux/man-pages/man1/gcore.1.html)
## 2.1.2 [gcore](http://man7.org/linux/man-pages/man1/gcore.1.html)

使用 gcore 可以不重启程序而 dump 出特定进程的 core 文件。gcore 使用方法如下:

Expand All @@ -45,7 +45,7 @@ $ gcore [-o filename] pid

在 Core Dump 时,默认会在执行 gcore 命令的目录生成 core.\<PID\> 的文件。

## llnode
## 2.1.3 llnode

什么是 llnode?

Expand Down Expand Up @@ -93,7 +93,7 @@ $ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 6DA62DE462C7DA6D

--recv-keys 后面跟的是前面报错提示的 PUBKEY。

## 测试 Core Dump
## 2.1.4 测试 Core Dump

下面用一个典型的全局变量缓存导致的内存泄漏的例子来测试 llnode 的用法。代码如下:

Expand Down Expand Up @@ -131,7 +131,7 @@ $ sudo gcore `pgrep -n node`

生成 core.2763 文件。

## 分析 Core 文件
## 2.1.5 分析 Core 文件

使用 lldb 加载刚才生成的 Core 文件:

Expand Down Expand Up @@ -224,7 +224,7 @@ Core file '/home/nswbmw/test/./core.2763' (x86_64) was loaded.

**小提示**: `v8 i``v8 inspect` 的缩写,`v8 p``v8 print` 的缩写。

## --abort-on-uncaught-exception
## 2.1.6 --abort-on-uncaught-exception

在 Node.js 程序启动时添加 --abort-on-uncaught-exception 参数,当程序 crash 的时候,会自动 Core Dump,方便 “死后验尸”。

Expand Down Expand Up @@ -271,11 +271,11 @@ Core file '/home/nswbmw/test/./core' (x86_64) was loaded.
9440 126368
```

## 总结
## 2.1.7 总结

我们的测试代码很简单,没有引用任何第三方模块,如果项目较大且引用的模块较多,则 `v8 findjsobjects` 的结果将难以甄别,这个时候可以多次使用 gcore 进行 Core Dump,对比发现增长的对象,再进行诊断。

## 参考链接
## 2.1.8 参考链接

- http://www.cnblogs.com/Anker/p/6079580.html
- http://www.brendangregg.com/blog/2016-07-13/llnode-nodejs-memory-leak-analysis.html
Expand Down
8 changes: 5 additions & 3 deletions 2.2 heapdump.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 2.2.1 使用 heapdump

[heapdump](https://github.com/bnoordhuis/node-heapdump) 是一个 dump V8 堆信息的工具。[v8-profiler](https://github.com/node-inspector/v8-profiler) 也包含了这个功能,这两个工具的原理都是一致的,都是 v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(title, control),但是 heapdump 的使用简单些。下面我们以 heapdump 为例讲解如何分析 Node.js 的内存泄漏。

这里以一段经典的内存泄漏代码作为测试代码:
Expand Down Expand Up @@ -51,7 +53,7 @@ heapdump-100427359.61348.heapsnapshot
heapdump-100438986.797085.heapsnapshot
```

## Chrome DevTools
## 2.2.2 Chrome DevTools

我们使用 Chrome DevTools 来分析前面生成的 heapsnapshot 文件。调出 Chrome DevTools -> Memory -> Load,按顺序依次加载前面生成的 heapsnapshot 文件。单击第 2 个堆快照,在左上角有个下拉菜单,有如下 4 个选项:

Expand Down Expand Up @@ -88,15 +90,15 @@ heapdump-100438986.797085.heapsnapshot

**小提示**:如果背景色是黄色的,则表示这个对象在 JavaScript 中还存在引用,所以可能没有被清除。如果背景色是红色的,则表示这个对象在 JavaScript 中不存在引用,但是依然存活在内存中,一般常见于 DOM 对象,它们存放的位置和 JavaScript 中的对象还是有不同的,在 Node.js 中很少遇见。

## 对比快照
## 2.2.3 对比快照

切换到 Comparison 视图下,可以看到一些 #New、#Deleted、#Delta 等属性,+ 和 - 代表相对于比较的堆快照而言。我们对比第 2 个快照和第 1 个快照,如下所示:

![](./assets/2.2.4.png)

**可以看出**:(string) 增加了 10 个,每个 string 大小为 10000024 字节。

## 参考链接
## 2.2.4 参考链接

- https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156
- https://www.zhihu.com/question/56806069
Expand Down
10 changes: 6 additions & 4 deletions 2.3 memwatch-next.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 2.3.1 使用 memwatch-next

[memwatch-next](https://github.com/marcominetti/node-memwatch)(以下简称 memwatch)是一个用来监测 Node.js 的内存泄漏和堆信息比较的模块。下面我们以一段事件监听器导致内存泄漏的代码为例,讲解如何使用 memwatch。

测试代码如下:
Expand Down Expand Up @@ -120,7 +122,7 @@ $ ab -c 1 -n 5 http://localhost:3000/

**可以看出**:Node.js 已经警告我们事件监听器超过了 11 个,可能造成内存泄露。连续 5 次内存增长触发 leak 事件打印出增长了多少内存(bytes)和预估每小时增长多少 bytes。

## Heap Diffing
## 2.3.2 Heap Diffing

memwatch 有一个 HeapDiff 函数,用来对比并计算出两次堆快照的差异。修改测试代码如下:

Expand Down Expand Up @@ -172,7 +174,7 @@ memwatch.on('leak', (info) => {

**可以看出**:内存由 4.51mb 涨到了 8.52mb,其中 Closure 和 Array 涨了绝大部分,而我们知道注册事件监听函数的本质就是将事件函数(Closure)push 到相应的数组(Array)里。

## 结合 heapdump
## 2.3.3 结合 heapdump

memwatch 在结合 heapdump 使用时才能发挥更好的作用。通常用 memwatch 监测到发生内存泄漏,用 heapdump 导出多份堆快照,然后用 Chrome DevTools 分析和比较,定位内存泄漏的元凶。

Expand Down Expand Up @@ -218,9 +220,9 @@ heapdump-21126-1519545975702.heapsnapshot

**可以看出**:增加了 5 万个 leakEventCallback 函数,单击其中任意一个,可以从 Retainers 中看到更详细的信息,例如 GC path 和所在的文件等信息。

## 参考链接
## 2.3.4 参考链接

- [https://github.com/marcominetti/node-memwatch](https://github.com/marcominetti/node-memwatch)
- https://github.com/marcominetti/node-memwatch

上一节:[2.2 heapdump](https://github.com/nswbmw/node-in-debugging/blob/master/2.2%20heapdump.md)

Expand Down
10 changes: 8 additions & 2 deletions 2.4 cpu-memory-monitor.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
前面介绍了 heapdump 和 memwatch-next 的用法,但在实际使用时并不那么方便,我们总不能一直盯着服务器的状况,在发现内存持续增长并超过心里的阈值时,再手动去触发 Core Dump 吧?在大多数情况下发现问题时,就已经错过了现场。所以,我们可能需要 [cpu-memory-monitor](https://github.com/nswbmw/cpu-memory-monitor)。顾名思义,这个模块可以用来监控 CPU 和 Memory 的使用情况,并可以根据配置策略自动 dump CPU 的使用情况(cpuprofile)和内存快照(heapsnapshot)。

## cpu-memory-monitor
## 2.4.1 使用 cpu-memory-monitor

我们先来看看如何使用 cpu-memory-monitor,其实很简单,只需在进程启动的入口文件中引入以下代码:

Expand Down Expand Up @@ -59,7 +59,7 @@ require('cpu-memory-monitor')({

通常情况下,只使用其中一种就可以了。

## 源码解读
## 2.4.2 源码解读

cpu-memory-monitor 的源代码不过百余行,大体逻辑如下:

Expand Down Expand Up @@ -127,6 +127,12 @@ module.exports = function cpuMemoryMonitor(options = {}) {
2. 在传入 memory 配置时,用了 memwatch-next 额外监听了 leak 事件,也会 dump Memory,格式是 `leak-memory-${process.pid}-${Date.now()}.heapsnapshot`
3. 顶部引入了 heapdump,所以即使没有 memory 配置,也可以通过 `kill -USR2 <PID>` 手动触发 Memory Dump。

## 2.4.3 参考链接

- https://github.com/node-inspector/v8-profiler
- https://github.com/bnoordhuis/node-heapdump
- https://github.com/marcominetti/node-memwatch

上一节:[2.3 memwatch-next](https://github.com/nswbmw/node-in-debugging/blob/master/2.3%20memwatch-next.md)

下一节:[3.1 Promise](https://github.com/nswbmw/node-in-debugging/blob/master/3.1%20Promise.md)
25 changes: 15 additions & 10 deletions 3.1 Promise.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

以下 promise 均指代 Promise 实例。

## 题目一
## 3.1.1 题目一

```js
const promise = new Promise((resolve, reject) => {
Expand All @@ -27,7 +27,7 @@ console.log(4)

**解释**:Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。

## 题目二
## 3.1.2 题目二

```js
const promise1 = new Promise((resolve, reject) => {
Expand Down Expand Up @@ -64,7 +64,7 @@ promise2 Promise {

**解释**:promise 有 3 种状态:pending、fulfilled 或 rejected。状态改变只能是 pending->fulfilled 或者 pending->rejected,状态一旦改变则不能再变。上面的 promise2 并不是 promise1,而是返回的一个新的 Promise 实例。

## 题目三
## 3.1.3 题目三

```js
const promise = new Promise((resolve, reject) => {
Expand All @@ -90,7 +90,7 @@ then: success1

**解释**:构造函数中的 resolve 或 reject 只有在第 1 次执行时有效,多次调用没有任何作用,再次印证代码二的结论:promise 状态一旦改变则不能再变。

## 题目四
## 3.1.4 题目四

```js
Promise.resolve(1)
Expand All @@ -115,7 +115,7 @@ Promise.resolve(1)

**解释**:promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 在每次调用 .then 或者 .catch 时都会返回一个新的 promise,从而可以实现链式调用。

## 题目五
## 3.1.5 题目五

```js
const promise = new Promise((resolve, reject) => {
Expand Down Expand Up @@ -144,7 +144,7 @@ success 1007

**解释**:promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说,promise 内部状态一经改变,并且有了一个值,则后续在每次调用 .then 或者 .catch 时都会直接拿到该值。

## 题目六
## 3.1.6 题目六

```js
Promise.resolve()
Expand Down Expand Up @@ -174,7 +174,7 @@ then: Error: error!!!

因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 `return new Error('error!!!')` 等价于 `return Promise.resolve(new Error('error!!!'))`

## 题目七
## 3.1.7 题目七

```js
const promise = Promise.resolve()
Expand Down Expand Up @@ -204,7 +204,7 @@ process.nextTick(function tick () {
})
```

## 题目八
## 3.1.8 题目八

```js
Promise.resolve(1)
Expand All @@ -221,7 +221,7 @@ Promise.resolve(1)

**解释**:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

## 题目九
## 3.1.9 题目九

```js
Promise.resolve()
Expand Down Expand Up @@ -258,7 +258,7 @@ Promise.resolve()
})
```

## 题目十
## 3.1.10 题目十

```js
process.nextTick(() => {
Expand All @@ -285,6 +285,11 @@ setImmediate

**解释**:process.nextTick 和 promise.then 都属于 microtask,而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,以上代码本身(macrotask)在执行完后会执行一次 microtask。

## 3.1.11 参考链接

- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://promisesaplus.com/

上一节:[2.4 cpu-memory-monitor](https://github.com/nswbmw/node-in-debugging/blob/master/2.4%20cpu-memory-monitor.md)

下一节:[3.2 Async + Await](https://github.com/nswbmw/node-in-debugging/blob/master/3.2%20Async%20%2B%20Await.md)
Loading

0 comments on commit b645e02

Please sign in to comment.