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:

  1. var http = require('http');
  2. var server = http.createServer(function (request, response) {
  3. response.writeHead(200, {"Content-Type": "text/plain"});
  4. response.end("Hello World!\n");
  5. });
  6. server.listen(8000);
  7. console.log("Server running at http://localhost:8000/");
  • Save this program as HelloWorld.js and then run:
  1. $ node --inspect --jvm HelloWorld.js
  2. Debugger listening on port 9229.
  3. To start debugging, open the following URL in Chrome:
  4. chrome-devtools://devtools/bundled/js_app.html?ws=127.0.1.1:9229/76fcb6dd-35267eb09c3
  5. 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:

Tools Reference - 图1

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:

  1. --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.

  1. --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 polyglotaccept the —inspect[=[host:]<port number>] option, but suspend on the first line ofthe application code by default.

  1. --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:

  1. --inspect.Path=<path>

Allows to specify a fixed path that generates a predictable connection URL. Bydefault, the path is randomly generated.

  1. --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.

  1. --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.
  1. --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:

  1. --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.

  1. --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/Contextto launch the inspector backend. The following code snippet provides an example ofa possible launch:

  1. String port = "4242";
  2. String path = "session-identifier";
  3. String remoteConnect = "true";
  4. Context context = Context.newBuilder("js")
  5. .option("inspect", port)
  6. .option("inspect.Path", path)
  7. .option("inspect.Remote", remoteConnect)
  8. .build();
  9. String hostAdress = "localhost";
  10. String url = String.format(
  11. "chrome-devtools://devtools/bundled/js_app.html?ws=%s:%s/%s",
  12. hostAdress, port, path);
  13. // 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:
  1. class AcceptFilter {
  2. accept(n) {
  3. return true
  4. }
  5. }
  6. class DivisibleByFilter {
  7. constructor(number, next) {
  8. this.number = number;
  9. this.next = next;
  10. }
  11. accept(n) {
  12. var filter = this;
  13. while (filter != null) {
  14. if (n % filter.number === 0) {
  15. return false;
  16. }
  17. filter = filter.next;
  18. }
  19. return true;
  20. }
  21. }
  22. class Primes {
  23. constructor() {
  24. this.number = 2;
  25. this.filter = new AcceptFilter();
  26. }
  27. next() {
  28. while (!this.filter.accept(this.number)) {
  29. this.number++;
  30. }
  31. this.filter = new DivisibleByFilter(this.number, this.filter);
  32. return this.number;
  33. }
  34. }
  35. var primes = new Primes();
  36. var primesArray = [];
  37. for (let i = 0; i < 5000; i++) {
  38. primesArray.push(primes.next());
  39. }
  40. console.log(`Computed ${primesArray.length} prime numbers. ` +
  41. `The last 5 are ${primesArray.slice(-5)}.`);
  • Run js primes.js.

The example application should print output as follows:

  1. $> js primes.js
  2. 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:

  1. $ ./js primes.js --cpusampler
  2. Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
  3. ---------------------------------------------------------------------------------------------------
  4. Sampling Histogram. Recorded 1184 samples with period 1ms
  5. Self Time: Time spent on the top of the stack.
  6. Total Time: Time the location spent on the stack.
  7. Opt %: Percent of time spent in compiled and therfore non-interpreted code.
  8. ---------------------------------------------------------------------------------------------------
  9. Name | Total Time | Opt % || Self Time | Opt % | Location
  10. ---------------------------------------------------------------------------------------------------
  11. next | 1216ms 98.5% | 87.9% || 1063ms 85.9% | 99.0% | primes.js~31-37:564-770
  12. accept | 159ms 11.2% | 22.7% || 155ms 12.5% | 14.8% | primes.js~13-22:202-439
  13. :program | 1233ms 100.0% | 0.0% || 18ms 1.5% | 0.0% | primes.js~1-47:0-1024
  14. constructor | 1ms 0.1% | 0.0% || 1ms 0.1% | 0.0% | primes.js~7-23:72-442
  15. ---------------------------------------------------------------------------------------------------

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.

  1. accept(n) {
  2. var filter = this;
  3. while (filter != null) {
  4. if (n % filter.number === 0) {
  5. return false;
  6. }
  7. filter = filter.next;
  8. }
  9. return true;
  10. }

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=*acceptto collect statement samples for all functions that end with accept.
  1. $ js primes.js --cpusampler --cpusampler.Mode=statements --cpusampler.FilterRootName=*accept
  2. Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
  3. ----------------------------------------------------------------------------------------------------
  4. Sampling Histogram. Recorded 1567 samples with period 1ms
  5. Self Time: Time spent on the top of the stack.
  6. Total Time: Time the location spent on the stack.
  7. Opt %: Percent of time spent in compiled and therfore non-interpreted code.
  8. ----------------------------------------------------------------------------------------------------
  9. Name | Total Time | Opt % || Self Time | Opt % | Location
  10. ----------------------------------------------------------------------------------------------------
  11. accept~16-18 | 436ms 27.8% | 94.3% || 435ms 27.8% | 94.5% | primes.js~16-18:275-348
  12. accept~15 | 432ms 27.6% | 97.0% || 432ms 27.6% | 97.0% | primes.js~15:245-258
  13. accept~19 | 355ms 22.7% | 95.5% || 355ms 22.7% | 95.5% | primes.js~19:362-381
  14. accept~17 | 1ms 0.1% | 0.0% || 1ms 0.1% | 0.0% | primes.js~17:322-334
  15. ----------------------------------------------------------------------------------------------------

