Node.jsのCoreにレポート機能が入った

2019 / 01 / 24

Edit
🚨 This article hasn't been updated in over a year
💁‍♀️ This post was copied from Hatena Blog
node-report: meld into core by gireeshpunathil · Pull Request #22712 · nodejs/node Checklist make -j4 test (UNIX), or vcbuild test (Windows) passes tests and/or benchmarks are inc...

結構前から進行してて入れたいねーってなってたらこんなにかかってしまいました。

semver-minor なので、次のリリースで入るでしょう。

目的

主な目的としては、何かのエラーで例外をキャッチしたときにその時の詳細情報をコア側から提供し、原因特定の手助けをします。

node-report

node-report とは、公式が出しているレポーターです。

主に以下の情報を提供します。

  • JavaScript Stack Trace
  • Native Stack Trace
  • JavaScript Heap and Garbage Collector
  • Resource Usage
  • Node.js libuv Handle Summary
  • System Information

ネイティブのスタックトレース、ヒープ統計情報、プラットフォーム情報、リソース使用状況などが人間が読める形でレポート化されます。 また、未処理の例外や致命的なエラーにも対応します。

以下は、現在 npm に置かれているリンクです。 (今後はコアに入りますが、npm にも publish されるかは自分は知りません)

node-report Diagnostic Report for Node.js. Latest version: 2.2.11, last published: 2 years ago. Start using node...

node-report 単体で動かす場合は以下のように動かします。

$ npm i node-report
$ node -r node-report test.js
$ cat node-report.20171105.202142.9066.001.txt
================================================================================
==== Node Report ===============================================================
...
Node.js version: v9.0.0
...
================================================================================
==== JavaScript Stack Trace ====================================================
...
================================================================================
==== Native Stack Trace ========================================================
...
================================================================================
==== JavaScript Heap and Garbage Collector =====================================
...
================================================================================
==== Resource Usage ============================================================
...
================================================================================
==== Node.js libuv Handle Summary ==============================================
...
================================================================================
==== System Information ========================================================
...
================================================================================

注意点として、node-report はテキスト形式でしたが、コアでは json が採用されています。

使い方

ここからは、core から使う方法を説明します。 現在、reportは stability:1 なので、実行するときには実験中フラグ(--experimental-report)が必要となります。

使用方法としては、CLI から指定して使う方法とコードから呼ぶ方法があります。

CLI

$ node --experimental-report --diagnostic-report-uncaught-exception \
  --diagnostic-report-on-fatalerror --diagnostic-report-on-signal \
  --diagnostic-report-signal=SIGUSR2  --diagnostic-report-filename=./report.json \
  --diagnostic-report-directory=/home/nodeuser --diagnostic-report-verbose index.js

--diagnostic-report-uncaught-exception

uncaught-exception をトリガーにします。

--diagnostic-report-on-signal

実行中の Node.js プロセスへの指定されたシグナルを受信したときにレポートを生成します。 デフォルトはSIGUSR2です。 この機能では、レポートを他のプログラムから起動する必要がある場合に便利で、モニタリングアプリケーションはこの機能を利用して定期的にレポートを収集することが可能です。

--diagnostic-report-on-fatalerror

アプリケーションの終了につながる致命的なエラー(e.g メモリ不足等の Node.js ランタイム内の内部エラー)をレポートをトリガーにします。

--diagnostic-report-directory

出力先のディレクトリを指定します。

--diagnostic-report-filename

出力のファイル名を指定します。

--diagnostic-report-signal

レポート生成のシグナルを設定またはリセットします。(windows サポート外)

--diagnostic-report-verbose

レポート生成中に追加で情報を入れます。

コードから

基本的に特定箇所のエラーでレポートしてほしいときには、コードから呼ぶのがよいでしょう。

