Experimental feature in GraalVM

GraalVM 洞察

GraalVM Insight 是一个多功能、灵活的工具,用于追踪程序运行时行为并收集洞察信息。

该工具的动态特性帮助用户有选择地在已运行的应用程序上应用追踪切入点,且不损失性能。GraalVM Insight 还提供对程序运行时行为的详细访问,允许用户在调用或分配点检查值和类型。该工具还允许用户修改计算值、中断执行以及快速试验行为更改,而无需修改应用程序代码。该工具的实现细节可在API 规范中找到。

本页面提供截至 20.1 版本的 GraalVM Insight 信息。要了解 20.0 和 19.3 版本的 Insight,请点击此处

开始使用 #

  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 对象将源监听器附加到运行时。每当脚本加载时,监听器就会收到通知并可以采取行动——打印已处理脚本的长度和名称。

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 脚本中共享的全局变量,它允许代码在 insight.on('enter') 函数和 dumpHotness 函数之间共享数据。后者在 Node 进程执行结束后执行(通过 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!
    

    由于 source-tracing.js 脚本仍然是用 JavaScript 编写的,因此必须使用 --polyglot 参数启动 Ruby 启动器。

用户可以对基于 GraalVM 的任何语言进行插桩,同时 Insight 脚本也可以用 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 Insight 不仅允许追踪程序执行的位置,还可以在程序执行期间访问局部变量和函数参数的值。例如,您可以编写一个工具来显示函数 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

Insight 深入探讨 #

任何中等技能的开发者都可以轻松创建自己的所谓“钩子”,并将其动态应用于实际程序。这提供了对应用程序执行和行为的终极洞察,同时不损害执行速度。

要继续学习并深入了解 GraalVM Insight,请查阅Insight 手册,该手册以一个必要的 HelloWorld 示例开始,然后演示了更具挑战性的任务。

将 GraalVM Insight 嵌入应用程序 #

GraalVM 语言(使用 Truffle 框架实现的语言)可以通过Polyglot Context API 嵌入到自定义应用程序中。GraalVM Insight 也可以通过相同的 API 进行控制。

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

使用 GraalVM Insight 进行追踪 #

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

API 规范 #

如果您对实现细节感兴趣,请查阅API 规范。您将在其中找到关于 insight 对象属性、函数等的信息。

联系我们