Node.jsの使用メモリを観測する方法

# はじめに

メモリサイズの取得方法を列挙するのみで、メモリサイズの解析に関する詳細には立ち入りません。

# お品書き
- [Google ChromeのDevToolsでデバッグ](https://nodejs.org/api/debugger.html#debugger_v8_inspector_integration_for_node_js)
- [process.memoryUsage()](https://nodejs.org/api/process.html#process_process_memoryusage)

# 参考

- [Guide: How To Inspect The Memory Usage in Node.Js](https://www.valentinog.com/blog/memory-usage-node-js/)
- [Node.jsアプリケーションを、Google ChromeのDevToolsでデバッグする - CLOVER](http://d.hatena.ne.jp/Kazuhira/touch/20180212/1518433536)
- [5 Commands to Check Memory Usage on Linux | Linux.com | The source for Linux information](https://www.linux.com/blog/5-commands-check-memory-usage-linux)

# 本文
## 個別のオブジェクトの使用サイズを見る

特定のオブジェクトがメモリを大きく使うことを想定している時に、実際にどれぐらい使っているのか確かめたいことがあります。次のような用途です。

- 実際の使用量を元に、負荷がn倍になった時の利用メモリ量の予想
- メモリ使用量のチューニングの効果測定

例えば、`itemMap`というオブジェクトが、大量の値を登録している[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)インスタンスを持つ場合を考えます。`HEAP SNAPSHOT`を取得して、Mapのインスタンスから大きそうなインスタンスを探すと、該当するMapインスタンスを見つけることができます。

<img width="1274" alt="スクリーンショット 2018-05-13 21.45.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/15964/1333430f-86a3-b25e-fb28-ac1434bf7bd4.png">

- Shallow Size: インスタンス単体のサイズ。Mapや配列であればインスタンス単体のサイズより中身のサイズが知りたいです。この値にはあまり興味がありません
- Retained Size: ずばり、Mapや配列の中身のサイズの合計

`Retained Size`を算出してくれるのが、めちゃくちゃ便利です。

[HEAP SNAPSHOTを取得して、Mapのインスタンスから大きそうなインスタンスを探すYouTube動画](https://youtu.be/g0uCDO2Iegk)を用意しました。DevToolsの使い方がわからない方はどうぞ。


Google ChromeのDevToolsでのDebugのはじめ方は、[Node.jsアプリケーションを、Google ChromeのDevToolsでデバッグする - CLOVER](http://d.hatena.ne.jp/Kazuhira/touch/20180212/1518433536)を読んでください。



## 全体的なメモリ使用量の傾向を見る
メモリリークの有無を知りたい時に知りたい情報です。

### process.memoryUsage()

Node.jsの標準API [process.memoryUsage()](https://nodejs.org/api/process.html#process_process_memoryusage)を使います。
特に追加のリソースは必要ないので、ソースを変更できる場合は、一番手軽です。

このAPIを使って、デバッグプリントします。
例えば、次のソースコードを追加すれば、5分おきにメモリ使用量を標準出力に出力します。

```js
// 定期的にメモリ使用量を吐き出す
setInterval(() => {
  const used = process.memoryUsage()
  const messages = []
  for (let key in used) {
    messages.push(`${key}: ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`)
  }
  console.log(new Date(), messages.join(', '))
}, 5 * 60 * 1000)
```

ログの例です。

```
2018-05-13T11:39:46.293Z 'rss: 315.79 MB, heapTotal: 253.01 MB, heapUsed: 239.41 MB, external: 0.22 MB'
2018-05-13T11:44:46.308Z 'rss: 355.88 MB, heapTotal: 267.43 MB, heapUsed: 254.4 MB, external: 0.31 MB'
2018-05-13T11:49:46.323Z 'rss: 324.54 MB, heapTotal: 256.04 MB, heapUsed: 242.81 MB, external: 0.23 MB'
2018-05-13T11:54:46.338Z 'rss: 359.49 MB, heapTotal: 271.09 MB, heapUsed: 257.61 MB, external: 0.36 MB'
2018-05-13T11:59:46.353Z 'rss: 346.75 MB, heapTotal: 262.74 MB, heapUsed: 248.15 MB, external: 0.28 MB'
2018-05-13T12:04:46.368Z 'rss: 316.83 MB, heapTotal: 252.87 MB, heapUsed: 239.29 MB, external: 0.2 MB'
```

- rss: プロセス全体が使っているメモリ使用量。topコマンドなどで出力させる値
- heapTotal: プログラム中で作成したオブジェクトが使用しているメモリの総量

### memwatch-next
`process.memoryUsage()`では、その瞬間の「使用しているオブジェクトの総メモリ量」は取れません。`process.memoryUsage()`は、要求された瞬間のメモリ使用量を取得しますが、その瞬間がGarbage Collectionの実行直後かはわかりません。つまり、`process.memoryUsage()`の出力する値には、すでに使っていないが解放されていないGarbageオブジェクトが含まれている可能性があります。

この問題に対応するためのnpmパッケージが[memwatch-next](https://www.npmjs.com/package/memwatch-next)です。Node.jsのC拡張を使って、Garbage Collectionが走ったタイミングでイベントを送出します。このイベントを使うことで、正確な「使用しているオブジェクトの総メモリ量」が取得できます。これを継続して取得し、増加傾向があればメモリリークの有無を検知できます。

説明を読んだだけで実際は使っていません。

# その他の参考
- [メモリに関する用語  |  Tools for Web Developers  |  Google Developers](https://developers.google.com/web/tools/chrome-devtools/memory-problems/memory-101)
- [node.jsでメモリ使用量を取得する方法あれこれ - Qiita](https://qiita.com/shouta-dev/items/09d7055329710684d9c7)
- [Node.jsの開発時に必要な情報 - Qiita](https://qiita.com/mima_ita/items/1cc6b2fb938d5a1fc3aa#plato%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89%E3%83%A1%E3%83%88%E3%83%AA%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AE%E5%8F%8E%E9%9B%86)
- [Node.jsアプリケーションのデバッグはVisual Studio Codeだと簡単にできるよ - Qiita](https://qiita.com/keitarou/items/298377ae34ec1b3df448)