- GraalVM Debugging and Monitoring Tools
- Debugger
- Profiler
- Code Coverage
Shows what percent of each element was covered during execution
Path | Statements | Lines | Roots
/path/to/primes.js | 20.69% | 26.67% | 22.22%
Shows what percent of each element was covered during execution
Path | Statements | Lines | Roots
/path/to/primes.js | 100.00% | 100.00% | 100.00%
T-Trace
GraalVM VisualVM
Visual Studio Code Extensions for GraalVM
Ideal Graph Visualizer
GraalVM Debugging and Monitoring Tools
GraalVM provides a set of tools for developers, integrators, and ITadministrators to debug and monitor GraalVM and deployed applications.
Debugger
GraalVM supports debugging of guest language applications and provides abuilt-in implementation ofthe Chrome DevTools Protocol.This allows you to attach compatible debuggers such asChrome Developer Toolsto GraalVM.
To debug guest language applications, pass the —inspect
option to thecommand-line launcher, as in the followingexample with a Node.js hello world program:
var http = require('http');
var server = http.createServer(function (request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello World!\n");
});
server.listen(8000);
console.log("Server running at http://localhost:8000/");
- Save this program as
HelloWorld.js
and then run:
$ node --inspect --jvm HelloWorld.js
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome:
chrome-devtools://devtools/bundled/js_app.html?ws=127.0.1.1:9229/76fcb6dd-35267eb09c3
Server running at http://localhost:8000/
Navigate to
http://localhost:8000/
in your browser to launch the node application.Open the
chrome-devtools:…
link in a separate Chrome browser tab.Navigate to the
HelloWorld.js
file and submit a breakpoint at line 4.Refresh the node.js app and you can see the breakpoint hit.
You can inspect the stack, variables, evaluate variables and selected expressionsin a tooltip, and so on. By hovering a mouse over the response
variable, forinstance, you can inspect its properties as can be seen in the screenshot below:
Consult theJavaScript Debugging Referencefor details on Chrome DevTools debugging features.
This debugging process applies to all guest languages that GraalVM supports.Other languages such as R and Ruby can be debugged as easily as JavaScript,including stepping through language boundaries during guest languageinteroperability.
Inspect Options
Node Launcher
The node.js implementation that GraalVM provides accepts the same options asnode.js built on the V8 JavaScript engine, such as:
--inspect[=[host:]<port number>]
Enables the inspector agent and listens on port 9229 by default. To listen on adifferent port, specify the optional port number.
--inspect-brk[=[host:]<port number>]
Enables the inspector agent and suspends on the first line of the applicationcode. Listens on port 9229 by default, to listen on a different port, specifythe optional port number. This applies to the node
launcher only.
Other Language Launchers
Other guest language launchers such as js
, python
, Rscript
, ruby
, lli
and polyglot
accept the —inspect[=[host:]<port number>]
option, but suspend on the first line ofthe application code by default.
--inspect.Suspend=(true|false)
Disables the initial suspension if you specify —inspect.Suspend=false
.
Additional Common Inspect Options
All launchers accept also following additional options:
--inspect.Path=<path>
Allows to specify a fixed path that generates a predictable connection URL. Bydefault, the path is randomly generated.
--inspect.SourcePath=<source path>
This option specifies a list of directories or ZIP/JAR files representing the source path. When the inspected application contains relative references to source files, their content is loaded from locations resolved with respect to this source path. It is useful during LLVM debugging, for instance.The paths are delimited by :
on UNIX systems and by ;
on MS Windows.
--inspect.Secure=(true|false)
When true, use TLS/SSL to secure the debugging protocol. Besides changing the WS(web socket) protocol to WSS, the HTTP endpoint that serves metadata about the debuggeeis also changed to HTTPS. This is not compatible e.g. withchrome://inspect page, which is not able to provide the debuggeeinformation and launch the debugger then. Launch debugging via the printed WSS URL directly.
Use the standard javax.net.ssl.*
system options to provide information aboutkeystore with the TLS/SSL encryption keys, or following options:
—inspect.KeyStore
keystore file path,—inspect.KeyStoreType
keystore file type (defaults to JKS),—inspect.KeyStorePassword
keystore password,—inspect.KeyPassword
password for recovering keys, if it’s different from the keystore password.
--inspect.WaitAttached=(true|false)
When true, no guest language source code is executed until the inspector clientis attached. Unlike —inspect.Suspend=true
, the execution is resumed rightafter the client is attached. That assures that no execution is missed by theinspector client. It is false
by default.
Advanced Debug Options
Following options are for language experts and language developers:
--inspect.Initialization=(true|false)
When true, inspect the language initialization phase. When initial suspension isactive, suspends at the beginning of language initialization and not necessarilyat the beginning of the application code. It’s false
by default.
--inspect.Internal=(true|false)
When true, internal sources are inspected as well. Internal sources may providelanguage implementation details. It’s false
by default.
Programmatic Launch of Inspector Backend
Embedders can provide the appropriate inspector options to the Engine/Context
to launch the inspector backend. The following code snippet provides an example ofa possible launch:
String port = "4242";
String path = "session-identifier";
String remoteConnect = "true";
Context context = Context.newBuilder("js")
.option("inspect", port)
.option("inspect.Path", path)
.option("inspect.Remote", remoteConnect)
.build();
String hostAdress = "localhost";
String url = String.format(
"chrome-devtools://devtools/bundled/js_app.html?ws=%s:%s/%s",
hostAdress, port, path);
// Chrome Inspector client can be attached by opening the above url in Chrome
Profiler
GraalVM provides Profiling command line tools that let you optimize your codethrough analysis of CPU and memory usage.
Most applications spend 80 percent of their runtime in 20 percent of the code.For this reason, to optimize the code, it is essential to know where theapplication spends its time. GraalVM provides simple command line tools forruntime and memory profiling to help you analyze and optimize your code.
In this section, we use an example application to demonstrate the profilingcapabilities that GraalVM offers. This example application uses a basic primenumber calculator based on the ancient Sieve of Eratosthenesalgorithm.
- Copy the following code into a new file named
primes.js
:
class AcceptFilter {
accept(n) {
return true
}
}
class DivisibleByFilter {
constructor(number, next) {
this.number = number;
this.next = next;
}
accept(n) {
var filter = this;
while (filter != null) {
if (n % filter.number === 0) {
return false;
}
filter = filter.next;
}
return true;
}
}
class Primes {
constructor() {
this.number = 2;
this.filter = new AcceptFilter();
}
next() {
while (!this.filter.accept(this.number)) {
this.number++;
}
this.filter = new DivisibleByFilter(this.number, this.filter);
return this.number;
}
}
var primes = new Primes();
var primesArray = [];
for (let i = 0; i < 5000; i++) {
primesArray.push(primes.next());
}
console.log(`Computed ${primesArray.length} prime numbers. ` +
`The last 5 are ${primesArray.slice(-5)}.`);
- Run
js primes.js
.
The example application should print output as follows:
$> js primes.js
Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
This code takes a moment to compute so let’s see where all the time is spent.
- Run
js primes.js —cpusampler
to enable CPU sampling.
The CPU sampler tool should print output for the example application as follows:
$ ./js primes.js --cpusampler
Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
---------------------------------------------------------------------------------------------------
Sampling Histogram. Recorded 1184 samples with period 1ms
Self Time: Time spent on the top of the stack.
Total Time: Time the location spent on the stack.
Opt %: Percent of time spent in compiled and therfore non-interpreted code.
---------------------------------------------------------------------------------------------------
Name | Total Time | Opt % || Self Time | Opt % | Location
---------------------------------------------------------------------------------------------------
next | 1216ms 98.5% | 87.9% || 1063ms 85.9% | 99.0% | primes.js~31-37:564-770
accept | 159ms 11.2% | 22.7% || 155ms 12.5% | 14.8% | primes.js~13-22:202-439
:program | 1233ms 100.0% | 0.0% || 18ms 1.5% | 0.0% | primes.js~1-47:0-1024
constructor | 1ms 0.1% | 0.0% || 1ms 0.1% | 0.0% | primes.js~7-23:72-442
---------------------------------------------------------------------------------------------------
The sampler prints an execution time histogram for each JavaScript function. By default, CPU sampling takes a sample every single millisecond. From the result we can see that roughly 96 percent of the time is spent in the DivisibleByFilter.accept
function.
accept(n) {
var filter = this;
while (filter != null) {
if (n % filter.number === 0) {
return false;
}
filter = filter.next;
}
return true;
}
Now find out more about this function by filtering the samples and include statements in the profile in addition to methods.
- Run
js primes.js —cpusampler —cpusampler.Mode=statements —cpusampler.FilterRootName=*accept
to collect statement samples for all functions that end withaccept
.
$ js primes.js --cpusampler --cpusampler.Mode=statements --cpusampler.FilterRootName=*accept
Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
----------------------------------------------------------------------------------------------------
Sampling Histogram. Recorded 1567 samples with period 1ms
Self Time: Time spent on the top of the stack.
Total Time: Time the location spent on the stack.
Opt %: Percent of time spent in compiled and therfore non-interpreted code.
----------------------------------------------------------------------------------------------------
Name | Total Time | Opt % || Self Time | Opt % | Location
----------------------------------------------------------------------------------------------------
accept~16-18 | 436ms 27.8% | 94.3% || 435ms 27.8% | 94.5% | primes.js~16-18:275-348
accept~15 | 432ms 27.6% | 97.0% || 432ms 27.6% | 97.0% | primes.js~15:245-258
accept~19 | 355ms 22.7% | 95.5% || 355ms 22.7% | 95.5% | primes.js~19:362-381
accept~17 | 1ms 0.1% | 0.0% || 1ms 0.1% | 0.0% | primes.js~17:322-334
----------------------------------------------------------------------------------------------------
Roughly 30 percent of the time is spent in this if condition:
if (n % filter.number === 0) {
return false;
}
The if condition contains an expensive modulo operation, which might explain the runtime of the statement.
Now use the CPU tracer tool to collect execution counts of each statement.
- Run
js primes.js —cputracer —cputracer.TraceStatements —cputracer.FilterRootName=*accept
to collect execution counts for all statements in methods ending withaccept
.
$ js primes.js --cputracer --cputracer.TraceStatements --cputracer.FilterRootName=*accept
Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
-----------------------------------------------------------------------------------------
Tracing Histogram. Counted a total of 351278226 element executions.
Total Count: Number of times the element was executed and percentage of total executions.
Interpreted Count: Number of times the element was interpreted and percentage of total executions of this element.
Compiled Count: Number of times the compiled element was executed and percentage of total executions of this element.
-----------------------------------------------------------------------------------------
Name | Total Count | Interpreted Count | Compiled Count | Location
-----------------------------------------------------------------------------------------
accept | 117058669 33.3% | 63575 0.1% | 116995094 99.9% | primes.js~15:245-258
accept | 117053670 33.3% | 63422 0.1% | 116990248 99.9% | primes.js~16-18:275-348
accept | 117005061 33.3% | 61718 0.1% | 116943343 99.9% | primes.js~19:362-381
accept | 53608 0.0% | 1857 3.5% | 51751 96.5% | primes.js~14:215-227
accept | 53608 0.0% | 1857 3.5% | 51751 96.5% | primes.js~13-22:191-419
accept | 48609 0.0% | 1704 3.5% | 46905 96.5% | primes.js~17:322-334
accept | 4999 0.0% | 153 3.1% | 4846 96.9% | primes.js~21:409-412
accept | 1 0.0% | 1 100.0% | 0 0.0% | primes.js~2-4:25-61
accept | 1 0.0% | 1 100.0% | 0 0.0% | primes.js~3:52-55
-----------------------------------------------------------------------------------------
Now the output shows execution counters for each statement, instead of timing information. Tracing histograms often provides insights into the behavior of the algorithm that needs optimization.
Lastly, use the memory tracer tool for capturing allocations, for which GraalVM currently provides experimental support. Node, —memtracer
as an experimental tool must be preceded by the —experimental-options
command line option.
- Run
js primes.js —experimental-options —memtracer
to display source code locations andcounts of reported allocations.
$ js primes.js --experimental-options --memtracer
Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
------------------------------------------------------------
Location Histogram with Allocation Counts. Recorded a total of 5013 allocations.
Total Count: Number of allocations during the execution of this element.
Self Count: Number of allocations in this element alone (excluding sub calls).
------------------------------------------------------------
Name | Self Count | Total Count | Location
------------------------------------------------------------
next | 5000 99.7% | 5000 99.7% | primes.js~31-37:537-737
:program | 11 0.2% | 5013 100.0% | primes.js~1-46:0-966
Primes | 1 0.0% | 1 0.0% | primes.js~25-38:454-739
------------------------------------------------------------
This output shows the number of allocations which were recorded per function. For each prime number that was computed, the program allocates one object in next
and one in constructor
of DivisibleByFilter
. Allocations are recorded independently of whether they could get eliminated by the compiler. The Graal compiler is particularly powerful in optimizing allocations and can push allocations into infrequent branches to increase execution performance. The GraalVM team plans to add information about memory optimizations to the memory tracer in the future.
Tool Reference
Use the —help:tools
option in all guest language launchers to displayreference information for the CPU sampler, the CPU tracer, and the memory tracer.
The current set of available options is as follows:
CPU Sampler Command Options
—cpusampler
: enables the CPU sampler. Disabled by default.—cpusampler.Delay=<Long>
: delays the sampling for the given number of milliseconds (default: 0).—cpusampler.FilterFile=<Expression>
: applies a wildcard filter for sourcefile paths. For example,program.sl
. The default is ∗.—cpusampler.FilterLanguage=<String>
: profiles languages only with thematching mime-type. For example,+
. The default is no filter.—cpusampler.FilterRootName=<Expression>
: applies a wildcard filter forprogram roots. For example,Math.*
. The default is ∗.—cpusampler.GatherHitTimes
: saves a timestamp for each taken sample. The default is false.—cpusampler.Mode=<Mode>
: describes level of sampling detail. Please note that increased detail can lead to reduced accuracy.exclude_inlined_roots
samples roots excluding inlined functions (enabled by default);roots
samples roots including inlined functions;statements
samples all statements.
—cpusampler.Output=<Output>
: prints a ‘histogram’ or ‘calltree’ as output.The default is ‘histogram’.—cpusampler.Period=<Long>
: specifies the period, in milliseconds, tosample the stack.—cpusampler.SampleInternal
: captures internal elements. The default isfalse.—cpusampler.StackLimit=<Integer>
: specifies the maximum number of stackelements.—cpusampler.SummariseThreads
: prints sampling output as a summary of all ‘per thread’ profiles. The default is false.
CPU Tracer Command Options
—cputracer
: enables the CPU tracer. Disabled by default.—cputracer.FilterFile=<Expression>
: applies a wildcard filter for sourcefile paths. For example,program.sl
. The default is ∗.—cputracer.FilterLanguage=<String>
: profiles languages only with thematching mime-type. For example,+
. The default is no filter.—cputracer.FilterRootName=<Expression>
: applies a wildcard filter forprogram roots. For example,Math.*
. The default is ∗.—cputracer.Output=<Output>
prints ahistogram
orjson
as output. The default ishistogram
.—cputracer.TraceCalls
: captures calls when tracing. The default is false.—cputracer.TraceInternal
: traces internal elements. The default is false.—cputracer.TraceRoots=<Boolean>
: captures roots when tracing. The defaultis true.—cputracer.TraceStatements
: captures statements when tracing. The defaultis false.
Memory Tracer Command Options
Warning: The memory tracer tool is currently an experimental tool. Make sure to prepend —experimental-options
flag to enable —memtracer
.
—experimental-options —memtracer
: enables the memory tracer. Disabled by default.—memtracer.FilterFile=<Expression>
: applies a wildcard filter for source file paths. For example,program.sl
. The default is ∗.—memtracer.FilterLanguage=<String>
: profiles languages only with the matching mime-type. For example,+
. The default is no filter.—memtracer.FilterRootName=<Expression>
: applies a wildcard filter for program roots. For example,Math.*
. The default is ∗.—memtracer.Output=<Format>
: prints a ‘typehistogram’, ‘histogram’, or ‘calltree’ as output. The default is ‘histogram’.—memtracer.StackLimit=<Integer>
: sets the maximum number of maximum stack elements.—memtracer.TraceCalls
: captures calls when tracing. The default is false.—memtracer.TraceInternal
: captures internal elements. The default is false.—memtracer.TraceRoots=<Boolean>
: captures roots when tracing. The default is true.—memtracer.TraceStatements
: captures statements when tracing. The default is false.
Code Coverage
As of version 19.3.0 GraalVM provides a Code coverage command line toolthat lets record and analyse the source code coverage of a particular executionof code.
Code coverage, as a percentage of source code lines, functions or statementscovered, is an important metric for understanding a particular source code execution, and is commonly associated with test quality (test coverage).Providing a visual coverage overview for individual lines of code showsthe developer which code paths are covered and which are not, giving insightinto the character of the execution which can, for example, inform furthertesting efforts.
In this section, we use an example application to demonstrate the codecoverage capabilities that GraalVM offers. This example application defines agetPrime
function that calculates the n-th prime using a basic prime numbercalculator based on the ancient Sieve of Eratosthenes algorithm. It also has a somewhat naive cache of the first 20 prime numbers.
1.Copy the following code into a new file named primes.js
:
class AcceptFilter {
accept(n) {
return true
}
}
class DivisibleByFilter {
constructor(number, next) {
this.number = number;
this.next = next;
}
accept(n) {
var filter = this;
while (filter != null) {
if (n % filter.number === 0) {
return false;
}
filter = filter.next;
}
return true;
}
}
class Primes {
constructor() {
this.number = 2;
this.filter = new AcceptFilter();
}
next() {
while (!this.filter.accept(this.number)) {
this.number++;
}
this.filter = new DivisibleByFilter(this.number, this.filter);
return this.number;
}
}
function calculatePrime(n) {
var primes = new Primes();
var primesArray = [];
for (let i = 0; i < n; i++) {
primesArray.push(primes.next());
}
return primesArray[n-1];
}
function getPrime(n) {
var cache = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71];
var n = arguments[0];
if (n > cache.length) { return calculatePrime(n); }
return cache[n-1];
}
// TESTS
console.assert(getPrime(1) == 2);
console.assert(getPrime(10) == 29);
Notice that the last couple of lines are assertions which we will, for illustration, treat as unit tests.
2.Run js primes.js
. The example application should print no output, since all the assertions pass. But how well do the assertions test the implementation?
3.Run js primes.js —coverage
to enable code coverage. The code coverage tool should print output for the example application as follows:
$ js primes.js —coverage
$ js primes.js —coverage
Code coverage histogram.
Shows what percent of each element was covered during execution
Path | Statements | Lines | Roots
/path/to/primes.js | 20.69% | 26.67% | 22.22%
The tracer prints a coverage histogram for each source file. We can see thatstatement coverage is roughly 20%, line coverage is 26% and root coverage (theterm root covers functions, methods, etc.) is 22.22%. This tells us that oursimple tests are not particularly good at exercising the source code. Now wewill figure out what parts of the code are not covered.
4.Run js primes.js —coverage —coverage.Output=detailed
. Prepare for a somewhat verbose output.Specifying the output as detailed
will print all the source code lines with acoverage annotation at the beginning. Due to potentially large output it isrecommended to combine this output mode with the —coverage.OutputFile
optionwhich prints the output directly to a file. The output for our exampleapplication is as follows:
$ js primes.js —coverage —coverage.Output=detailed
$ js primes.js —coverage —coverage.Output=detailed
Code coverage per line of code and what percent of each element was covered during execution (per source)
- indicates the line is covered during execution
- indicates the line is not covered during executionp indicates the line is part of a statement that was incidentally covered during executione.g. a not-taken branch of a covered if statement
Path | Statements | Lines | Roots /path/to/primes.js | 20.69% | 26.67% | 22.22%
class AcceptFilter { accept(n) {
- return true}}class DivisibleByFilter {constructor(number, next) {
- this.number = number;
- this.next = next;}accept(n) {
- var filter = this;
- while (filter != null) {
- if (n % filter.number === 0) {
- return false;
- }
- filter = filter.next;}
- return true;}}class Primes {constructor() {
- this.number = 2;
- this.filter = new AcceptFilter();}next() {
- while (!this.filter.accept(this.number)) {
- this.number++;}
- this.filter = new DivisibleByFilter(this.number, this.filter);
- return this.number;}}function calculatePrime(n) {
- var primes = new Primes();
- var primesArray = [];
- for (let i = 0; i < n; i++) {
- primesArray.push(primes.next());}
- return primesArray[n-1];}function getPrime(n) {
- var cache = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71];
- var n = arguments[0];p if (n > cache.length) { return calculatePrime(n); }
- return cache[n-1];}// TESTS
- console.assert(getPrime(1) == 2);
- console.assert(getPrime(10) == 29);
As the legend at the beginning of the output explains, lines that are covered bythe execution are preceded with a +
, lines not covered by the execution arepreceded with a -
. Lines that are covered partially (e.g. when an if
statement is covered, but only one branch is taken, we consider the other benchto be incidentally covered) are preceded with p
.
Looking at the output we can see that the calculatePrime
function and all itscalls are never executed. Looking again at the assertions and the getPrime
function it becomes clear that our tests always hit the cache, thus most of thecode is never executed. Let’s improve on that.
5.Add console.assert(getPrime(30) == 113);
to the end of the primes.js
fileand run js primes.js —coverage
. Since the new assertion added callsgetPrime
with 30 (our cache only has 20 entries) our coverage will look likethis:
$ js primes.js —coverage
$ js primes.js —coverage
Code coverage histogram.
Shows what percent of each element was covered during execution
Path | Statements | Lines | Roots
/path/to/primes.js | 100.00% | 100.00% | 100.00%
Integrating with other tools
The code coverage tool provides ways to integrate with other tools. Runningwith —coverage.Output=lcov
produces output in the commonly usedlcov format which is used by multiple tools(e.g. genhtml
) to display coverage data. Take a look at the next example thatshows how to visualise coverage of a Node.js app with Visual Studio Code.
1.Copy the following code into a new file named nodeapp.js
:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.get('/neverCalled', (req, res) => {
res.send('You should not be here')
})
app.get('/shutdown', (req, res) => {
process.exit();
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
2.Install the express module dependency: npm install express
.
3.Launch Visual Studio Code and install a code coverage plugin that supports lcov.We use Code Coverage Highlighter for this example, but other plugins should work similarly.
4.Run the nodeapp.js file with coverage enabled and configured:
node --coverage --coverage.Output=lcov \
--coverage.OutputFile=coverage/lcov.info \
nodeapp.js
Note that the Code Coverage Highlighter plugin looks for the lcov.info
file inthe coverage
directory by default, so we direct the output of the codecoverage tool there.
5.Visit localhost:3000/ in your browser, then visit localhost:3000/shutdown to close the app.
6.Open Visual Studio Code and open the folder containing the nodeapp.js
fileand coverage
directory and you should be greeted with an image similar to the following.
If you wish to integrate the data gathered by the GraalVM code coverage toolwith your own visualisation, the —coverage.Output=json
option results inthe output being a JSON file with the raw data gathered by the tracker.
T-Trace
GraalVM 19.3.0 introduced a T-Trace tool for tracing a program runtime behavior and insights gathering.It is a multipurpose, flexible tool for writing reliable microservices solutions.
The dynamic nature of the tool helps to selectively apply tracing pointcuts onalready running applications with no loss of performance. T-Trace insightsprovide detailed access to runtime behavior of a program allowing a user toinspect values, types at invocation or allocation sites, gathering usefulinformation and collecting and presenting it. The T-Trace insights permit tomodify computed values, interrupt execution and quickly experiment withbehavioral changes without modifying the application code.
Warning: The T-Trace functionality is offered as a technology preview and requires topass the —experimental-options
option to enable the —agentscript
instrument.
Start Using T-Trace
- Create a simple source-tracing.js script with following content:
agent.on('source', function(ev) {
print(`Loading ${ev.characters.length} characters from ${ev.name}`);
});
- Having set
JAVA_HOME
to GraalVM home directory, start thenode
launcher withthe—agentscript
instrument and observe what scripts are being loaded andevaluated:
$ $JAVA_HOME/bin/node --experimental-options --agentscript=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
The T-Tracing source-tracing.js script used the provided agent object toattach a source listener to the runtime. Whenever the script was loaded, thelistener got notified of it and could take an action – printing the length andname of processed script.
The insights information can be collected to a print statement or a histogram.The following function-histogram-tracing.js script counts all method invocationsand 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("===================");
}
agent.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
});
agent.on('close', dumpHistogram);
The map
is a global variable shared inside of the T-Trace script that allows thecode to share data between the agent.on('enter')
function and the dumpHistogram
function. The latter is executed when the node process execution is over(registered via agent.on('close', dumpHistogram
). Invoke as:
$ $JAVA_HOME/bin/node --experimental-options --agentscript=function-histogram-tracing.js -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 the polyglot natureof GraalVM, you can take the same instrument and use it in a program written ine.g. the Ruby language.
- Create source-trace.js file:
agent.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 --polyglot --experimental-options --agentscript=source-trace.js helloworld.rb
JavaScript instrument observed load of helloworld.rb
Hello from GraalVM Ruby!
It is necessary to start GraalVM’s Ruby launcher with —polyglot
parameter as the source-tracing.js script remains written in JavaScript.
A user can instrument any GraalVM language, but also the T-Trace scripts can bewritten in any GraalVM supported language.
- Create the source-tracing.rb Ruby file:
puts "Ruby: Initializing T-Trace script"
agent.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 written script:
$ $JAVA_HOME/bin/node --experimental-options --polyglot --agentscript=source-tracing.rb -e "print('With Ruby: ' + 6 * 7)" | grep Ruby:
Ruby: Initializing T-Trace 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
T-Trace not only allows one to trace where the program execution is happening,it also offers access to values of local variables and function arguments duringprogram execution. You can, for example, write instrument that shows the value ofargument n
in the function fib
:
agent.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 oflocal variables inside every instrumented function. The above T-Trace scriptalso uses rootNameFilter
to apply its hook only to 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 infib.js
, invoking the following command yields detailed information about theprogram execution and parameters passed between function invocations:
$ $JAVA_HOME/bin/node --experimental-options --agentscript=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
To learn more about T-Trace, proceed to the GraalVM Tools suite reference. The documentation of the agent object properties and functions is available as part of the Javadoc.
GraalVM VisualVM
GraalVM comes with GraalVM VisualVM, an enhanced version of the popularVisualVM tool which includes special heap analysisfeatures for the supported guest languages. These languages and features arecurrently available:
- Java: Heap Summary, Objects View, Threads View, OQL Console
- JavaScript: Heap Summary, Objects View, Thread View
- Python: Heap Summary, Objects View
- Ruby: Heap Summary, Objects View, Threads View
- R: Heap Summary, Objects View
Starting GraalVM VisualVM
To start GraalVM VisualVM execute jvisualvm
. Immediately after the startup,the tool shows all locally running Java processes in the Applications area,including the VisualVM process itself.
Important:GraalVM Native Image does not implement JVMTI agent, hence triggering heap dump creation from Applications area is impossible. Apply -H:+AllowVMInspection
flag with the native-image
tool for Native Image processes. This way your application will handle signals and get a heap dump when it receives SIGUSR1 signal. Guest language REPL process must be started also with the —jvm
flag to monitor it using GraalVM VisualVM. This functionality is available with GraalVM Enterprise Edition. It is not available in GraalVM open source version available on GitHub. See the Generating Native Heap Dumps page for details on creating heap dumps from a native image process.
Getting Heap Dump
To get a heap dump of, for example, a Ruby application for later analysis,first start your application, and let it run for a few seconds to warm up. Thenright-click its process in GraalVM VisualVM and invoke the Heap Dump action. Anew heap viewer for the Ruby process opens.
Analyzing Objects
Initially the Summary view for the Java heap is displayed. To analyze the Rubyheap, click the leftmost (Summary) dropdown in the heap viewer toolbar, choosethe Ruby Heap scope and select the Objects view. Now the heap viewer displaysall Ruby heap objects, aggregated by their type.
Expand the Proc node in the results view to see a list of objects of this type.Each object displays its logical value as provided by the underlyingimplementation. Expand the objects to access their variables and references,where available.
Now enable the Preview, Variables and References details by clicking the buttonsin the toolbar and select the individual ProcType objects. Where available, thePreview view shows the corresponding source fragment, the Variables view showsvariables of the object and References view shows objects referring to theselected object.
Last, use the Presets dropdown in the heap viewer toolbar to switch the viewfrom All Objects to Dominators or GC Roots. To display the heap dominators,retained sizes must be computed first, which can take a few minutes for theserver.rb example. Select the Objects aggregation in the toolbar to view theindividual dominators or GC roots.
Analyzing Threads
Click the leftmost dropdown in the heap viewer toolbar and select the Threadsview for the Ruby heap. The heap viewer now displays the Ruby thread stacktrace, including local objects. The stack trace can alternatively be displayedtextually by clicking the HTML toolbar button.
Reading JFR Snapshots
VisualVM tool bundled with GraalVM 19.2.x and later in both Community and Enterpriseeditions has the ability to read JFR snapshots – snapshots taken with JDKFlight Recorder (previously Java Flight Recorder). JFR is a tool for collectingdiagnostic and profiling data about a running Java application. It is integratedinto the Java Virtual Machine (JVM) and causes almost no performance overhead,so it can be used even in heavily loaded production environments.
To install the JFR support, released as a plugin:
- run
<GRAALVM_HOME>/bin/jvisualvm
to start VisualVM; - navigate to Tools > Plugins > Available Plugins to list all available plugins and install the VisualVM-JFR andVisualVM-JFR-Generic modules.The JFR snapshots can be opened using either the File > Load action or bydouble-clicking the JFR Snapshots node and adding the snapshot into the JFRrepository permanently. Please follow the documentation for your Java version tocreate JFR snapshots.
The JFR viewer reads all JFR snapshots created from Java 7 and newer and presents the data in typicalVisualVM views familiar to the tool users.
These views and functionality are currently available:
- Overview tab displays the basic information about the recorded process likeits main class, arguments, JVM version and configuration, and system properties.This tab also provides access to the recorded thread dumps.
- Monitor tab shows the process uptime and basic telemetry – CPU usage, Heapand Metaspace utilization, number of loaded classes and number of live & startedthreads.
- Threads tab reconstructs the threads timeline based on all events recorded inthe snapshot as precisely as possible, based on the recording configuration.
- Locks tab allows to analyze threads synchronization.
- File IO tab presents information on read and write events to the filesystem.
- Socket IO tab presents information on read and write events to the network.
- Sampler tab shows per-thread CPU utilization and memory allocations, and aheap histogram. There is also an experimental feature “CPU sampler” building CPUsnapshot from the recorded events. It does not provide an exact performanceanalysis but still helps to understand what was going on in the recordedapplication and where the CPU bottleneck might be.
- Browser tab provides a generic browser of all events recorded in the snapshot.
- Environment tab gives an overview of the recording machine setup and conditionlike CPU model, memory size, operating system version, CPU utilization, memoryusage, etc..
- Recording tab lists the recording settings and basic snapshot telemetry likenumber of events, total recording time, etc..
Warning: The support of JDK Flight Recorder is currently experimental. Some advanced features likeanalyzing JVM internals, showing event stack traces or support for creating JFRsnapshots from live processes are not available in this preview version and willbe addressed incrementally in the following releases.
Visual Studio Code Extensions for GraalVM
This section explains how to start using Visual Studio Code support forGraalVM, introduced in the 19.2 version. Visual Studio Code (from now onVS Code) is a source-code editor that provides embedded Git and GitHub control,syntax highlighting, code refactoring etc.. To enable a polyglot environment inVS Code, we created extensions for GraalVM supported languages: JS, Ruby, R,Python. This allows a simple registration of GraalVM as a runtime, code editingand debugging of polyglot applications.
The following extensions are available from VSCode Marketplace:
- GraalVM – a VS Code extension providing the basic environment for editing and debugging programs running on GraalVM and includes JavaScript and Node.js support by default.
- GraalVM R – a VS Code extension providing the basic support for editing and debugging R programs running on GraalVM.
- Graalvm Ruby – a VS Code extension providing the basic support for editing and debugging Ruby programs on GraalVM.
- GraalVM Python – a VS Code extension providing the basic support for editing and debugging Python programs running on GraalVM.
- GraalVM Extensions Pack – pack of all above listed extensions for simpler installation.
Install Extensions
GraalVM VSCode extensions can be simply installed from VSCode IDE Extensions panel by searching for GraalVM
GraalVM Extension
Upon the graalvm extension installation, launch VS Code bydouble-clicking on the icon in the launchpad or by typing code .
from the CLI.The user is then requested to provide a path to the GraalVM home directory.
For that purpose, next options can be used (invoke by Ctrl+Shift+P hot keys combination):
- Select GraalVM Installation - Provides the UI to select an already installed GraalVM. By default, the following locations are searched for the already installed GraalVM:
- the extension’s global storage
/opt
folder as the default RPM install locationPATH
environment variable contentGRAALVM_HOME
environment variable contentJAVA_HOME
environment variable content
- Install GraalVM - Downloads the latest GraalVM release from Github and installs it within the extension’s global storage.
- Install GraalVM Component - Downloads and installs one of the GraalVM’s optional components.
To verify whether the PATH
environment variable is valid, navigate to Code -> Preferences -> Settings -> Extensions -> GraalVM -> Home:
If the path to GraalVM home directory is provided properly, the following debugconfigurations can be used:
- Attach - Attaches debugger to a locally running GraalVM.
- Attach to Remote - Attaches debugger to the debug port of a remote GraalVM.
- Launch JavaScript - Launches JavaScript using GraalVM in a debug mode.
- Launch Node.js Application - Launches a Node.js network application using GraalVM in a debug mode.
Languages interoperability is one of the defining features of GraalVM, enabledwith Polyglot APIs. The code completioninvoked inside JavaScript sources provides items for Polyglot.eval(…)
,Polyglot.evalFile(…)
and Java.type(…)
calls.
For JavaScript sources opened in the editor, all the Polyglot.eval(…)
callsare detected and the respective embedded languages are injected to theirlocations. For example, having an R code snippet called via the Polyglot APIfrom inside the JavaScript source, the R language code is embedded inside thecorresponding JavaScript string and all VS Code’s editing features (syntaxhighlighting, bracket matching, auto closing pairs, code completion, etc.) treatthe content of the string as the R source code.
R Extension
Upon the extension installation in VS Code, GraalVM is checked forpresence of the R component and a user is provided with an option of an automaticinstallation of the missing component. The Ctrl+Shift+P command from theCommand Palette can be also used to invoke Install GraalVMComponent option to install the R component manually.
Once GraalVM contains the R component, the following debug configurationscan be used to debug your R scripts running on GraalVM:
- Launch R Script - Launches an R script using GraalVM in a debug mode.
- Launch R Terminal - Launches an integrated R terminal running on GraalVM in a debug mode.
Thanks to languages interoperability within GraalVM, the code completion invokedinside R sources provides items for eval.polyglot(…)
and new("<Java type>",…)
calls. For R sources opened in editor, all the eval.polyglot(…)
calls are detected and the respective embedded languages are injected to their locations.
Please note, this R extension depends on the basic support for R language and GraalVM extension in VS Code.
Ruby Extension
Similar to the above R extension installation, GraalVM is checked forpresence of the Ruby component and a user is provided with an option of an automaticinstallation of the missing component. The Ctrl+Shift+P command from theCommand Palette can be also used to invoke Install GraalVMComponent option to install the Ruby component manually.
Once GraalVM contains the Ruby component, Launch Ruby Script debugconfiguration can be used to run your Ruby script in a debug mode.
The code completion invoked inside Ruby sources provides items for Polyglot.eval(…)
, Polyglot.eval_file(…)
and Java.type(…)
calls. As with other languages, all the Polyglot.eval(…)
calls are detected and the respective embedded languages are injected to their locations. For example, the JavaScript language code is embedded inside the corresponding Ruby string and all VS Code’s editing features (syntax highlighting, bracket matching, auto closing pairs, code completion, etc.) treat the content of the string as the JavaScript source code.
This Ruby extension requires a default Ruby language support and GraalVM extension in VS Code.
Python Extension
GraalVM is checked for presence of the Python component and a user is providedwith an option of an automatic installation of the missing component, similar tothe previous R and Ruby extensions. The Ctrl+Shift+P command from the CommandPalette can be also used to invoke Install GraalVM Component option toinstall the Ruby component manually.
Once GraalVM contains the Ruby component, Launch Python Script debugconfiguration can be used to run the Python script in a debug mode.
Python VS Code extension requires a default Python language support and GraalVM extension in VS Code.
Ideal Graph Visualizer
Ideal Graph Visualizer or IGV is a developer tool, currently maintained as part of the GraalVM compiler, recommended for performance issues investigation.
The tool is essential for any language implementers building on top of GraalVM Enterprise Edition.It is available as a separate download on Oracle Technology Network and requires accepting the Oracle Technology Network Developer License.
Ideal Graph Visualizer is developed to view and inspect interim graph representations from GraalVM and Truffle compilations.
1.Unzip the downloaded package and enter bin
directory:
$ cd idealgraphvisualizer/bin
2.Launch the tool:
$ idealgraphvisualizer
3.Save the following code snippet as Test.rb
:
require 'json'
obj = {
time: Time.now,
msg: 'Hello World',
payload: (1..10).to_a
}
encoded = JSON.dump(obj)
js_obj = Polyglot.eval('js', 'JSON.parse').call(encoded)
puts js_obj[:time]
puts js_obj[:msg]
puts js_obj[:payload].join(' ')
4.From another console window, make sure ruby
component is installed in GraalVM,and connect Test.rb
script to the running IGV:
$ gu list
$ ruby --jvm --vm.Dgraal.Dump=:1 --vm.Dgraal.PrintGraph=Network Test.rb
This causes GraalVM to dump compiler graphs in IGV format over the network to an IGV process listeningon 127.0.0.1:4445
. Once the connection is made, you are able to see the graphs in the Outline window.Find e.g. java.lang.String.char(int)
folder and open its After parsing graph by double-clicking.If the node has sourceNodePosition
property, then the Processing Window will attempt to display its location and the entire stacktrace.
Browsing Graphs
Once a specific graph is opened, you can search for nodes by name, ID, or by property=value
data, and all matching results will be shown.Another cool feature of this tool is the ability to navigate to the original guest language source code!Select a node in graph and press ‘go to source’ button in the Stack View window.
Graphs navigation is available also from the context menu, enabled by focusingand right-clicking a specific graph node. Extract nodes option will re-rendera graph and display just selected nodes and their neighbours.
If the graph is larger than the screen, manipulate with the ‘satellite’ view buttonin the main toolbar to move the viewport rectangle.
For user preference, the graph color scheme is adjustable by editingthe Coloring filter, enabled by default in the left sidebar.
Viewing Source Code
Source code views can be opened in manual and assisted modes. Once you select a nodein the graph view, the Processing View will open. If IGV knows where the source codefor the current frame is, the green ‘go to source’ arrow is enabled. If IGV does notknow where the source is, the line is grayed out and a ‘looking glass’ button appears.
Press it and select “Locate in Java project” to locate the correct project in the dialog.IGV hides projects which do not contain the required source file.The “Source Collections” serves to display the stand alone roots added by “Add root of sources” general action.If the source is located using the preferred method (i.e., from a Java project),its project can be later managed on the Project tab. That one is initially hidden,but you can display the list of opened projects using Window - Projects.
Dumping Graphs
The IGV tool is developed to allow GraalVM language implementersto optimize their languages assembled with the Truffle framework. As a developmenttool it should not be installed to production environments.
To dump the GraalVM compiler graphs from an embedded Java application to IGV,you need to add options to GraalVM based processes. Depending on the language/VMused, you may need to prefix the options by —vm
. See the particularlanguage’s documentation for the details. The main option to add is-Dgraal.Dump=:1
. This will dump graphs in an IGV readable format to the localfile system. To send the dumps directly to IGV over the network, add-Dgraal.PrintGraph=Network
when starting a GraalVM instance. Optionally aport can be specified. Then dumps are sent to IGV from the running GraalVM onlocalhost. If IGV does not listen on localhost, options “Ideal Graph Settings|Accept Data from network” can be checked. If there is not an IGV instancelistening on 127.0.0.1
or it cannot be connected to, the dumps will beredirected to the local file system. The file system location is graal_dumps/
under the current working directory of the process and can be changed with the-Dgraal.DumpPath
option.
In case an older GraalVM is used, you may need to explicitly request that dumpsinclude the nodeSourcePosition
property. This is done by adding the-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints
options.