Roughly 30 percent of the time is spent in this if condition:

  1. if (n % filter.number === 0) {
  2. return false;
  3. }

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=*acceptto collect execution counts for all statements in methods ending with accept.
  1. $ js primes.js --cputracer --cputracer.TraceStatements --cputracer.FilterRootName=*accept
  2. Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
  3. -----------------------------------------------------------------------------------------
  4. Tracing Histogram. Counted a total of 351278226 element executions.
  5. Total Count: Number of times the element was executed and percentage of total executions.
  6. Interpreted Count: Number of times the element was interpreted and percentage of total executions of this element.
  7. Compiled Count: Number of times the compiled element was executed and percentage of total executions of this element.
  8. -----------------------------------------------------------------------------------------
  9. Name | Total Count | Interpreted Count | Compiled Count | Location
  10. -----------------------------------------------------------------------------------------
  11. accept | 117058669 33.3% | 63575 0.1% | 116995094 99.9% | primes.js~15:245-258
  12. accept | 117053670 33.3% | 63422 0.1% | 116990248 99.9% | primes.js~16-18:275-348
  13. accept | 117005061 33.3% | 61718 0.1% | 116943343 99.9% | primes.js~19:362-381
  14. accept | 53608 0.0% | 1857 3.5% | 51751 96.5% | primes.js~14:215-227
  15. accept | 53608 0.0% | 1857 3.5% | 51751 96.5% | primes.js~13-22:191-419
  16. accept | 48609 0.0% | 1704 3.5% | 46905 96.5% | primes.js~17:322-334
  17. accept | 4999 0.0% | 153 3.1% | 4846 96.9% | primes.js~21:409-412
  18. accept | 1 0.0% | 1 100.0% | 0 0.0% | primes.js~2-4:25-61
  19. accept | 1 0.0% | 1 100.0% | 0 0.0% | primes.js~3:52-55
  20. -----------------------------------------------------------------------------------------

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.
  1. $ js primes.js --experimental-options --memtracer
  2. Computed 5000 prime numbers. The last 5 are 48563,48571,48589,48593,48611.
  3. ------------------------------------------------------------
  4. Location Histogram with Allocation Counts. Recorded a total of 5013 allocations.
  5. Total Count: Number of allocations during the execution of this element.
  6. Self Count: Number of allocations in this element alone (excluding sub calls).
  7. ------------------------------------------------------------
  8. Name | Self Count | Total Count | Location
  9. ------------------------------------------------------------
  10. next | 5000 99.7% | 5000 99.7% | primes.js~31-37:537-737
  11. :program | 11 0.2% | 5013 100.0% | primes.js~1-46:0-966
  12. Primes | 1 0.0% | 1 0.0% | primes.js~25-38:454-739
  13. ------------------------------------------------------------

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);
    • rootssamples 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 a histogram or json as output. The default is histogram.
  • —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:

  1. class AcceptFilter {
  2. accept(n) {
  3. return true
  4. }
  5. }
  6. class DivisibleByFilter {
  7. constructor(number, next) {
  8. this.number = number;
  9. this.next = next;
  10. }
  11. accept(n) {
  12. var filter = this;
  13. while (filter != null) {
  14. if (n % filter.number === 0) {
  15. return false;
  16. }
  17. filter = filter.next;
  18. }
  19. return true;
  20. }
  21. }
  22. class Primes {
  23. constructor() {
  24. this.number = 2;
  25. this.filter = new AcceptFilter();
  26. }
  27. next() {
  28. while (!this.filter.accept(this.number)) {
  29. this.number++;
  30. }
  31. this.filter = new DivisibleByFilter(this.number, this.filter);
  32. return this.number;
  33. }
  34. }
  35. function calculatePrime(n) {
  36. var primes = new Primes();
  37. var primesArray = [];
  38. for (let i = 0; i < n; i++) {
  39. primesArray.push(primes.next());
  40. }
  41. return primesArray[n-1];
  42. }
  43. function getPrime(n) {
  44. var cache = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71];
  45. var n = arguments[0];
  46. if (n > cache.length) { return calculatePrime(n); }
  47. return cache[n-1];
  48. }
  49. // TESTS
  50. console.assert(getPrime(1) == 2);
  51. 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:

  1. $ 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:

  1. $ 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 ifstatement 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 getPrimefunction 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:

  1. $ 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:

  1. const express = require('express')
  2. const app = express()
  3. const port = 3000
  4. app.get('/', (req, res) => {
  5. res.send('Hello World!')
  6. })
  7. app.get('/neverCalled', (req, res) => {
  8. res.send('You should not be here')
  9. })
  10. app.get('/shutdown', (req, res) => {
  11. process.exit();
  12. })
  13. 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:

  1. node --coverage --coverage.Output=lcov \
  2. --coverage.OutputFile=coverage/lcov.info \
  3. 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.

