Experimental feature in GraalVM

GraalVM 洞察

GraalVM 洞察是一种多功能且灵活的工具,可以跟踪程序运行时行为并收集洞察信息。

该工具的动态特性帮助用户在不会损失性能的情况下,选择性地对已运行的应用程序应用跟踪切点。GraalVM 洞察还提供对程序运行时行为的详细访问,使用户能够在调用或分配站点检查值和类型。此外,该工具允许用户修改计算出的值、中断执行,并快速尝试行为更改,而无需修改应用程序代码。该工具的实现细节可以在API 规范中找到。

此页面提供有关 GraalVM 洞察 20.1 版的信息。要了解有关 20.0 版和 19.3 版的洞察信息,请访问此处

入门 #

  1. 创建一个包含以下内容的简单 source-tracing.js 脚本
    insight.on('source', function(ev) {
     if (ev.characters) {
         print(`Loading ${ev.characters.length} characters from ${ev.name}`);
     }
    });
    
  2. 安装了Node.js 运行时后,使用 --insight 工具启动 node 启动器,并观察正在加载和评估的脚本
    ./bin/node --insight=source-tracing.js --js.print --experimental-options -e "print('The result: ' + 6 * 7)" | tail -n 10
    Loading 215 characters from internal/modules/esm/transform_source.js
    Loading 12107 characters from internal/modules/esm/translators.js
    Loading 1756 characters from internal/modules/esm/create_dynamic_module.js
    Loading 12930 characters from internal/vm/module.js
    Loading 2710 characters from internal/modules/run_main.js
    Loading 308 characters from module.js
    Loading 10844 characters from internal/source_map/source_map.js
    Loading 170 characters from [eval]-wrapper
    Loading 29 characters from [eval]
    The result: 42
    

    source-tracing.js 脚本使用提供的 insight 对象将源侦听器附加到运行时。只要加载了脚本,侦听器就会收到通知,并可以采取操作 - 打印已处理脚本的长度和名称。

洞察信息可以收集到打印语句或直方图中。以下 function-hotness-tracing.js 脚本计算所有方法调用并转储最频繁的调用,当程序执行结束时

var map = new Map();

function dumpHotness() {
    print("==== Hotness Top 10 ====");
    var count = 10;
    var digits = 3;
    Array.from(map.entries()).sort((one, two) => two[1] - one[1]).forEach(function (entry) {
        var number = entry[1].toString();
        if (number.length >= digits) {
            digits = number.length;
        } else {
            number = Array(digits - number.length + 1).join(' ') + number;
        }
        if (count-- > 0) print(`${number} calls to ${entry[0]}`);
    });
    print("========================");
}

insight.on('enter', function(ev) {
    var cnt = map.get(ev.name);
    if (cnt) {
        cnt = cnt + 1;
    } else {
        cnt = 1;
    }
    map.set(ev.name, cnt);
}, {
    roots: true
});

insight.on('close', dumpHotness);

