MProfiler的字节码增强服务原创
在引入字节码增强Service之前,研究了一下BTrace和jvm-sandbox的实现,因为这两个开源软件是以程序编写的方式使用的,另外还有Arthas,虽然是以命令执行的方式使用的,但其相关的一些命令,如monitor和watch等仍然使用了字节码增强,有很高的参考价。下面我们就这3个知名度比较大的开源软件进行对比,如下表所示:
在使用上,个人觉得BTrace更简洁一些,而jvm-sandbox需要新建module并打包成jar后存储到相应的目录中才可以。举个例子体验一下BTrace和jvm-sandbox,需要被增强的代码如下:
package btrace.script;
public class Calculator {
private int c = 1;
public int add(int a, int b) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return a + b;
}
}
BTrace增强脚本如下:
@BTrace
public class BTraceTest {
@OnMethod(
clazz = "btrace.script.Calculator",
method = "add",
location = @Location(Kind.RETURN)
)
public static void trace(int a, int b, @Return int sum) {
println("trace1:a=" + a + ",b=" + b + ",sum=" + sum);
}
}
jvm-sandbox增强脚本如下:
@MetaInfServices(Module.class)
@Information(id = "calculator")
public class JVMSandboxTest implements Module {
@Resource
private ModuleEventWatcher moduleEventWatcher;
@Command("trace")
public void trace() {
new EventWatchBuilder(moduleEventWatcher)
.onClass("Calculator")
.onBehavior("add")
.onWatch(new AdviceListener() {
@Override
protected void afterReturning(Advice advice) throws Throwable {
Object[] parameters = advice.getParameterArray();
if (parameters != null) {
for (Object obj : parameters) {
System.out.println("参数类型:" + obj.getClass().getName());
System.out.println("参数值:" + obj);
}
}
}
});
}
}
两个增强脚本都是在目标应用程序中执行的,所以BTrace类的trace()方法和AdviceListener匿名类中afterReturning()方法都是在目标进程中执行的,我们可以将采集到的数据发送到脚本启动进程中,例如BTrace脚本中的println()就将结果通过socket直接传回去了,而jvm-sandbox由于使用的是http,所以只能主动去拉取数据。
如果要将jvm-sandbox或BTrace集成到MProfiler并以Service的方式提供出来,需要在增强脚本中将相关的数据按一定的格式发送到MProfiler。
假设现在MProfiler要对Calculator类的add()方法进行增强,获取某一次的调用参数,则增强逻辑类似如下:
@Inject
MPorfilerInstrumentService service;
MPorfilerInstrument<Result> mp = service.createMProfilerInstrument()
.onClass("Calculator")
.onMethod("add")
.onAgentWatch(new AdviceListener() {
@Override
protected Result afterReturning(Advice advice) throws Throwable {
// Agent附加的目标进程中执行的逻辑
// 将获取到的信息封装为特定类对象(这里是Result对象)后返回
Result r = ...
return r;
}
});
我们为了防止执行诊断项的主线程阻塞,需要在MProfiler中异步处理增强返回的结果,如下:
// 在MProfiler进程中异步执行
mp.thenAccept((result) -> {
Object[] parameters = result.getParameterArray();
if (parameters != null) {
for (Object obj : parameters) {
System.out.println("参数类型:" + obj.getClass().getName());
System.out.println("参数值:" + obj);
}
}
});
// 在MProfiler进程中异步执行处理异常
mp.exceptionally((e) -> {
e.printStackTrace();
return null;
});
由于MProfiler中已经封装并提供了Attach服务,获取所有类服务(用于Attach后执行retransform操作)及定义了进程间socket通信方式,所以如果要深度集成BTrace或jvm-sandbox还需要大的改动,不如借鉴这些成熟工具自己编写一个字节码增强服务。