Experimental feature in GraalVM

GraalVM Insight

GraalVM Insight は、プログラムの実行時の動作をトレースし、洞察を収集する多目的で柔軟なツールです。

このツールの動的な性質により、ユーザーは実行中のアプリケーションにトレースポイントカットを選択的に適用することができ、パフォーマンスの低下はありません。 GraalVM Insight は、プログラムの実行時の動作への詳細なアクセスも提供し、ユーザーは呼び出しサイトまたは割り当てサイトで値と型を検査できます。 さらに、このツールを使用すると、ユーザーは計算された値を変更し、実行を中断し、アプリケーションコードを変更することなく動作の変更をすばやく試すことができます。 このツールの詳細な実装については、API仕様をご覧ください。

このページでは、バージョン20.1時点のGraalVM Insightに関する情報を提供しています。バージョン20.0および19.3のInsightについては、こちらをご覧ください。

はじめに #

  1. 以下の内容で、単純な *source-tracing.js* スクリプトを作成します。
    insight.on('source', function(ev) {
     if (ev.characters) {
         print(`Loading ${ev.characters.length} characters from ${ev.name}`);
     }
    });
    
  2. Node.js ランタイムをインストールしたら、 `--insight` ツールを使用して `node` ランチャーを起動し、どのスクリプトがロードおよび評価されているかを確認します。
    ./bin/node --insight=source-tracing.js --js.print --experimental-options -e "print('The result: ' + 6 * 7)" | tail -n 10
    Loading 215 characters from internal/modules/esm/transform_source.js
    Loading 12107 characters from internal/modules/esm/translators.js
    Loading 1756 characters from internal/modules/esm/create_dynamic_module.js
    Loading 12930 characters from internal/vm/module.js
    Loading 2710 characters from internal/modules/run_main.js
    Loading 308 characters from module.js
    Loading 10844 characters from internal/source_map/source_map.js
    Loading 170 characters from [eval]-wrapper
    Loading 29 characters from [eval]
    The result: 42
    

    *source-tracing.js* スクリプトは、提供された `insight` オブジェクトを使用して、ソースリスナーをランタイムにアタッチしました。 スクリプトがロードされるたびに、リスナーはそれを通知され、アクション(処理されたスクリプトの長さと名前を出力する)を実行できました。

Insight情報は、print文またはヒストグラムに収集できます。 次の *function-hotness-tracing.js* スクリプトは、すべてのメソッド呼び出しをカウントし、プログラムの実行が終了したときに最も頻繁に呼び出されたメソッドをダンプします。

var map = new Map();

function dumpHotness() {
    print("==== Hotness Top 10 ====");
    var count = 10;
    var digits = 3;
    Array.from(map.entries()).sort((one, two) => two[1] - one[1]).forEach(function (entry) {
        var number = entry[1].toString();
        if (number.length >= digits) {
            digits = number.length;
        } else {
            number = Array(digits - number.length + 1).join(' ') + number;
        }
        if (count-- > 0) print(`${number} calls to ${entry[0]}`);
    });
    print("========================");
}

insight.on('enter', function(ev) {
    var cnt = map.get(ev.name);
    if (cnt) {
        cnt = cnt + 1;
    } else {
        cnt = 1;
    }
    map.set(ev.name, cnt);
}, {
    roots: true
});

insight.on('close', dumpHotness);

`map` は、Insightスクリプト内で共有されるグローバル変数であり、コードは `insight.on('enter')` 関数と `dumpHotness` 関数の間でデータを共有できます。 後者は、ノードプロセスが終了したときに実行されます( `insight.on('close', dumpHotness` を介して登録されます)。 `node` プロセスが終了すると、関数呼び出しの名前とカウントを含むテーブルが出力されます。

以下のように呼び出します。

./bin/node --insight=function-hotness-tracing.js --js.print --experimental-options -e "print('The result: ' + 6 * 7)"
The result: 42
==== Hotness Top 10 ====
516 calls to isPosixPathSeparator
311 calls to :=>
269 calls to E
263 calls to makeNodeErrorWithCode
159 calls to :anonymous
157 calls to :program
 58 calls to getOptionValue
 58 calls to getCLIOptionsFromBinding
 48 calls to validateString
 43 calls to hideStackFrames
========================

ポリグロット トレース #

