Insightのマニュアル
GraalVM Insightは、信頼性のあるアプリケーションを記述するための柔軟な汎用ツールです。このツールの動的な性質により、パフォーマンスを損なうことなく、すでにデプロイされているアプリケーションにトレース・ポイントカットを選択的に適用できます。
中程度のスキルを持つハッカーであれば、いわゆるInsightスニペットを簡単に作成し、実際のプログラムに動的に適用できます。これにより、実行速度を損なうことなく、アプリケーションの実行と動作に関する究極のインサイトが提供されます。
目次
- クイック・スタート
- ホット度上位10の例
- Insightの任意のGraalVM言語への適用
- JavaScriptを使用したInsight
- Pythonを使用したInsight
- Rubyを使用したInsight
- Rを使用したInsight
- CコードへのInsightの追加
- 値の検査
- ローカル変数の変更
- 特定の場所へのインサイト
- Node.JSでのInsight初期化の遅延
- 例外の処理
- 実行のインターセプトおよび変更
- 最小限のオーバヘッド
- ローカルへのアクセス時の最小限のオーバヘッド
- 実行スタックへのアクセス
- GraalVM Insight APIに関するノート
- ヒープ・ダンプ
クイック・スタート
必須のHelloWorldの例から始めます。次のコンテンツを含むsource-tracing.js
スクリプトを作成します:
insight.on('source', function(ev) {
print(`Loading ${ev.characters.length} characters from ${ev.name}`);
});
GraalVMのnode
ランチャで--insight
インストゥルメント・オプションを追加して実行します。ロードおよび評価されるスクリプトを確認します:
graalvm/bin/node --js.print --insight=source-tracing.js -e "print('The result: ' + 6 * 7)" | tail -n 10
Loading 29938 characters from url.js
Loading 345 characters from internal/idna.js
Loading 12642 characters from punycode.js
Loading 33678 characters from internal/modules/cjs/loader.js
Loading 13058 characters from vm.js
Loading 52408 characters from fs.js
Loading 15920 characters from internal/fs/utils.js
Loading 505 characters from [eval]-wrapper
Loading 29 characters from [eval]
The result: 42
どのようになりましたか。GraalVM Insightのsource-tracing.js
スクリプトにより、指定されたinsight
オブジェクトを使用して、ソース・リスナーがランタイムにアタッチされました。そのため、node
でスクリプトがロードされるたびに、リスナーは通知を受け取り、(この場合は処理されたスクリプトの長さと名前を出力する)アクションを実行できます。
ホット度上位10の例
インサイト情報の収集は、印刷物に限定されません。ご使用の言語でチューリング完全計算を実行できます。たとえば、すべてのメソッド呼出しをカウントし、実行が終了した時点で最も頻度が高かったメソッド呼出しをダンプするプログラムです。
次のコードをfunction-hotness-tracing.js
に保存します:
var map = new Map();
function dumpHotness() {
print("==== Hotness Top 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 (number > 10) 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
関数の間で、コードによるデータの共有を許可します。後者は、node
プロセスの実行が終了すると実行されます(insight.on('close', dumpHotness)
を介して登録されます)。プログラムを起動します:
graalvm/bin/node --js.print --insight=function-hotness-tracing.js -e "print('The result: ' + 6 * 7)"
The result: 42
==== Hotness Top 10 ====
543 calls to isPosixPathSeparator
211 calls to E
211 calls to makeNodeErrorWithCode
205 calls to NativeModule
198 calls to uncurryThis
154 calls to :=>
147 calls to nativeModuleRequire
145 calls to NativeModule.compile
55 calls to internalBinding
53 calls to :anonymous
49 calls to :program
37 calls to getOptionValue
24 calls to copyProps
18 calls to validateString
13 calls to copyPrototype
13 calls to hideStackFrames
13 calls to addReadOnlyProcessAlias
========================
node
プロセスの終了時に、関数呼出しの名前と数を含む表が出力されます。
Insightの任意のGraalVM言語への適用
前述の例はJavaScriptで記述され、node
が使用されていましたが、GraalVMのポリグロットの性質上、同じインストゥルメントを取得して、GraalVMがサポートする任意の言語に適用できます。たとえば、GraalVM InsightでRuby言語をテストします。
開始するには、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);
}
});
helloworld.rb
ファイルでRubyプログラムを準備します:
puts 'Hello from GraalVM Ruby!'
ノート: Rubyサポートが有効になっていることを確認します。ポリグロット・プログラミング・ガイドを参照してください。
JavaScriptインストゥルメントをRubyプログラムに適用します次に、表示される内容を示します:
graalvm/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
パラメータを使用してGraalVMのRubyランチャを起動する必要があります。
JavaScriptを使用したInsight
前の項で説明したように、GraalVM InsightはNode.jsのみに限定されるものではありません。GraalVMが提供するすべての言語ランタイムで使用できます。GraalVMに付属するJavaScriptの実装を試してください。
function-tracing.js
スクリプトを作成します:
var count = 0;
var next = 8;
insight.on('enter', function(ev) {
if (count++ % next === 0) {
print(`Just called ${ev.name} as ${count} function invocation`);
next *= 2;
}
}, {
roots: true
});
sieve.jsの上で実行します。これは、エラトステネスの篩のバリアントを使用して10万個の素数を計算するサンプル・スクリプトです:
graalvm/bin/js --insight=function-tracing.js sieve.js | grep -v Computed
Just called :program as 1 function invocation
Just called Natural.next as 17 function invocation
Just called Natural.next as 33 function invocation
Just called Natural.next as 65 function invocation
Just called Natural.next as 129 function invocation
Just called Filter as 257 function invocation
Just called Natural.next as 513 function invocation
Just called Natural.next as 1025 function invocation
Just called Natural.next as 2049 function invocation
Just called Natural.next as 4097 function invocation
Pythonを使用したInsight
GraalVM言語をインストゥルメントできるだけでなく、Insightスクリプトをその言語で記述することもできます。この項では、Pythonの例を紹介します。
PythonでGraalVM Insightスクリプトを記述できます。このようなインサイトは、Pythonまたはその他の言語で記述されたプログラムに適用できます。
次に、関数minusOne
がコールされると変数n
の値を出力するスクリプトの例を示します。次のコードをagent.py
ファイルに保存します:
def onEnter(ctx, frame):
print(f"minusOne {frame.n}")
class At:
sourcePath = ".*agent-fib.js"
class Roots:
roots = True
at = At()
rootNameFilter = "minusOne"
insight.on("enter", onEnter, Roots())
次のコードは、GraalVM 22.2で導入されたソースの場所の宣言仕様を使用します。古いGraalVMバージョンで動的sourceFilter
を使用します:
def onEnter(ctx, frame):
print(f"minusOne {frame.n}")
class Roots:
roots = True
rootNameFilter = "minusOne"
def sourceFilter(self, src):
return src.name == "agent-fib.js"
insight.on("enter", onEnter, Roots())
次のコマンドを使用して、このスクリプトをagent-fib.jsに適用します:
`js --polyglot --insight=agent.py agent-fib.js`
ノート: Pythonサポートが有効になっていることを確認してください。ポリグロット・プログラミング・ガイドを参照してください。
Rubyを使用したInsight
RubyでGraalVM Insightスクリプトを記述できます。このようなインサイトは、Rubyまたはその他の言語で記述されたプログラムに適用できます。
ノート: Rubyサポートが有効になっていることを確認します。ポリグロット・プログラミング・ガイドを参照してください。
source-tracing.rb
スクリプトを作成します:
puts("Ruby: Insight version #{insight.version} is launching")
insight.on("source", -> (env) {
puts "Ruby: observed loading of #{env.name}"
})
puts("Ruby: Hooks are ready!")
Node.jsアプリケーションを起動し、Rubyスクリプトを使用してインストゥルメントします:
graalvm/bin/node --js.print --experimental-options --polyglot --insight=source-tracing.rb agent-fib.js
Ruby: Initializing GraalVM Insight script
Ruby: Hooks are ready!
Ruby: observed loading of node:internal/errors
Ruby: observed loading of node:internal/util
Ruby: observed loading of node:events
....
Ruby: observed loading of node:internal/modules/run_main
Ruby: observed loading of <...>/agent-fib.js
Three is the result 3
変数値を追跡するには、agent.rb
スクリプトを作成します:
insight.on("enter", -> (ctx, frame) {
puts("minusOne #{frame.n}")
}, {
roots: true,
rootNameFilter: "minusOne",
at: {
sourcePath: ".*agent-fib.js"
}
})
次のコードは、GraalVM 22.2で導入されたソースの場所の宣言仕様を使用します。古いGraalVMバージョンで動的sourceFilter
を使用します:
insight.on("enter", -> (ctx, frame) {
puts("minusOne #{frame.n}")
}, {
roots: true,
rootNameFilter: "minusOne",
sourceFilter: -> (src) {
return src.name == Dir.pwd+"/agent-fib.js"
}
})
前述のRubyスクリプトの例では、agent-fib.jsプログラムの関数minusOne
がコールされると、変数n
の値が出力されます:
graalvm/bin/node --js.print --experimental-options --polyglot --insight=agent.rb agent-fib.js
minusOne 4
minusOne 3
minusOne 2
minusOne 2
Three is the result 3
Rを使用したInsight
同じインストゥルメントをR言語で記述できます。
agent-r.R
スクリプトを作成します:
cat("R: Initializing GraalVM Insight script\n")
insight@on('source', function(env) {
cat("R: observed loading of ", env$name, "\n")
})
cat("R: Hooks are ready!\n")
これを使用して、test.R
プログラムをトレースします:
graalvm/bin/Rscript --insight=agent-r.R test.R
R: Initializing GraalVM Insight script
R: Hooks are ready!
R: observed loading of test.R
唯一の変更点はR言語であることです。他のすべてのGraalVM Insightの機能およびAPIは同じままです。
CコードへのInsightの追加
動的言語を解釈できるだけでなく、GraalVMのLLI実装により、C、C++、Fortran、Rustなどで記述された、静的にコンパイルされたプログラムも混在させることができます。
たとえば、sieve.cなどのプログラムが長時間実行されているとします。ここには、main
メソッドに終了しないfor
ループが含まれています。それに実行割当てを行います。
最初に、GraalVMでプログラムを実行します:
export TOOLCHAIN_PATH=`graalvm/bin/lli --print-toolchain-path`
${TOOLCHAIN_PATH}/clang agent-sieve.c -lm -o sieve
graalvm/bin/lli sieve
GraalVMのclang
ラッパーは、通常のネイティブ・コードに沿って、sieve
実行ファイルにLLVMビットコード情報を保持するように通常のclang
に指示する特別なオプションを追加します。その後、GraalVMのlli
インタプリタは、ビットコードを使用して、プログラムをフル・スピードで解釈できます。ちなみに、./sieve
による直接ネイティブ実行の結果と、graalvm/bin/lli sieve
のインタプリタ速度を比較してください。インタプリタに関しては、かなり良い結果になるはずです。
ここで、無限ループを断ち切ることに焦点を当てます。次のJavaScript agent-limit.js
Insightスクリプトを使用して実行できます:
var counter = 0;
insight.on('enter', function(ctx, frame) {
if (++counter === 1000) {
throw `GraalVM Insight: ${ctx.name} method called ${counter} times. enough!`;
}
}, {
roots: true,
rootNameFilter: 'nextNatural'
});
このスクリプトは、CのnextNatural
関数の呼出し数をカウントし、関数が1000回呼び出されると、エラーを発行してsieve
の実行を終了します。次のようにプログラムを実行します:
graalvm/bin/lli --polyglot --insight=agent-limit.js sieve
Computed 97 primes in 181 ms. Last one is 509
GraalVM Insight: nextNatural method called 1000 times. enough!
at <js> :anonymous(<eval>:7:117-185)
at <llvm> nextNatural(agent-sieve.c:14:186-221)
at <llvm> nextPrime(agent-sieve.c:74:1409)
at <llvm> measure(agent-sieve.c:104:1955)
at <llvm> main(agent-sieve.c:123:2452)
ネイティブ・コードからプリミティブ・ローカル変数にアクセスできます。前述のInsightスクリプトを次のように置き換えます:
insight.on('enter', function(ctx, frame) {
print(`found new prime number ${frame.n}`);
}, {
roots: true,
rootNameFilter: (n) => n === 'newFilter'
});
新しいプライムがフィルタ・リストに追加されるたびに、メッセージを出力します:
graalvm/bin/lli --polyglot --insight=agent-limit.js sieve | head -n 3
found new prime number 2
found new prime number 3
found new prime number 5
lli
、ポリグロットおよびGraalVM Insightを組み合せると、ネイティブ・プログラムのトレース、制御、および対話型デバッグまたはバッチ・デバッグに大きな可能性が広がります。
値の検査
GraalVM Insightでは、プログラムが実行されている場所をトレースできるだけでなく、実行中にローカル変数および関数の引数の値にアクセスすることもできます。たとえば、関数fib
の引数n
の値を示すインストゥルメントを作成できます:
insight.on('enter', function(ctx, frame) {
print('fib for ' + frame.n);
}, {
roots: true,
rootNameFilter: 'fib'
});
このインストゥルメントは、2番目の関数の引数frame
を使用して、インストゥルメントされたすべての関数内のローカル変数の値にアクセスします。前述のInsightスクリプトでは、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
に格納されている場合、次のコマンドを起動すると、プログラムの実行および関数の呼出し間で渡されるパラメータに関する詳細情報が生成されます:
graalvm/bin/node --js.print --insight=fib-trace.js fib.js
fib for 3
fib for 2
fib for 1
fib for 0
fib for 1
Two is the result 2
この項をまとめると、GraalVM Insightは、ポリグロットで言語に依存しないアスペクト指向プログラミングに役立つツールです。
ローカル変数の変更
GraalVM Insightは、ローカル変数にアクセスできるだけでなく、それを変更することもできます。たとえば、配列を合計する次のプログラムを考えてみます:
function plus(a, b) {
return a + b;
}
var sum = 0;
[1, 2, 3, 4, 5, 6, 7, 8, 9].forEach((n) => sum = plus(sum, n));
print(sum);
数値45
が出力されます。次のInsightスクリプトを適用して、偶数でない数値を加算する前に消去します:
insight.on('enter', function zeroNonEvenNumbers(ctx, frame) {
if (frame.b % 2 === 1) {
frame.b = 0;
}
}, {
roots: true,
rootNameFilter: 'plus'
});
js --insight=erase.js sumarray.js
で起動すると、値20
のみが出力されます。
GraalVM Insightのenter
およびreturn
フックは、既存の変数のみを変更できます。これらは、新しいものを導入することはできません。そうしようとすると、例外が発生します。
特定の場所へのインサイト
特定のコードの場所の変数を取得するために、at
オブジェクトには、必須のソース指定(ソース・ファイル・パスに一致する正規表現を持つsourcePath
プロパティ、またはソースURIの文字列表現を持つsourceURI
プロパティ)のいずれか以外も含めることができます。オプションのline
またはcolumn
(あるいはその両方)を指定することもできます。distance.js
ソース・ファイルを見てみましょう:
(function(x, y) {
let x2 = x*x;
let y2 = y*y;
let d = Math.sqrt(x2 + y2);
for (let i = 0; i < d; i++) {
// ...
}
return d;
})(3, 4);
次に、次のdistance-trace.js
インサイト・スクリプトを適用して、変数の値を取得します:
insight.on('enter', function(ctx, frame) {
print("Squares: " + frame.x2 + ", " + frame.y2);
}, {
statements: true,
at: {
sourcePath: ".*distance.js",
line: 4
}
});
insight.on('enter', function(ctx, frame) {
print("Loop var i = " + frame.i);
}, {
expressions: true,
at: {
sourcePath: ".*distance.js",
line: 5,
column: 21
}
});
出力は次のとおりです:
graalvm/bin/js --insight=distance-trace.js distance.js
Squares: 9, 16
Loop var i = 0
Loop var i = 1
Loop var i = 2
Loop var i = 3
Loop var i = 4
Loop var i = 5
Node.JSでのInsight初期化の遅延
GraalVM Insightは、node
実装を含む任意のGraalVM言語ランタイムで使用できます。ただし、node
には、プレーンなInsightスクリプトを記述することを望んでいません。モジュールを含めて、node
エコシステムを最大限に使用することを推奨します。これを行うagent-require.js
スクリプトのサンプルを次に示します:
let initialize = function (require) {
let http = require("http");
print(`${typeof http.createServer} http.createServer is available to the agent`);
}
let waitForRequire = function (event) {
if (typeof process === 'object' && process.mainModule && process.mainModule.require) {
insight.off('source', waitForRequire);
initialize(process.mainModule.require.bind(process.mainModule));
}
};
insight.on('source', waitForRequire, { roots: true });
Insightスクリプトはできるかぎり早く初期化され、その時点でrequire
関数はまだ準備ができていません。そのため、このスクリプトは、まずロードされたスクリプトにリスナーをアタッチし、メイン・ユーザー・スクリプトがロードされているときに、そのprocess.mainModule.require
関数を取得します。次に、insight.off
を使用してプローブを削除し、実際のinitialize
関数を呼び出して、すべてのノード・モジュールにアクセスしながら実際の初期化を実行します。スクリプトは、次を使用して実行できます:
graalvm/bin/node --js.print --insight=agent-require.js yourScript.js
この初期化シーケンスは、メインのyourScript.js
パラメータを使用して起動されたGraalVMのnode
バージョン12.10.0で動作することが知られています。
例外の処理
GraalVM Insightインストゥルメントは、例外をスローでき、その例外は周囲のユーザー・スクリプトに伝播されます。様々なメッセージを記録するプログラムseq.js
があるとします:
function log(msg) {
print(msg);
}
log('Hello GraalVM Insight!');
log('How');
log('are');
log('You?');
記録されたメッセージを監視して、インストゥルメントterm.js
を登録し、seq.js
プログラム実行の途中で実行を終了できます:
insight.on('enter', (ev, frame) => {
if (frame.msg === 'are') {
throw 'great you are!';
}
}, {
roots: true,
rootNameFilter: 'log'
});
term.js
インストゥルメントは、メッセージare
を含むlog
関数のコールを待機し、その時点で、ユーザー・プログラムの実行を効果的に中断して独自の例外を生成します。その結果、次のようになります:
graalvm/bin/js --polyglot --insight=term.js seq.js
Hello GraalVM Insight!
How
great you are!
at <js> :=>(term.js:3:75-97)
at <js> log(seq.js:1-3:18-36)
at <js> :program(seq.js:7:74-83)
Insightインストゥルメントによって生成された例外は、通常の言語例外として扱われます。seq.js
プログラムは、通常のtry { ... } catch (e) { ... }
ブロックを使用してそれらを捕捉し、通常のユーザー・コードによって生成されたかのように処理できます。
実行のインターセプトおよび変更
GraalVM Insightでは、プログラムの実行を変更できます。特定の計算をスキップし、独自の代替計算に置き換えることができます。例として、次のplus
関数を示します:
function plus(a, b) {
return a + b;
}
plus
メソッドの動作は簡単に変更できます。次のInsightスクリプトは、ctx.returnNow
機能を使用して、+
演算を乗算に置き換えます:
insight.on('enter', function(ctx, frame) {
ctx.returnNow(frame.a * frame.b);
}, {
roots: true,
rootNameFilter: 'plus'
});
returnNow
メソッドは、すぐに実行を停止し、plus
関数のコール元に戻ります。plus
メソッドの本体がまったく実行されないのは、たとえば実際の関数の本体が実行される前にインサイトon('enter', ...)
が適用されたためです。2つの数値を加算するかわりに乗算することは、あまり魅力的ではないと思われるかもしれませんが、同じアプローチは、関数呼出しを繰り返すアドオン・キャッシュ(メモ化など)の提供に役立ちます。
また、元の関数コードを実行して、その結果の変更のみを行うことも可能です。たとえば、plus
関数の結果が常に非負数になるように変更します:
insight.on('return', function(ctx, frame) {
let result = ctx.returnValue(frame);
ctx.returnNow(Math.abs(result));
}, {
roots: true,
rootNameFilter: 'plus'
});
Insightフックは、plus
関数の戻り時に実行され、returnValue
ヘルパー関数を使用して、現在のframe
オブジェクトから計算された戻り値を取得します。その後、値を変更でき、かわりにreturnNow
によって新しい結果が返されます。returnValue
関数は、提供されたctx
オブジェクトで常に使用できますが、on('return', ...)
フックで使用される場合にのみ意味のある値を返します。
最小限のオーバヘッド
スクリプトの適用時にGraalVM Insightでパフォーマンスのオーバーヘッドが発生するかというと、答えは「いいえ」または「最小限」になります。オーバーヘッドはスクリプトの動作によって異なります。複雑な計算をコード・ベース全体に追加して分散すると、その計算の代価を支払います。ただし、これはインストゥルメンテーションではなくコードのオーバーヘッドになります。単純なfunction-count.js
スクリプトを使用して、オーバーヘッドを測定します。
var count = 0;
function dumpCount() {
print(`${count} functions have been executed`);
}
insight.on('enter', function(ev) {
count++;
}, {
roots: true
});
insight.on('close', dumpCount);
sieve.jsサンプルの50回の反復でスクリプトを使用します。このサンプルは、エラトステネスの篩のバリアントを使用して10万個の素数を計算します。計算を50回繰り返すと、ランタイムがウォームアップし、適切に最適化できます。最適な実行は次のとおりです:
graalvm/bin/js sieve.js | grep -v Computed
Hundred thousand prime numbers in 75 ms
Hundred thousand prime numbers in 73 ms
Hundred thousand prime numbers in 73 ms
ここで、GraalVM Insightスクリプトを有効にして実行した場合の実行時間と比較します:
graalvm/bin/js --insight=function-count.js sieve.js | grep -v Computed
Hundred thousand prime numbers in 74 ms
Hundred thousand prime numbers in 74 ms
Hundred thousand prime numbers in 75 ms
72784921 functions have been executed
違いは2ミリ秒です。GraalVM Insightでは、アプリケーション・コードとインサイト収集スクリプトの違いが融合され、すべてのコードが1つのものとして機能します。count++
の呼出しは、アプリケーション関数のROOT
を表すすべての場所で、アプリケーションに自然に含まれるものとなります。
ローカルへのアクセス時の最小限のオーバヘッド
GraalVM Insightは、ほぼ無料でローカル変数にアクセスできます。ローカル変数にアクセスするGraalVM Insightコードは、変数を定義する実際の関数コードと融合され、目に見える速度低下はありません。
このことは、10万個の素数を計算する次のsieve.jsアルゴリズムで示すことができます。見つかった素数は、次の関数を使用して作成されたリンク・リストに保持されます:
function Filter(number) {
this.number = number;
this.next = null;
this.last = this;
}
まず、計算を50回呼び出し、最後のラウンドを終了するまでの時間を測定して、動作をテストします:
graalvm/bin/js -e "var count=50" --file sieve.js | grep Hundred | tail -n 1
Hundred thousand prime numbers in 73 ms
次に、new Filter
コンストラクタのコールなど、新しい素数スロットの各割当てを監視することにより、システムを「困らせ」ます:
var sum = 0;
var max = 0;
insight.on('enter', (ctx, frame) => {
sum += frame.number;
if (frame.number > max) {
max = frame.number;
}
}, {
roots: true,
rootNameFilter: 'Filter'
});
insight.on('return', (ctx, frame) => {
log(`Hundred thousand prime numbers from 2 to ${max} has sum ${sum}`);
sum = 0;
max = 0;
}, {
roots: true,
rootNameFilter: 'measure'
});
new Filter(number)
が割り当てられるたびに、number
の最大値(見つかった最大素数など)が取得され、これまでに見つかったすべての素数のsum
も取得されます。measure
のメイン・ループが終了すると、たとえば10万個の素数がある場合、その結果が出力されます。
ここで、次を試します:
graalvm/bin/js -e "var count=50" --insight=sieve-filter1.js --file sieve.js | grep Hundred | tail -n 2
Hundred thousand prime numbers from 2 to 1299709 has sum 62260698721
Hundred thousand prime numbers in 74 ms
速度低下はまったくありません。GraalVM Insightは、GraalVMコンパイラのインライン化アルゴリズムと組み合せると、パフォーマンスをほとんど低下させることなく、優れたインストゥルメンテーション機能を実現します。
実行スタックへのアクセス
GraalVM Insightで実行スタック全体にアクセスする方法があります。次のコード・スニペットは、その方法を示しています:
insight.on("return", function(ctx, frame) {
print("dumping locals");
ctx.iterateFrames((at, vars) => {
for (let p in vars) {
print(` at ${at.name} (${at.source.name}:${at.line}:${at.column}) ${p} has value ${vars[p]}`);
}
});
print("end of locals");
}, {
roots: true
});
Insightフックがトリガーされるたびに、関数source.name
、line
およびcolumn
のname
を使用して現在の実行スタックが出力されます。さらに、各フレームですべてのローカルvars
の値も出力されます。また、次のように既存の変数に新しい値を割り当てることで、既存の変数の値を変更することもできます: vars.n = 42
。スタック全体へのアクセスは柔軟ですが、現在の実行フレーム内のローカルへのアクセスとは異なり、高速な操作ではないため、プログラムを引き続きフル・スピードで実行する場合は、よく考えて使用してください。
ヒープ・ダンプ
GraalVM Insightを使用すると、実行中にプログラム・ヒープのリージョンのスナップショットを作成できます。--heap.dump=/path/to/output.hprof
オプションを通常の --insight
オプションと一緒に使用します。Insightスクリプトは、dump
関数を使用してheap
オブジェクトにアクセスします。必要な場所にフックを配置し、適切なタイミングでヒープをダンプします:
insight.on('return', (ctx, frame) => {
heap.dump({
format: '1.0',
depth: 50, // set max depth for traversing object references
events: [
{
stack : [
{
at : ctx, // location of dump sieve.js:73
frame : {
// assemble frame content as you want
primes : frame.primes, // capture primes object
cnt : frame.cnt, // capture cnt value
},
depth : 10 // optionally override depth to ten references
}, // there can be more stack elements like this one
]
},
// there can be multiple events like the previous one
],
});
throw 'Heap dump written!';
}, {
roots: true,
rootNameFilter: 'measure'
});
コード・スニペットをdump.js
ファイルとして保存します。sieve.jsファイルを取得し、次のように起動します:
graalvm/bin/js --insight=dump.js --heap.dump=dump.hprof --file sieve.js
dump.hprof
ファイルは、プログラム・メモリーの状態を取得するmeasure
関数の最後に作成されます。生成された.hprof
ファイルをVisualVMやNetBeansなどの通常のツールで調べます:
前の図は、sieve.jsスクリプトのmeasure
関数の最後で取得されたヒープ・ダンプを示しています。この関数は、10万個(変数cnt
で使用可能な数)の素数を計算しました。この図は、2
から17
までの素数を保持するリンク・リストFilter
を示しています。リンク・リストの残りの部分は、unreachable
オブジェクトの背後に隠されています(深さ10
までの参照のみがリクエストされました)。最後の変数x
は、すべての素数を計算するために検索された自然数の数を示しています。
ヒープ・ダンプ・キャッシュ
ヒープ・ダンプ・プロセスを高速化し、結果のダンプを最適化するために、メモリー・キャッシュを有効にできます。キャッシュへのダンプ間でプロパティが変更されないオブジェクトは1回のみ格納され、結果として生成されるヒープ・ダンプ・サイズが削減されます。たとえば、--heap.cacheSize=1000
オプションを追加すると、1000イベントのメモリー・キャッシュが使用されます。デフォルトでは、キャッシュはファイルにダンプされ、いっぱいになると消去されます。このポリシーは、--heap.cacheReplacement=lru
オプションによって変更できます。これにより、キャッシュ・サイズ制限に達すると、キャッシュ内の最新のダンプ・イベントは保持され、最も古いものが削除されます。
キャッシュをヒープ・ダンプ・ファイルにフラッシュするには、heap.flush()
を明示的にコールする必要があります。
GraalVM Insight APIに関するノート
insight
オブジェクトを介して公開されるGraalVM Insight APIは、互換性のある方法で実装されます。GraalVM Insight APIは、このリンクで確認できます。insight
オブジェクトのプロパティおよび関数は、そのjavadocの一部として提供されています。
将来のバージョンでは新機能が追加されますが、一度公開されたものはすべて引き続き機能します。スクリプトがいくつかの新機能に依存している場合、公開されているAPIのバージョンを確認することがあります:
print(`GraalVM Insight version is ${insight.version}`);
API内の新しい要素には、関連する@since
タグが付けられ、関連付けられた機能が使用可能になる最小バージョンが記述されます。