前言
arthas 中 trace 可以监控一个方法内部调用路径,并输出方法路径上的每个节点上耗时.
1 | $ trace demo.MathGame run |
在刚接触到这个功能时,我在想这不就是Spring中的切面编程,对方法进行加强嘛!!
不过还是有点想当然,首先要注意的是我们监控的方法所在的虚拟机没有重启,更没有对一个方法加上注解或者通过配置文件来加强这个方法,因此trace是在虚拟机层面上的AOP!!还好这部分我们在上一篇已经说到过了,那么还有一个问题如何监控方法内部的调用路径呢??关于这个点我会在后面的章节分享我的做法。代码上传至:github
如何获得一个方法的调用路径
假设我们的doAdd()方法中有以下方法:
1 | public int doAdd(int x, int y) throws Exception{ |
arthas匹配到的函数里的子调用,并不会向下trace多层,因此我们只考虑如何知道doAdd中子调用链路。有些同学可能会想到用 stackTraceElement 来实现:
1 | StackTraceElement[] stackTraceElement = Thread.currentThread().getStackTrace(); |
但是getStackTrace只能获取到在调用该语句之前还未出栈的栈帧。
回想一下在前面《阿尔萨斯 原理探究 asm再认识(转载)》中,我们通过重写MethodVisitor实现对一个方法的字节码的修改:
1 | static class TestMethodVisitor extends MethodVisitor { |
那么MethodVisitor会不会有对子方法调用的入口呢?的确是有的这个入口,visitMethodInsn:访问方法操作指令
1 | //opcode:为INVOKESPECIAL,INVOKESTATIC,INVOKEVIRTUAL,INVOKEINTERFACE; |
通过这个方法,我们可以监控到所有子调用的信息了,但是要注意asm中只有访问方法操作指令,并没有方法结束的调用指令。注意visitInsn通过opcode来判断指令是否为return只能知道当前方法的结束,但是不能获取子调用的结束的方法,这么说可能会有点绕,那上面的doAdd来解释:
asm 中的MethodVisitor的确可以通过visitCode对doAdd执行前进行干预,也可以通过visitInsn中的opcode知道doAdd的结束操作。那么一前一后就可以对方法进行监控了啊!!
1 | doAdd(int x, int y){ |
但是doAdd中的子调用( Thread.sleep、test)却只有通过visitMethodInsn在子调用开始之前进行干预,也就是如下:
1 | doAdd(int x, int y){ |
其实这里可以灵活一点,就是每个方法除了记录自己的开始时间,也把上一个的结束时间记录一下即可:
1 | doAdd(int x, int y){ |
如果没能理解可以看后面详细的代码。
TraceAgentMain
1 | public class TraceAgentMain { |
TraceTransformer
TraceTransformer主要编写了类转换的实现,通过自定义的TraceClassVisitor。
1 | public class TraceTransformer implements ClassFileTransformer { |
TraceClassVisitor
在进行方法的访问时,对方法进行加强!
1 | public class TraceClassVisitor extends ClassVisitor { |
TraceMethodVisitor
1 | public class TraceMethodVisitor extends MethodVisitor { |
实现效果
启动Main函数:
1 | public class Main { |
Job的实现:
1 | public class Job { |
通知目标主机:
1 | public class Iarthas { |
运行结果:一开始没有输出运行时间,当 trace(“agent.Job.doAdd”,”29212”)后 可以对Job类中的方法进行监控,输出子调用的过程:
1 | 3 |