前の例はJavaScriptで記述されましたが、GraalVMのポリグロットの性質により、同じ計測器を使用して、たとえばRuby言語で記述されたプログラムで使用できます。

  1. *source-trace.js* ファイルを作成します。
    insight.on('source', function(ev) {
    if (ev.uri.indexOf('gems') === -1) {
      let n = ev.uri.substring(ev.uri.lastIndexOf('/') + 1);
      print('JavaScript instrument observed load of ' + n);
    }
    });
    
  2. *helloworld.rb* Rubyファイルを作成します。
    puts 'Hello from GraalVM Ruby!'
    
  3. JavaScript計測器をRubyプログラムに適用します。
    ./bin/ruby --polyglot --insight=source-trace.js helloworld.rb
    JavaScript instrument observed load of helloworld.rb
    Hello from GraalVM Ruby!
    

    *source-tracing.js* スクリプトはJavaScriptで記述されたままであるため、 `--polyglot` パラメータを使用してRubyランチャーを起動する必要があります。

ユーザーはGraalVM上で任意の言語を計測できますが、InsightスクリプトはGraalVMでサポートされている任意の言語(Truffle言語実装フレームワークで実装)で記述できます。

  1. *source-tracing.rb* Rubyファイルを作成します。
    puts "Ruby: Initializing GraalVM Insight script"
    insight.on('source', ->(ev) {
     name = ev[:name]
     puts "Ruby: observed loading of #{name}"
    })
    puts 'Ruby: Hooks are ready!'
    
  2. Node.jsアプリケーションを起動し、Rubyスクリプトで計測します。
    ./bin/node --polyglot --insight=source-tracing.rb -e "console.log('With Ruby: ' + 6 * 7)" | grep Ruby
    Ruby: Initializing GraalVM Insight script
    Ruby: Hooks are ready!
    Ruby: observed loading of internal/per_context/primordials.js
    Ruby: observed loading of internal/per_context/setup.js
    Ruby: observed loading of internal/per_context/domexception.js
    ....
    Ruby: observed loading of internal/modules/cjs/loader.js
    Ruby: observed loading of vm.js
    Ruby: observed loading of fs.js
    Ruby: observed loading of internal/fs/utils.js
    Ruby: observed loading of [eval]-wrapper
    Ruby: observed loading of [eval]
    With Ruby: 42
    

値の検査 #

GraalVM Insightを使用すると、プログラムの実行場所をトレースできるだけでなく、プログラムの実行中にローカル変数と関数引数の値にアクセスすることもできます。 たとえば、関数 `fib` の引数 `n` の値を表示する計測器を作成できます。

insight.on('enter', function(ctx, frame) {
   print('fib for ' + frame.n);
}, {
   roots: true,
   rootNameFilter: (name) => 'fib' === name
});

この計測器は、2番目の関数引数 `frame` を使用して、計測されたすべての関数内のローカル変数の値にアクセスします。 上記のスクリプトは、 `rootNameFilter` を使用して、 `fib` という名前の関数にのみフックを適用します。

function fib(n) {
  if (n < 1) return 0;
  if (n < 2) return 1;
  else return fib(n - 1) + fib(n - 2);
}
print("Two is the result " + fib(3));

計測器が `fib-trace.js` ファイルに保存され、実際のコードが `fib.js` にある場合、次のコマンドを呼び出すと、プログラムの実行と関数呼び出し間で渡されるパラメータに関する詳細情報が得られます。

./bin/node --insight=fib-trace.js --js.print --experimental-options fib.js
fib for 3
fib for 2
fib for 1
fib for 0
fib for 1
Two is the result 2

Insightの詳細 #

ある程度のスキルを持つ開発者であれば、独自のいわゆる「フック」を簡単に作成し、実際のプログラムに動的に適用できます。 これにより、実行速度を損なうことなく、アプリケーションの実行と動作に関する究極の洞察が得られます。

GraalVM Insightについてさらに学習し、深く掘り下げるには、必須の *HelloWorld* の例から始まり、より難しいタスクを紹介するInsightマニュアルに進んでください。

アプリケーションへのGraalVM Insightの埋め込み #

GraalVM言語(Truffleフレームワークで実装された言語)は、Polyglot Context APIを介してカスタムアプリケーションに埋め込むことができます。 GraalVM Insightは、同じAPIを介して制御することもできます。

GraalVM Insight機能を安全な方法でアプリケーションに統合する方法については、埋め込みドキュメントをお読みください。

GraalVM Insightを使用したトレース #

GraalVM Insightは、既存のコードにトレース機能を動的に追加します。 アプリケーションを通常どおりに記述し、必要に応じてOpen Telemetryトレースを動的に適用します。 InsightとJaegerの統合の詳細については、専用のガイドをご覧ください。

API仕様 #

実装の詳細に興味がある場合は、API仕様を確認してください。 `insight` オブジェクトのプロパティ、関数などに関する情報があります。

お問い合わせ