- JDK 23対応GraalVM (最新)
- JDK 24対応GraalVM (早期アクセス)
- JDK 21対応GraalVM
- JDK 17対応GraalVM
- アーカイブ
- 開発ビルド
Insight マニュアル
GraalVM Insight は、信頼性の高いアプリケーションを作成するための多目的で柔軟なツールです。このツールの動的な性質により、既存のアプリケーションにトレースポイントカットを選択的に適用でき、パフォーマンスの低下もありません。
ある程度のスキルを持つハッカーであれば、いわゆる **Insight スニペット** を簡単に作成し、実際のアプリケーションに動的に適用できます。これにより、プログラムの速度を損なうことなく、プログラムの実行と動作に関する究極の洞察を得ることができます。
目次 #
- クイックスタート
- Hotness Top 10 の例
- あらゆる GraalVM 言語に Insight を適用する
- JavaScript での Insight
- Python での Insight
- Ruby での Insight
- R での Insight
- C コードへの Insight
- 値の検査
- ローカル変数の変更
- 特定の場所への Insight
- Node.JS での Insight 初期化の遅延
- 例外処理
- 実行のインターセプトと変更
- 最小限のオーバーヘッド
- ローカル変数へのアクセス時の最小限のオーバーヘッド
- 実行スタックへのアクセス
- GraalVM Insight API に関する注意
- ヒープダンプ
クイックスタート #
必須の **HelloWorld** の例から始めましょう。次の内容で *source-tracing.js* という名前のスクリプトを作成します。
insight.on('source', function(ev) {
if (ev.characters) {
print(`Loading ${ev.characters.length} characters from ${ev.name}`);
}
});
GraalVM の node
ランチャーで実行し、--insight
インストラメントオプションを追加します。ロードおよび評価されるスクリプトを観察します。
./bin/node --js.print --experimental-options --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
オブジェクトを使用して、ランタイムに **source** リスナーをアタッチしました。そのため、node
がスクリプトをロードするたびに、リスナーはそれを通知され、アクションを実行できました(この場合は、処理されたスクリプトの長さと名前を出力します)。
Hotness Top 10 の例 #
洞察情報の収集は、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
関数の間でデータを共有できるようにします。後者は、node
プロセス実行が終了したときに実行されます(insight.on('close', dumpHotness)
を介して登録されます)。プログラムを実行します。
./bin/node --js.print --experimental-options --insight=function-hotness-tracing.js -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
========================
node
プロセスが終了すると、関数呼び出しの名前とカウントを含むテーブルが出力されます。
あらゆる GraalVM 言語に Insight を適用する #
前の例は 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 プログラムに適用します。表示される内容は次のとおりです。
./bin/ruby --polyglot --insight=source-trace.js helloworld.rb
JavaScript instrument observed load of helloworld.rb
Hello from GraalVM Ruby!
*source-tracing.js* スクリプトは JavaScript で記述されたままなので、GraalVM の Ruby ランチャーを --polyglot
パラメーターで起動する必要があります。
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* の上で実行します。これは、エラトステネスのふるいの変種を使用して 100,000 個の素数を計算するサンプルスクリプトです。
./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 の例を示します。
GraalVM Insight スクリプトを Python で記述できます。このような洞察は、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* に適用します。
`./bin/js --polyglot --insight=agent.py agent-fib.js`
注:Python サポートが有効になっていることを確認してください。多言語プログラミングガイドを参照してください。
Ruby での Insight #
GraalVM Insight スクリプトを Ruby で記述できます。このような洞察は、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 スクリプトでインストルメントします。
./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
の値を出力します。
./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* プログラムをトレースします。
./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=`.../bin/lli --print-toolchain-path`
${TOOLCHAIN_PATH}/clang agent-sieve.c -lm -o sieve
./bin/lli sieve
GraalVM clang
ラッパーは、通常の clang
に LLVM ビットコード情報を通常のネイティブコードとともに sieve
実行可能ファイルに保持するように指示する特別なオプションを追加します。GraalVM の lli
インタプリタは、ビットコードを使用してプログラムをフルスピードで解釈できます。ちなみに、./sieve
による直接ネイティブ実行の結果と ./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
実行を停止します。次のようにプログラムを実行します。
./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'
});
新しい素数がフィルターリストに追加されるたびにメッセージを出力します。
./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* に格納されている場合、次のコマンドを呼び出すと、プログラムの実行と関数呼び出しの間で渡されるパラメーターに関する詳細情報が得られます。
./bin/node --js.print --experimental-options --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
フックは、既存の変数を変更することのみ可能です。新しい変数を導入することはできません。そうしようとすると例外が発生します。
特定の場所への Insight #
特定のコードの場所にある変数にアクセスするには、at
オブジェクトに必須のソース指定のいずれか 1 つだけを含めることができます。ソースファイルパスと一致する正規表現を持つ 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
}
});
それは私たちに与えます
./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
関数を呼び出して、すべてのノードモジュールにアクセスしながら実際の初期化を実行します。スクリプトは以下のように実行できます。
./bin/node --js.print --experimental-options --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
関数への呼び出しを待機し、その時点で独自の例外を発行して、ユーザープログラムの実行を効果的に中断します。その結果、以下が得られます。
./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
関数の呼び出し元に戻ります。インサイトon('enter', ...)
が適用されたため、たとえば、関数の実際の本体が実行される前に、plus
メソッドの本体はまったく実行されません。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回繰り返すと、ランタイムはウォームアップして適切に最適化することができます。最適な実行は次のとおりです。
./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スクリプトを有効にして実行した場合の実行時間と比較します。
./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回呼び出して、最後のラウンドが完了するまでの時間を測定することで、動作をテストします。
./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万個の素数があることを意味します)、結果が出力されます。
次に、以下を試してください。
./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フックがトリガーされるたびに、関数のname
、source.name
、line
、column
を含む現在の 実行スタックが出力されます。さらに、各フレームですべてのローカルvars
の値も出力します。既存の変数の値に新しい値を代入することで、値を変更することもできます。vars.n = 42
。スタック全体へのアクセスは柔軟ですが、現在の 実行フレーム内のローカル変数へのアクセスとは異なり、高速な操作ではないため、プログラムをフルスピードで実行し続けたい場合は、賢明に使用してください。
ヒープダンプ #
GraalVM Insightを使用して、実行中にプログラムヒープの領域のスナップショットを作成できます。通常の--insight
オプションと一緒に--heap.dump=/path/to/output.hprof
オプションを使用します。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ファイルを取得し、次のように起動します。
./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
タグが付いています。