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