Tools Reference - 图2

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 —agentscriptinstrument.

Start Using T-Trace

  • Create a simple source-tracing.js script with following content:
  1. agent.on('source', function(ev) {
  2. print(`Loading ${ev.characters.length} characters from ${ev.name}`);
  3. });
  • Having set JAVA_HOME to GraalVM home directory, start the node launcher withthe —agentscript instrument and observe what scripts are being loaded andevaluated:
  1. $ $JAVA_HOME/bin/node --experimental-options --agentscript=source-tracing.js -e "print('The result: ' + 6 * 7)" | tail -n 10
  2. Loading 29938 characters from url.js
  3. Loading 345 characters from internal/idna.js
  4. Loading 12642 characters from punycode.js
  5. Loading 33678 characters from internal/modules/cjs/loader.js
  6. Loading 13058 characters from vm.js
  7. Loading 52408 characters from fs.js
  8. Loading 15920 characters from internal/fs/utils.js
  9. Loading 505 characters from [eval]-wrapper
  10. Loading 29 characters from [eval]
  11. 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:

  1. var map = new Map();
  2. function dumpHistogram() {
  3. print("==== Histogram ====");
  4. var digits = 3;
  5. Array.from(map.entries()).sort((one, two) => two[1] - one[1]).forEach(function (entry) {
  6. var number = entry[1].toString();
  7. if (number.length >= digits) {
  8. digits = number.length;
  9. } else {
  10. number = Array(digits - number.length + 1).join(' ') + number;
  11. }
  12. if (number > 10) print(`${number} calls to ${entry[0]}`);
  13. });
  14. print("===================");
  15. }
  16. agent.on('enter', function(ev) {
  17. var cnt = map.get(ev.name);
  18. if (cnt) {
  19. cnt = cnt + 1;
  20. } else {
  21. cnt = 1;
  22. }
  23. map.set(ev.name, cnt);
  24. }, {
  25. roots: true
  26. });
  27. 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 dumpHistogramfunction. The latter is executed when the node process execution is over(registered via agent.on('close', dumpHistogram). Invoke as:

  1. $ $JAVA_HOME/bin/node --experimental-options --agentscript=function-histogram-tracing.js -e "print('The result: ' + 6 * 7)"
  2. The result: 42
  3. === Histogram ===
  4. 543 calls to isPosixPathSeparator
  5. 211 calls to E
  6. 211 calls to makeNodeErrorWithCode
  7. 205 calls to NativeModule
  8. 198 calls to uncurryThis
  9. 154 calls to :=>
  10. 147 calls to nativeModuleRequire
  11. 145 calls to NativeModule.compile
  12. 55 calls to internalBinding
  13. 53 calls to :anonymous
  14. 49 calls to :program
  15. 37 calls to getOptionValue
  16. 24 calls to copyProps
  17. 18 calls to validateString
  18. 13 calls to copyPrototype
  19. 13 calls to hideStackFrames
  20. 13 calls to addReadOnlyProcessAlias
  21. =================

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:
  1. agent.on('source', function(ev) {
  2. if (ev.uri.indexOf('gems') === -1) {
  3. let n = ev.uri.substring(ev.uri.lastIndexOf('/') + 1);
  4. print('JavaScript instrument observed load of ' + n);
  5. }
  6. });
  • Prepare the helloworld.rb Ruby file:
  1. puts 'Hello from GraalVM Ruby!'
  • Apply the JavaScript instrument to the Ruby program:
  1. $ $JAVA_HOME/bin/ruby --polyglot --experimental-options --agentscript=source-trace.js helloworld.rb
  2. JavaScript instrument observed load of helloworld.rb
  3. 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:
  1. puts "Ruby: Initializing T-Trace script"
  2. agent.on('source', ->(ev) {
  3. name = ev[:name]
  4. puts "Ruby: observed loading of #{name}"
  5. })
  6. puts 'Ruby: Hooks are ready!'
  • Launch a Node.js application and instrument it with the Ruby written script:
  1. $ $JAVA_HOME/bin/node --experimental-options --polyglot --agentscript=source-tracing.rb -e "print('With Ruby: ' + 6 * 7)" | grep Ruby:
  2. Ruby: Initializing T-Trace script
  3. Ruby: Hooks are ready!
  4. Ruby: observed loading of internal/per_context/primordials.js
  5. Ruby: observed loading of internal/per_context/setup.js
  6. Ruby: observed loading of internal/per_context/domexception.js
  7. ....
  8. Ruby: observed loading of internal/modules/cjs/loader.js
  9. Ruby: observed loading of vm.js
  10. Ruby: observed loading of fs.js
  11. Ruby: observed loading of internal/fs/utils.js
  12. Ruby: observed loading of [eval]-wrapper
  13. Ruby: observed loading of [eval]
  14. 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:

  1. agent.on('enter', function(ctx, frame) {
  2. print('fib for ' + frame.n);
  3. }, {
  4. roots: true,
  5. rootNameFilter: (name) => 'fib' === name
  6. });

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:

  1. function fib(n) {
  2. if (n < 1) return 0;
  3. if (n < 2) return 1;
  4. else return fib(n - 1) + fib(n - 2);
  5. }
  6. 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:

  1. $ $JAVA_HOME/bin/node --experimental-options --agentscript=fib-trace.js fib.js
  2. fib for 3
  3. fib for 2
  4. fib for 1
  5. fib for 0
  6. fib for 1
  7. 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.

Tools Reference - 图3

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.

Tools Reference - 图4

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.

Tools Reference - 图5

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.

Tools Reference - 图6

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.

Tools Reference - 图7

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 location
    • PATH environment variable content
    • GRAALVM_HOME environment variable content
    • JAVA_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.

Tools Reference - 图8

To verify whether the PATH environment variable is valid, navigate to Code -> Preferences -> Settings -> Extensions -> GraalVM -> Home:

Tools Reference - 图9

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.

Tools Reference - 图10

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.

Tools Reference - 图11

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.

Tools Reference - 图12

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.

Tools Reference - 图13

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.

Tools Reference - 图14

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.

Tools Reference - 图15

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:

  1. $ cd idealgraphvisualizer/bin

2.Launch the tool:

  1. $ idealgraphvisualizer

3.Save the following code snippet as Test.rb:

  1. require 'json'
  2. obj = {
  3. time: Time.now,
  4. msg: 'Hello World',
  5. payload: (1..10).to_a
  6. }
  7. encoded = JSON.dump(obj)
  8. js_obj = Polyglot.eval('js', 'JSON.parse').call(encoded)
  9. puts js_obj[:time]
  10. puts js_obj[:msg]
  11. 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:

  1. $ gu list
  2. $ 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.

Tools Reference - 图16

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.

Tools Reference - 图17

If the graph is larger than the screen, manipulate with the ‘satellite’ view buttonin the main toolbar to move the viewport rectangle.

Tools Reference - 图18

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.

Tools Reference - 图19

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.