GraalVM Insight
GraalVM Insight is a multipurpose, flexible tool for writing reliable microservices solutions that traces program runtime behavior and gathers insights.
The dynamic nature of the tool helps users to selectively apply tracing pointcuts on already running applications with no loss of performance. Insight also provides detailed access to runtime behavior of a program, allowing users to inspect values and types at invocation or allocation sites. GraalVM Insight further permits users to modify computed values, interrupt execution, and quickly experiment with behavioral changes without modifying the application code.
This page provides information on GraalVM Insight as of the 20.1 version. To learn about Insight on versions 20.0 and 19.3, proceed here.
Note: the GraalVM Insight tool is offered as a technology preview and requires the user to
pass the --experimental-options
option in order to enable the --insight
instrument.
Start Using GraalVM Insight
- Create a simple source-tracing.js script with the following content:
insight.on('source', function(ev) { print(`Loading ${ev.characters.length} characters from ${ev.name}`); });
- Having set
JAVA_HOME
to the GraalVM home directory, start thenode
launcher with the--insight
tool and observe what scripts are being loaded and evaluated:$JAVA_HOME/bin/node --experimental-options --insight=source-tracing.js --js.print -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
The source-tracing.js script used the provided
insight
object to attach a source listener to the runtime. Whenever the script was loaded, the listener got notified of it and could take an action – printing the length and name of the processed script.
The Insight information can be collected to a print statement or a histogram. The following function-histogram-tracing.js script counts all method invocations and dumps the most frequent ones when the execution of a program is over:
var map = new Map();
function dumpHistogram() {
print("==== Histogram ====");
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', dumpHistogram);
The map
is a global variable shared inside of the Insight script that allows the
code to share data between the insight.on('enter')
function and the dumpHistogram
function. The latter is executed when the node process execution is over
(registered via insight.on('close', dumpHistogram
). Invoke it as:
$JAVA_HOME/bin/node --experimental-options --insight=function-histogram-tracing.js --js.print -e "print('The result: ' + 6 * 7)"
The result: 42
=== Histogram ===
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
=================
Polyglot Tracing
The previous examples were written in JavaScript, but due to GraalVM’s polyglot nature, you can take the same instrument and use it in a program written in, e.g., the Ruby language.
- Create the source-trace.js file:
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); } });
- Prepare the helloworld.rb Ruby file:
puts 'Hello from GraalVM Ruby!'
- Apply the JavaScript instrument to the Ruby program:
$JAVA_HOME/bin/ruby --jvm --polyglot --experimental-options --insight=source-trace.js helloworld.rb JavaScript instrument observed load of helloworld.rb Hello from GraalVM Ruby!
It is necessary to start the Ruby launcher with the
--polyglot
parameter, as the source-tracing.js script remains written in JavaScript.
A user can instrument any language on top of GraalVM, but also the Insight scripts can be written in any of the GraalVM supported languages (implemented with the Truffle language implementation framework).
- Create the source-tracing.rb Ruby file:
puts "Ruby: Initializing GraalVM Insight script" insight.on('source', ->(ev) { name = ev[:name] puts "Ruby: observed loading of #{name}" }) puts 'Ruby: Hooks are ready!'
- Launch a Node.js application and instrument it with the Ruby script:
$JAVA_HOME/bin/node --jvm --polyglot --experimental-options --insight=source-tracing.rb --js.print -e "print('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
Inspecting Values
GraalVM Insight not only allows one to trace where the program execution is happening,
it also offers access to values of local variables and function arguments during
program execution. You can, for example, write an instrument that shows the value of
argument n
in the function fib
:
insight.on('enter', function(ctx, frame) {
print('fib for ' + frame.n);
}, {
roots: true,
rootNameFilter: (name) => 'fib' === name
});
This instrument uses the second function argument, frame
, to get access to values of
local variables inside every instrumented function. The above script
also uses rootNameFilter
to apply its hook only to the function named 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));
When the instrument is stored in a fib-trace.js
file and the actual code is in
fib.js
, invoking the following command yields detailed information about the
program execution and parameters passed between function invocations:
$JAVA_HOME/bin/node --experimental-options --insight=fib-trace.js --js.print fib.js
fib for 3
fib for 2
fib for 1
fib for 0
fib for 1
Two is the result 2
To learn more about GraalVM Insight, go to Insight Manual.
Documentation on the insight
object properties and functions is available as part of the Javadoc.