T-Trace:GraalVM中类似代理的测仪
:关于中国KubeCon + CloudNativeCon + 开源峰会
在本文中,我们将研究类似代理的测仪(instrumentation)工具T-Trace。该工具为运行在GraalVM上的应用程序提供非侵入性的测仪功能。我们将使用T-Trace和带有Jaeger NodeJS tracer的OpenTracing API来测仪(instrument)一个简单的NodeJS应用程序。
首先,GraalVM是一个多语言的虚拟机。它可以运行任何基于JVM的语言,也可以运行其他主流语言,比如NodeJS、Python和Ruby……它还支持像C和C++这样的LLVM语言。最理想的情况是,这些语言可以组合在一个应用程序中。另一个非常有趣的特性是GraalVM可以为JVM语言生成原生可执行文件。这个特性被Quarkus和Micronaut等现代云原生Java运行时使用。
测仪的风格
在深入研究演示应用程序之前,我们先来看看不同的测仪技术。有几种测仪风格可以应用于代码片段或整个应用程序。在高层次上,我们可以讨论黑盒和白盒工具。黑盒技术不需要对应用程序进行任何修改。监视系统在这情况中仅使用应用程序生成的事件。这些事件可以是日志,也可以是应用程序生成的任何数据。
在本文中,我们将重点讨论白盒技术。这种技术假设应用程序的代码是已知的,并且可以检查。有几种方法可以将测仪点添加到应用程序中:
非介入式代理(类似于代理)测仪:在这种情况下,测仪是动态地添加到应用程序中的。在某些语言(Python、Ruby、JavaScript)中,这种技术称为monkey-patching,在运行时动态地替换模块或类。在其他语言中,可以使用不太动态的技术,例如Java中的javaagent。
显式测仪:该测仪直接应用于代码。但是,这并不意味着应用程序开发人员必须测仪所有代码。测仪代码可以直接嵌入(RPC)框架或外部集成。在一些语言和运行时框架中,像在其他语言中一样,初始化工具更容易。例如,在Java中,只需将一个测仪JAR放入类路径中就足够了,而在像Golang这样的静态链接语言中,初始化必须显式地完成。
应用程序
该应用程序是一个简单的NodeJS服务器,只有一个处理程序。完整的演示代码和说明可以在GitHub的pavolloffay/graalvm-t-trace中找到。下面是server.js文件:
function jaegerAvailable(jaeger) {console.log("Providing Jaeger object to the agent");}jaegerAvailable(require("jaeger-client"));const port = 3000;const http = require("http");const srv = http.createServer((req, res) => {console.log(`server: obtained request ${res.id}`);setTimeout(() => {res.write(`OK# ${res.id}`);console.log(`server: replied to request ${res.id}`);res.end();}, 5);});srv.listen(port);
本例中唯一与跟踪相关的代码是加载npm模块jaeger-client。这是目前已知的T-Trace的局限性,因为代理脚本无法加载其他库。这个特性被添加到T-Trace中。然后应用程序在请求的开头和结尾打印请求id。代理脚本中设置了res.id。
现在让我们看一下测仪脚本jaegernode.js。有两个函数:initializeJaeger和initializeAgent。第一个函数使用HTTP发送器创建Jaeger跟踪器实例,并将其发送到收集器端口14268,最后调用第二个函数:
let initializeJaeger = function (ctx, frame) {agent.off('enter', initializeJaeger);let jaeger = frame.jaeger;var initTracer = jaeger.initTracer;console.log('agent: Jaeger tracer obtained');// See schema https://github.com/jaegertracing/jaeger-client-node/blob/master/src/configuration.js#L37var config = {serviceName: 't-trace-demo',reporter: {// Provide the traces endpoint; this forces the client to connect directly to the Collector and send// spans over HTTPcollectorEndpoint: 'http://localhost:14268/api/traces',// logSpans: true},sampler: {type : 'const',param : 1}};var options = {tags: {'t-trace-demo.version': '1.1.2',},// metrics: metrics,logger: console,sampler: {type : 'const',param : 1}};var tracer = initTracer(config, options);initializeAgent(tracer);};agent.on('return', initializeJaeger, {roots: true,rootNameFilter: name => name === 'jaegerAvailable'});
第二个函数是initializeAgent,它测仪应用程序代码:
let initializeAgent = function(tracer) {agent.on('enter', function(ctx, frame) {const args = frame.args;if ('request' !== frame.type || args.length !== 2 || typeof args[0] !== 'object' || typeof args[1] !== 'object') {return;}const req = args[0];const res = args[1];const span = tracer.startSpan(req.method);span.setTag("span.kind", "server");span.setTag("http.url", req.url);span.setTag("http.method", req.method);res.id = span.context().spanIdStr;res.span = span;console.log(`agent: handling #${res.id} request for ${req.url}`);}, {roots: true,rootNameFilter: name => name === 'emit',sourceFilter: src => src.name === 'events.js'});agent.on('return', function(ctx, frame) {var res = frame['this'];if (res.span) {res.span.setTag("http.status_code", res.statusCode);if (res.statusCode >= 400) {res.span.setTag("error", "true");}res.span.finish();console.log(`agent: finished #${res.id} request`);} else {// OK, caused for example by Tracer itself connecting to Jaeger server}}, {roots: true,rootNameFilter: name => name === 'end',sourceFilter: src => src.name === '_http_outgoing.js'});console.log('agent: ready');};
测仪是通过agent.on('enter', fn)和agent.on('return', fn)完成的。当调用应用程序中的任何函数时调用第一个测仪点,当函数返回调用者执行时调用第二个测仪点。agent.on函数可以访问frame变量和方法参数。参数用于检查函数是否为HTTP处理程序。你还可以注意到,span对象被注入到响应中。
现在让我们运行应用程序和Jaeger服务器:
docker run --rm -it --net=host jaegertracing/all-in-one:1.16.0$GRAALVM_HOME/bin/npm install jaeger-client@3.17.2$GRAALVM_HOME/bin/node --experimental-options --js.print=true --agentscript=jaegernode.js server.jscurl http://localhost:3000
从Jaeger的屏幕截图显示t-trace演示应用程序的跟踪。
总结
我们看到了一个带有Jaeger和OpenTracing的NodeJS hello-world的T-Trace示例。这演示了如何将类似代理的测仪应用到NodeJS应用程序中,而不需要monkey-patching。
可以对代码进行许多改进。例如,我们可以扩展它,不测仪代理脚本中的代码,而是重用NodeJS的OpenTracing测仪,并将其安装到代理脚本中。我们可以做的另一个改进是支持脚本的动态加载。在这种情况下,应用程序将使用代理脚本启动,该脚本暴露用于加载和禁用脚本的REST API。这是一个非常强大的特性,可以动态地更改测仪的粒度,而无需重新编译和重新部署应用程序。
T-Trace还能够将工具语言与主语言混合使用。例如,可以用JavaScript编写代理脚本来跟踪Ruby或C++应用程序。
参考文献
演示代码:
https://github.com/pavolloffay/graalvm-t-trace
T-Trace:
https://github.com/oracle/graal/blob/master/tools/docs/T-Trace.md
T-Trace,通晓多种语言的跟踪:
https://github.com/oracle/graal/blob/master/tools/docs/T-Trace-Manual.md#trully-polyglot---t-trace-any-language
T-Trace嵌入式跟踪:
https://github.com/oracle/graal/blob/master/tools/docs/T-Trace-Embedding.md
点击文末<<阅读原文>>进入网页了解更多。
:关于中国KubeCon + CloudNativeCon + 开源峰会
)
:
扫描二维码联系我们,加入中国最终用户支持者计划!
CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux Foundation,是非营利性组织。
CNCF(云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。请长按以下二维码进行关注。