map 是在洞察脚本内部共享的全局变量,允许代码在 insight.on('enter') 函数和 dumpHotness 函数之间共享数据。当节点进程执行结束时(通过 insight.on('close', dumpHotness 注册),后者被执行。当 node 进程退出时,会打印出包含函数调用名称和计数的表格。

调用方式如下

./bin/node --insight=function-hotness-tracing.js --js.print --experimental-options -e "print('The result: ' + 6 * 7)"
The result: 42
==== Hotness Top 10 ====
516 calls to isPosixPathSeparator
311 calls to :=>
269 calls to E
263 calls to makeNodeErrorWithCode
159 calls to :anonymous
157 calls to :program
 58 calls to getOptionValue
 58 calls to getCLIOptionsFromBinding
 48 calls to validateString
 43 calls to hideStackFrames
========================

多语言跟踪 #

前面的示例是用 JavaScript 编写的,但由于 GraalVM 的多语言特性,你可以使用相同的工具,在用其他语言(例如 Ruby 语言)编写的程序中使用它。

  1. 创建 source-trace.js 文件
    insight.on('source', function(ev) {
    if (ev.uri.indexOf('gems') === -1) {
      let n = ev.uri.substring(ev.uri.lastIndexOf('/') + 1);
      print('JavaScript instrument observed load of ' + n);
    }
    });
    
  2. 准备 helloworld.rb Ruby 文件
    puts 'Hello from GraalVM Ruby!'
    
  3. 将 JavaScript 工具应用于 Ruby 程序
    ./bin/ruby --polyglot --insight=source-trace.js helloworld.rb
    JavaScript instrument observed load of helloworld.rb
    Hello from GraalVM Ruby!
    

    有必要使用 --polyglot 参数启动 Ruby 启动器,因为 source-tracing.js 脚本仍然是用 JavaScript 编写的。

用户可以对 GraalVM 之上的任何语言进行工具化,但洞察脚本也可以用任何 GraalVM 支持的语言(使用Truffle 语言实现框架实现)编写。

  1. 创建 source-tracing.rb Ruby 文件
    puts "Ruby: Initializing GraalVM Insight script"
    insight.on('source', ->(ev) {
     name = ev[:name]
     puts "Ruby: observed loading of #{name}"
    })
    puts 'Ruby: Hooks are ready!'
    
  2. 启动 Node.js 应用程序,并使用 Ruby 脚本对其进行工具化
    ./bin/node --polyglot --insight=source-tracing.rb -e "console.log('With Ruby: ' + 6 * 7)" | grep Ruby
    Ruby: Initializing GraalVM Insight script
    Ruby: Hooks are ready!
    Ruby: observed loading of internal/per_context/primordials.js
    Ruby: observed loading of internal/per_context/setup.js
    Ruby: observed loading of internal/per_context/domexception.js
    ....
    Ruby: observed loading of internal/modules/cjs/loader.js
    Ruby: observed loading of vm.js
    Ruby: observed loading of fs.js
    Ruby: observed loading of internal/fs/utils.js
    Ruby: observed loading of [eval]-wrapper
    Ruby: observed loading of [eval]
    With Ruby: 42
    

检查值 #

GraalVM 洞察不仅允许跟踪程序执行发生的位置,还提供对程序执行期间局部变量和函数参数值的访问。例如,你可以编写一个工具,显示函数 fib 中参数 n 的值

insight.on('enter', function(ctx, frame) {
   print('fib for ' + frame.n);
}, {
   roots: true,
   rootNameFilter: (name) => 'fib' === name
});

该工具使用第二个函数参数 frame 来访问每个工具化函数内的局部变量的值。上面的脚本还使用 rootNameFilter 将其挂钩仅应用于名为 fib 的函数

function fib(n) {
  if (n < 1) return 0;
  if (n < 2) return 1;
  else return fib(n - 1) + fib(n - 2);
}
print("Two is the result " + fib(3));

当工具存储在 fib-trace.js 文件中,实际代码在 fib.js 中时,调用以下命令会产生有关程序执行和函数调用之间传递参数的详细信息

./bin/node --insight=fib-trace.js --js.print --experimental-options fib.js
fib for 3
fib for 2
fib for 1
fib for 0
fib for 1
Two is the result 2

洞察深度探讨 #

任何中等水平的开发人员都可以轻松创建自己的“挂钩”,并动态地将它们应用于实际的程序。这为深入了解应用程序的执行和行为提供了最终的洞察力,而不会影响执行速度。

要继续学习并深入了解 GraalVM 洞察,请访问洞察手册,该手册从必不可少的 HelloWorld 示例开始,然后演示更具挑战性的任务。

将 GraalVM 洞察嵌入应用程序 #

GraalVM 语言(使用 Truffle 框架实现的语言)可以通过多语言上下文 API嵌入到自定义应用程序中。GraalVM 洞察也可以通过相同的 API 控制。

阅读嵌入文档,了解如何在应用程序中以安全的方式集成 GraalVM 洞察功能。

使用 GraalVM 洞察进行跟踪 #

GraalVM 洞察将跟踪功能动态地添加到现有代码中。像往常一样编写应用程序,并在需要时动态地应用Open Telemetry 跟踪。在专用指南中阅读有关洞察和 Jaeger 集成的更多信息。

API 规范 #

如果您对实现细节感兴趣,请查看API 规范。在这里,您将找到有关 insight 对象属性、函数等的详细信息。

联系我们