目次


企業で利用する Node.js 用の IBM ツール

IBM SDK for Node.js のコア・ダンプ・デバッグ機能

IDDE Eclipse アドオンを使用してプログラムの異常終了とメモリー・リークを診断する

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: 企業で利用する Node.js 用の IBM ツール

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:企業で利用する Node.js 用の IBM ツール

このシリーズの続きに乞うご期待。

Node.js 開発では、プログラムの異常終了やメモリー・リークをデバッグする上でコア・ダンプ分析が役に立ちます。そんななか、IBM SDK for Node.js では、IBM Monitoring and Diagnostic Tools — Interactive Diagnostic Data Explorer (IDDE) を介して、Node.js アプリケーションのコア・ダンプ分析とコア・ダンプ・デバッグを行う新しい手段を提供しています。IDDE は無償ラインセンスで使用できる Eclipse アドオンであり、Mac OS X を除き、IBM SDK for Node.js の対象となっているすべてのプラットフォームのコア・ダンプをサポートしています。しかも、そのダンプは移植可能なので、あるコンピューターからコア・ダンプを取得して、そのダンプを別のコンピューター上の IDDE で開くこともできます。これは、そのコンピューターが (サポートされている) 別の OS を実行しているとしても同じです。

IDDE は、Eclipse マーケットプレースからインストールすることも、更新サイトを利用してインストールすることもできます。この記事を読んで、Node.js アプリケーションを開発する際に、IDDE を使用してプログラムの異常終了やメモリー・リークに対処する方法を学んでください。

コア・ダンプを生成する

コア・ダンプを生成する方法は、システムによって異なります。Joyent (Node.js の幹事企業) では、すべての本番 Node.js システムについて、--abort-on-uncaught-exception フラグを指定して実行するよう推奨しています。UNIX システムの場合は、ulimit -c unlimited を設定して、サイズの制限なしでコア・ファイルを生成できるようにする必要もあります。

例外がスローされない場合には、コア・ファイルを生成するにはシステム・ツールを使用する必要があります。例えば、Linux の場合は gcore、AIX の場合は gencore、そしてプロセスを kill しても構わない場合は kill -11 を使用します。Windows 7 以降では、タスク マネージャーを使用してコア・ダンプを生成できるようになっています。それには、Ctrl+Alt+Delete を押して「タスク マネージャーの起動」を選択し、「プロセス」タブで Node.js プロセスを右クリックして「ダンプ ファイルの作成」を選択します。あるいは、Windows 対応の無料の ProcDump ユーティリティーを使用することもできます。

異常終了をデバッグする

プログラムを異常終了させるために、ここでは test.js スクリプトを使用します。この単純なスクリプトは、5 回ループしてからエラーをスローします。

リスト 1. test.js
function main() {
  var inputObject = {
    input: ["one", "two", "three", "fifteen", "one hundred"],
    counter:0,
  };

  for(; inputObject.counter< inputObject.input.length; inputObject.counter++) {
    if (inputObject.input[inputObject.counter].length > 8) {
      throw "Input String Too Big";
    }
  }
}

main();

Linux で test.js を実行するには、以下のようにします。

$ cd node-v0.12.4-linux-x64/bin/
$ ulimit -c unlimited
$ ./node --abort-on-uncaught-exception ../../test.js
Uncaught Input String Too Big

FROM
main (/home/sian/test.js:11:7)
Object.<anonymous> (/home/sian/test.js:16:1)
Module._compile (module.js:460:26)
Object.Module._extensions..js (module.js:478:10)
Module.load (module.js:355:32)
Function.Module._load (module.js:310:12)
Function.Module.runMain (module.js:501:10)
startup (node.js:129:16)
node.js:814:3
Illegal instruction (core dumped)

コア・ダンプを IDDE で開く

コア・ダンプが保管されているコンピューター上に IDDE がインストールされて実行されている場合、そのコア・ダンプが保管されているディスクから直接コア・ダンプを開くことができます。「PD Navigator (PD ナビゲーター)」ビュー (PD は Problem Determination (問題判別) の略です) を右クリックして、「New PD Artifact (新規 PD 成果物)」を選択します。

図 1. 「New PD Artifact (新規 PD 成果物)」の選択
「New PD Artifact (新規 PD 成果物)」を選択する画面のスクリーンショット
「New PD Artifact (新規 PD 成果物)」を選択する画面のスクリーンショット

コア・ダンプの保管先を参照した後、「Finish (完了)」をクリックします。