try {
  console.log("hi!");
  throw new Error("bye!");
} catch (err) {
  // エラーオブジェクトを渡す
  process.report.triggerReport("report.json", err);
}
$ node --experimental-report index.js
{
  "header": {
    "event": "JavaScript API",
    "location": "TriggerReport",
    "filename": "report.json",
    "dumpEventTime": "2019-01-24T08:27:53Z",
    "dumpEventTimeStamp": "1548286073604",
    "processId": "56468",
    "commandLine": [
      "./node",
      "--experimental-report",
      "b.js"
    ],
    "nodejsVersion": "v12.0.0-pre",
    "wordSize": "64 bit",
    "componentVersions": {
      "node": "12.0.0-pre",
      "v8": "7.1.302.33-node.10",
      ...
    },
    "osVersion": "Darwin 18.2.0 Darwin Kernel Version 18.2.0: Mon Nov 12 20:24:46 PST 2018; root:xnu-4903.231.4~2/RELEASE_X86_64",
    "machine": "Darwin 18.2.0 Darwin Kernel Version 18.2.0: Mon Nov 12 20:24:46 PST 2018; root:xnu-4903.231.4~2/RELEASE_X86_64about-hiroppy.local x86_64"
  },
  "javascriptStack": {
    "message": "Error: bye!",
    "stack": [
      "at Object.<anonymous> (/Users/about_hiroppy/Programming/nodejs/node/out/Release/b.js:19:9)",
      "at Module._compile (internal/modules/cjs/loader.js:737:30)",
      "at Object.Module._extensions..js (internal/modules/cjs/loader.js:748:10)",
      "at Module.load (internal/modules/cjs/loader.js:629:32)",
      "at tryModuleLoad (internal/modules/cjs/loader.js:572:12)",
      "at Function.Module._load (internal/modules/cjs/loader.js:564:3)",
      "at Function.Module.runMain (internal/modules/cjs/loader.js:802:12)",
      "at executeUserCode (internal/bootstrap/node.js:497:15)"
    ]
  },
  "nativeStack": [
    " [pc=0x100130ed1] report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, v8::Local<v8::String>) [/Users/about_hiroppy/Programming/nodejs/node/out/Release/./node]",
    ...
  ],
  "javascriptHeap": {
    "totalMemory": "5603328",
    "totalCommittedMemory": "3743952",
    "usedMemory": "2601416",
    "availableMemory": "1521802280",
    "memoryLimit": "1526909922",
    "heapSpaces": {
      "read_only_space": {
        "memorySize": "524288",
        "committedMemory": "39208",
        "capacity": "515584",
        "used": "30504",
        "available": "485080"
      },
      "new_space": {
        "memorySize": "2097152",
        "committedMemory": "1877472",
        "capacity": "1031168",
        "used": "828632",
        "available": "202536"
      },
      "old_space": {
        "memorySize": "1748992",
        "committedMemory": "1308424",
        "capacity": "1273648",
        "used": "1273608",
        "available": "40"
      },
      "code_space": {
        "memorySize": "696320",
        "committedMemory": "185920",
        "capacity": "153152",
        "used": "153152",
        "available": "0"
      },
      "map_space": {
        "memorySize": "536576",
        "committedMemory": "332928",
        "capacity": "315520",
        "used": "315520",
        "available": "0"
      },
      "large_object_space": {
        "memorySize": "0",
        "committedMemory": "0",
        "capacity": "1521114624",
        "used": "0",
        "available": "1521114624"
      },
      "new_large_object_space": {
        "memorySize": "0",
        "committedMemory": "0",
        "capacity": "0",
        "used": "0",
        "available": "0"
      }
    }
  },
  "resourceUsage": {
    "userCpuSeconds": "0.082528",
    "kernelCpuSeconds": "0.022165",
    "cpuConsumptionPercent": "0.000000",
    "maxRss": "25232932864",
    "pageFaults": {
      "IORequired": "0",
      "IONotRequired": "6375"
    },
    "fsActivity": {
      "reads": "0",
      "writes": "0"
    }
  },
  "libuv": [
    {
      "type": "async",
      "is_active": "1",
      "is_referenced": "0",
      "address": "4339086624",
      "details": ""
    },
    {
      "type": "timer",
      "is_active": "0",
      "is_referenced": "0",
      "address": "140732920753032",
      "details": "repeat: 0, timeout in: 140734958161789 ms"
    },
    {
      "type": "check",
      "is_active": "1",
      "is_referenced": "0",
      "address": "140732920753184",
      "details": ""
    },
    {
      "type": "idle",
      "is_active": "0",
      "is_referenced": "1",
      "address": "140732920753304",
      "details": ""
    },
    {
      "type": "prepare",
      "is_active": "0",
      "is_referenced": "0",
      "address": "140732920753424",
      "details": ""
    },
    {
      "type": "check",
      "is_active": "0",
      "is_referenced": "0",
      "address": "140732920753544",
      "details": ""
    },
    {
      "type": "async",
      "is_active": "1",
      "is_referenced": "0",
      "address": "4320933640",
      "details": ""
    },
    ...
  ],
  "environmentVariables": {
    "TMPDIR": "/var/folders/0k/t5s25c2d30dgvmkr26mj83_h0000gn/T/",
    ...
  },
  "userLimits": {
    "core_file_size_blocks": {
      "soft": "",
      "hard": "unlimited"
    },
    "data_seg_size_kbytes": {
      "soft": "unlimited",
      "hard": "unlimited"
    },
    "file_size_blocks": {
      "soft": "unlimited",
      "hard": "unlimited"
    },
    "max_locked_memory_bytes": {
      "soft": "unlimited",
      "hard": "unlimited"
    },
    "max_memory_size_kbytes": {
      "soft": "unlimited",
      "hard": "unlimited"
    },
    "open_files": {
      "soft": "unlimited",
      "hard": "unlimited"
    },
    "stack_size_bytes": {
      "soft": "unlimited",
      "hard": "67104768"
    },
    "cpu_time_seconds": {
      "soft": "unlimited",
      "hard": "unlimited"
    },
    "max_user_processes": {
      "soft": "unlimited",
      "hard": "2128"
    },
    "virtual_memory_kbytes": {
      "soft": "unlimited",
      "hard": "unlimited"
    }
  },
  "sharedObjects": [
    "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
    ...
  ]
}

このように基本情報から js, native のスタック、js のヒープ関連、リソースの使われ方、イベントループ(libuv)、ユーザーリミット等を確認することができます。

また、以下のように特定イベント時に取得することも可能です。

// uncaught exceptions時のみトリガーさせる
process.report.setDiagnosticReportOptions({ events: ["exception"] });

// 内部エラーと外部のシグナル時のみトリガーさせる
process.report.setDiagnosticReportOptions({ events: ["fatalerror", "signal"] });

余談

まだ、リリースすらされてないのでライブラリを作るなら今! webpack-dashboard みたいなのが今後出てきそうな気がしています。

関連記事

Node.jsでのイベントループの仕組みとタイマーについて - 技術探し Node.jsでのイベントループとタイマーを解説します。

Node.jsのアプリケーションデバッグ・改善方法をおさらいする - 技術探し Node.jsで作られたアプリケーションのデバッグ方法とパフォーマンス改善を手助けする手法をおさらいする。

Node.jsのパフォーマンスチューニングのtips - 技術探し --inspect, --inspect-brk --trace-opt, --trace-deopt --prof --trace-events-enabled --trace-gc node-re...