vlambda博客
学习文章列表

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运行时使用。


T-Trace:GraalVM中类似代理的测仪


测仪的风格

在深入研究演示应用程序之前,我们先来看看不同的测仪技术。有几种测仪风格可以应用于代码片段或整个应用程序。在高层次上,我们可以讨论黑盒和白盒工具。黑盒技术不需要对应用程序进行任何修改。监视系统在这情况中仅使用应用程序生成的事件。这些事件可以是日志,也可以是应用程序生成的任何数据。


在本文中,我们将重点讨论白盒技术。这种技术假设应用程序的代码是已知的,并且可以检查。有几种方法可以将测仪点添加到应用程序中:

  • 非介入式代理(类似于代理)测仪:在这种情况下,测仪是动态地添加到应用程序中的。在某些语言(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#L37 var config = { serviceName: 't-trace-demo', reporter: { // Provide the traces endpoint; this forces the client to connect directly to the Collector and send // spans over HTTP collectorEndpoint: '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


T-Trace:GraalVM中类似代理的测仪

从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


点击文末<<阅读原文>>进入网页了解更多。




T-Trace:GraalVM中类似代理的测仪关于中国KubeCon + CloudNativeCon + 开源峰会


T-Trace:GraalVM中类似代理的测仪


扫描二维码联系我们,加入中国最终用户支持者计划!




CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux  Foundation,是非营利性组织。 

CNCF云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。请长按以下二维码进行关注。