ダンプを別のコンピューターで開くには、普段使用しているツールでダンプ・ファイルを別のコンピューター上のシステムにコピーし、そのシステム上にインストールされている IDDE から上記の方法でダンプ・ファイルを開きます。ダンプを別の場所や別のコンピューターにコピーした場合に、より有効なネイティブ・スタック・トレースにするには、Node 実行ファイルをダンプと同じディレクトリーにコピーして、シンボルを解決できるようにします。

ここからは、IDDE エディターで作業する必要があります。新規コア・ファイルの下に表示されている「Start Investigation (調査を開始)」を選択してエディターを開きます。

図 2. IDDE エディターを開く
IDDE エディターを開く画面のスクリーンショット
IDDE エディターを開く画面のスクリーンショット

ダンプ・ファイルのサイズが大きいと、ロードするのに時間がかかることがあります。

IDDE コマンド

IDDE エディターについては、エディターとコンソールの両機能を持つものとして考えると理解しやすいかもしれません。IDDE エディターでは、コンソールを使っているかのようにコマンドを入力して実行することができますが、その進捗状況はエディターでの場合と同じように保存されます。

コマンドを実行するには、コマンドの先頭に「!」を追加して、Ctrl+Enter を押します。例えば、「!help」と入力して Ctrl+Enter を押すと、ダンプを扱う際に使用できる他のコマンドを一覧表示するヘルプ・メッセージが出力されます。Node のコマンド・セットは Java のコマンド・セットとは異なります。別のバージョンの Node (または Java) とも異なる場合もあります。それは、IDDE の新しいバージョンに新しいコマンドが追加されているためです。

他の Eclipse エディターの場合と同じく、Ctrl+Space を押すと、入力補完候補が表示されます。IDDE エディターの場合、この候補はコマンドのリストです。

図 3. Ctrl+Space を押すと表示されるコマンド・リスト
Ctrl+Space を押すと表示されるコマンド・リストのスクリーンショット
Ctrl+Space を押すと表示されるコマンド・リストのスクリーンショット

手始めに使用するコマンドとして適切なのは、nodeoverview です。このコマンドは、実行していた Node のバージョンの基本的な概要、依存関係、等々を表示します。

!nodeoverview {

Node Property           Value                                                        
----------------------  -------------------------------------------------------------
Node version            0.12.4                                                       
Path to executable      /home/sian/node-v0.12.4-linux-x64/bin/node                   
Architecture            x64                                                          
Platform                linux                                                        
Command Line Arguments  /home/sian/node-v0.12.4-linux-x64/bin/node /home/sian/test.js
Execution Arguments     --abort-on-uncaught-exception                                
Process ID              5643                                                        

Dependency   Version   
-----------  ----------
http_parser  2.3       
node         0.12.4    
v8           3.28.71.19          2.2    

[...]

問題を調査する

前に記載したコンソール出力から、キャッチされない例外によってプログラムが異常終了したことがわかります。そこで、最初に行わなければならないのは、スタック・トレースを調べることです。まず、threads コマンドを実行してスレッド ID を調べます。

!threads {

Thread ID: 0x74c9 (29897) IP: 0x0000000000eb112d
    !stack 29897

}

実行されていたスレッドは 1 つだけなので、このスレッド ID が JavaScript スレッドのはずです。

このスレッドに対して stack コマンドを実行してみます。このコマンドのショートカットは、threads コマンドの出力に含まれている !stack 29897 なので、ショートカットを示す行の終わりにカーソルを置いて、Ctrl+Space を押してください。以下に出力を記載します (読みやすくするために、引数の列を削除し、一部の行を切り詰めています)。

!stack 29897 {

Instruction Pointer  Frame Address       Location / Frame Type                                                                                                                                     
-------------------  ------------------  ------------------------------------------------------------------------------------
 0x0000000000eb112d  0x00007FFF0A81F960  node::_ZN2v84base2OS11GetUserTimeEPjS2_+0x9d                                              
 0x0000000000a890c1  0x00007FFF0A81FAC0  node::_ZN2v88internal7Isolate29CaptureAndSetSimpleStackTraceENS0_6HandleINS0_8JSO...                                                 
 0x0000000000b63c9d  0x00007FFF0A81FAF0  node::_ZN2v88internal25Runtime_ThrowNotDateErrorEiPPNS0_6ObjectEPNS0_7IsolateE+0xdd
 0x00000E7CAA2A8E32  0x00007FFF0A81FB40  main [/home/sian/test.js]                                                                                                                                    
                                         !jsobject 0x00001519ED09B451                                                                                                                                       
 0x00000E7CAA2A8B34  0x00007FFF0A81FB78  <anonymous> [/home/sian/test.js]                                                                                    
                                         !jsobject 0x00001519ED09B341                                                                                                    
 0x00000E7CAA224AC6  0x00007FFF0A81FBE0  INTERNAL FRAME                                                                                                                                                     
 0x00000E7CAA2A7D26  0x00007FFF0A81FC58  Module._compile [module.js]                                                                                                                 
                                         !jsobject 0x00000DA39E6185F9                                                                                            
 0x00000E7CAA2A220C  0x00007FFF0A81FCA0  Module._extensions..js [module.js]                                                                                  
                                         !jsobject 0x00000DA39E618691                                                                                            
 0x00000E7CAA29E940  0x00007FFF0A81FCE8  Module.load [module.js]                                                                                                  
                                         !jsobject 0x00000DA39E618569                                                                                                                                      
 0x00000E7CAA295565  0x00007FFF0A81FD70  Module._load [module.js]           
                                         !jsobject 0x00000DA39E6184D9           
 0x00000E7CAA294F24  0x00007FFF0A81FDB8  Module.runMain [module.js]                                                                          
                                         !jsobject 0x00000DA39E618721                                                                                                       
 0x00000E7CAA26B31F  0x00007FFF0A81FE28  startup [node.js]                                                    
                                         !jsobject 0x0000079578AC8A61
 0x00000E7CAA269D10  0x00007FFF0A81FE58  <anonymous> [node.js]       
                                         !jsobject 0x0000079578A6EF49                                                                                                                                      
 0x00000E7CAA21EF40  0x00007FFF0A81FE98  INTERNAL FRAME                                                                                                                                                     
 0x00000E7CAA21DE90  0x00007FFF0A81FF20  ENTRY FRAME                                                                                                                                                        
 0x00000E7CAA21DE90  0x00007FFF0A81FF20  ENTRY FRAME                                                                                                                                                        
 0x0000000000914E28  0x00007FFF0A81FFF0  NONE FRAME                                                                                                                                                         
 0x0000000000813bda  0x00007FFF0A820060  node::_ZN2v88Function4CallENS_6HandleINS_5ValueEEEiPS3_+0xba                                                                                                      
 0x0000000000c9d40f  0x00007FFF0A820190  node::_ZN4node15LoadEnvironmentEPNS_11EnvironmentE+0x1df                                                                                                          
 0x0000000000c9d67f  0x00007FFF0A8202E0  node::_ZN4node15LoadEnvironmentEPNS_11EnvironmentE+0x44f                                                                                                          
 0x0000003d0fa1ed5d  0x0000000000000000  node::_fini+0x3d0eb6c8c5                                                                                                                                          

}

コマンドの出力から、異常終了が発生したのが main メソッドであったことがわかります。コードに戻って何が起こったのかを調べるために、IDDE を離れる必要はなく、上記の出力に太字で示されているコマンドを実行すれば、IDDE にソースが表示されます。

!jsobject 0x00001519ED09B451 {

Object has fast properties
Number of descriptors : 5

Name       Value               More Information               
---------  ------------------  -------------------------------
length     0x0000079578A0FE19  <EXECUTABLE_ACCESSOR_INFO_TYPE>
name       0x0000079578A0FE51  <EXECUTABLE_ACCESSOR_INFO_TYPE>
arguments  0x0000079578A0FE89  <EXECUTABLE_ACCESSOR_INFO_TYPE>
caller     0x0000079578A0FEC1  <EXECUTABLE_ACCESSOR_INFO_TYPE>
prototype  0x0000079578A0FEF9  <EXECUTABLE_ACCESSOR_INFO_TYPE>

Object is a function

Name: main

Source:

() {
  var inputObject = {
    input: ["one", "two", "three", "fifteen", "one hundred"],
    counter:0,
  };

  for(; inputObject.counter< inputObject.input.length; inputObject.counter++) {
    if (inputObject.input[inputObject.counter].length > 8) {
      throw "Input String Too Big";
    }
  }
}
}

注: この機能は、実行していたと考えるコードが実際に実行されていたコードであることを確認する場合にも役立ちます。

上記のコードから明らかなようなのは、mainObject.counter が 4 に達しているに違いないこと、そしてそれがストリング "one hundred" を指していて、ストリングの長さが 8 を超えていることです。これが事実であることは、IDDE の別の 2 つのコマンド、jsfindbypropertyjsobject を使用することで確かめることができます。jsfindbyproperty コマンドは、ヒープの中を検索し、指定された名前 (この例の場合は「counter」) をプロパティーに持つすべてのオブジェクトを検出します。一方、jsobject コマンドは、指定したオブジェクトのプロパティーを表示するコマンドです。

!jsfindbyproperty counter {

!jsobject 0x00001519ED09B539
!jsobject 0x00001519ED09B689

}


!jsobject 0x00001519ED09B689 {

Object has fast properties
Number of descriptors : 2

Name     Value               More Information                             
-------  ------------------  ---------------------------------------------
input    0x00001519ED09B6C1  <JS Array[5]> :- !jsobject 0x00001519ed09b6c1
counter  0x0000000400000000  SMI = 4                                      

}

jsfindbyproperty によって生成される 16 進数 (例えば、0x00003CF51F09B441) は、このコマンドによって検出されたオブジェクトのメモリー・アドレスを示します。これらの 16 進アドレスのいずれか 1 つに対して jsobject コマンドを実行することができます。そのアドレスに JavaScript オブジェクトがある場合、このコマンドによってそのオブジェクトのプロパティーに関する情報が出力されます。上記の出力からは、このオブジェクトで counter が 4 に達したことがわかります。

今度は、上記の出力に示されているショートカット・コマンドを使用して input 配列を調べます。

!jsobject 0x00001519ed09b6c1 {

Array at !hexdump 0x00001519ED09B4E1
Array len = 5

0 : 0x00000CEAAC1A3679, one
1 : 0x00000CEAAC1A3699, two
2 : 0x00000CEAAC1A36B9, three
3 : 0x00000CEAAC1A36D9, fifteen
4 : 0x00000CEAAC1A36F9, one hundred

}

これで、test.js コードのテストに失敗して例外をスローした配列要素を確認することができます。

メモリー・リークを見つける

メモリー・リークは、どのプログラムにも共通する一般的な問題であると言えます。IDDE には、メモリーを大量に使用しているオブジェクトを突き止めるのに役立つコマンドがいくつかあります。この例では、メモリー・リークが発生していると思われる Node.js アプリケーションから取得したコア・ダンプを出発点とします。

リークを突き止める 1 つの方法は、かなりの時間間隔を置いて 2 つ以上のダンプを取り、いくつかのコマンドの出力を 2 つのダンプの間で比較することです。

jsmeminfo コマンドを使用すれば、以下の出力例に示すように、サイズが非常に大きい 1 つのオブジェクトが相当な領域を使い尽くしている場合に、そのことがすぐにわかります。

!jsmeminfo {

Memory allocator, used: 1423 MB, available: 0 MB
Total Heap Objects: 29497

Largest 5 heap objects  Type               Size (bytes)  More information          
----------------------  -----------------  ------------  --------------------------
0x0000000088a6d319      JS_OBJECT_TYPE          1311125  !jsobject 0x0000000088a6d319
0x0000000088aac6d9      FIXED_ARRAY_TYPE          98360  !array 0x000003ff88aac6d9
0x000003ff8abe31b9      ASCII_STRING_TYPE         48176  !string 0x000003ff8abe31b9
0x000003ff8ab34f09      ASCII_STRING_TYPE         48104  !string 0x000003ff8ab34f09
0x000003ff8ab04101      ASCII_STRING_TYPE         40936  !string 0x000003ff8ab04101

「異常終了をデバッグする」セクションと同じく、このオブジェクトに対して jsobject コマンドを実行すると、このオブジェクトをプログラム内のオブジェクトに関連付けて問題を修正できるようになります。

別のタイプのメモリー問題としては、プログラムが多数のオブジェクトを作成している一方で、それらのオブジェクトを破棄していないという問題もあります。jsgroupobjects は、同じタイプのオブジェクトをグループ化し、プログラムにそのタイプのオブジェクトがどれだけあるのかを示します。jsgroupobjects はオブジェクトのコンストラクターも表示するため、Node.js バッファーが使用された場所を特定するのにも役立ちます (Node では、バッファーがヒープ外にメモリーを割り振る手段となります)。この例では、Buffer が最も頻繁に発生するオブジェクトです。

!jsgroupobjects {

Representative Object Address  Object Type    Num Objects Constructor  Num Properties  Properties                                                                      
-----------------------------  -------------  ----------  -----------  --------------  ---------------
!jsobject 0x000003ffec004101   JS_OBJECT_TYPE       2572      Buffer         2        length, parent
...

IDDE では、バッファーによって割り当てられた外部配列の場所を特定することによって、バッファーの中身を出力することができます (以下の出力に太字で示されている部分)。

print 0x000003ffec004101 {

Object at 0x000003FFEC004101 is JSObject

Class hierarchy :-

|-JSObject
|  |- kElementsOffset 0x10 (EXTERNAL_UINT8_ARRAY_TYPE, !print 0x000003FFEC004159)
|  |- kPropertiesOffset 0x8 (FIXED_ARRAY_TYPE, !print 0x000003FF92A04111)
| |-JSReceiver
| | |-HeapObject
| | |  |- kMapOffset 0x0 (MAP_TYPE, !print 0x000003FF8BE1F6E9)
| | | |-Object
...

kElementsOffset のアドレスを取得して、そのアドレスを array コマンドに指定します。

!array 0x000003FFEC004159 {

Array type : EXTERNAL_UINT8_ARRAY_TYPE
Len : 10
0 0x48 H
1 0x65 e
2 0x6c l
3 0x6c l
4 0x6f o
5 0x20 
6 0x6e n
7 0x6f o
8 0x64 d
9 0x65 e
..

メモリー問題には、objtypes コマンドも役立ちます。このコマンドは、V8 ヒープ・オブジェクト・タイプの数とメモリー内のサイズを表示します。

完全なコマンド・リファレンス

以下に、IDDE の完全なコマンド・リファレンスを記載します。斜体で示されているコマンドは、任意のコア・ダンプで使用することができます。その他すべてのコマンドは、Node.js ダンプに固有のものです。

array
指定されたアドレスにある固定配列の要素を表示します。
find, findall, findnext
メモリー内のストリングを検索します。
frame
単一の JavaScript スタック・フレームに関する詳細情報を表示します。
help
コマンドの一覧を表示します。
hexdump
メモリーの 1 つのセクションを 16 進形式および ASCII 形式で出力します。
jsfindbyproperty
指定されたプロパティーを持つ JavaScript オブジェクトを検索します。
jsgroupobjects
同じ Map を共有する JavaScript オブジェクトのグループを一覧表示します。
jslistobjects
指定された V8 オブジェクト・タイプのヒープ・オブジェクトを一覧表示します。
jsmeminfo
サイズが最も大きい 5 つのオブジェクトを含め、JavaScript のメモリー使用状況に関する情報を表示します。
jsobject
JavaScript オブジェクトの詳細を表示します。
jsobjectsmatching
指定されたオブジェクトと同じ Map を共有する JavaScript オブジェクトを出力します。
jsstringsearch
指定されたストリングをヒープで検索します。
locate
指定されたストリングをメモリー内で検索します。
nodeoverview
バージョンを含め、Node 情報の概要を表示します。
objtype
すべての V8 オブジェクト・タイプを一覧表示します。
objtypes
ヒープ使用状況の内訳を V8 インスタンス・タイプ別に表示します。
print
指定されたヒープ・オブジェクトの C++ 階層を表示します。
ranges
使用可能なメモリー範囲のリストを出力します。
stack
指定されたスレッドの JavaScript スタック・トレースを表示します。
string
指定されたアドレスにあるストリングを表示します。
threads
すべてのスレッドを一覧表示します。

まとめ

このチュートリアルでは、Node.js コア・ダンプを生成する方法、IDDE でダンプを開く方法、IDDE エディターでコマンドを入力して実行する方法を説明し、ダンプに使用できるすべてのコマンドの一覧を示しました。これで、プログラムの異常終了やメモリー・リークの根本原因を突き止めるのに最も有効な IDDE コマンドを理解できたはずです。

IDDE の使用方法について不明な点がある場合や、このツールで見つかったバグや問題を報告したいという場合には、javatool@uk.ibm.com 宛てにメールでお知らせください。


ダウンロード可能なリソース


関連トピック

  • IDDE Knowledge Center: IDDE Knowledge Center: にアクセスして、IDDE のオンライン・ヘルプを参照してください。
  • IBM developer kits: Health Center や GCMV をはじめとする、IDDE 以外の Java および Node.js 用の IBM 診断ツールに関する情報を入手してください。
  • 「MDB and Linux」: Joyent がどのようにして Node.js からコア・ダンプを取得するよう推奨しているかを理解してください。
  • Buffer: Node.js のドキュメントでバッファーについて読んで下さい。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Linux
ArticleID=1022728
ArticleTitle=企業で利用する Node.js 用の IBM ツール: IBM SDK for Node.js のコア・ダンプ・デバッグ機能
publish-date=12032015