此文已由作者易国强授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
1 btrace简介
BTrace是一个非常不错的java诊断工具。BTrace 中的B表示bytecode,它是在字节码层面上对代码进行trace ,通过在运行中的java类中注入trace代码, 并对运行中的目标程序进行热交换(hotswap)来达到对代码的跟踪 。BTrace应用较为广泛的原因应该是其安全性和无侵入性,以及热交互技术,使得我们无需启动Agent的情况下动态跟踪分析,其安全性不会导致对目标Java进程的任何破坏性影响,使得BTrace成为我们线上产品问题定位的利器。无侵入性无需我们对原有代码做任何修改,降低上线风险和测试成本,并且无需重启启动目标Java进程进行Agent加载即可动态分析和跟踪目标程序,可以说BTrace可以满足大部分的应用场景。
2 btrace原理
总体来说,BTrace是基于动态字节码修改技术(Hotswap)来实现运行时java程序的跟踪和替换。大体的原理如下所示:
Client(Java compile api + attach api) + Agent(脚本解析引擎 + ASM + JDK6 Instumentation) + Socket
BTrace的入口类在 https://gi
thub.com/btraceio/bt… 中。在其main方法中,可以看到起最终的核心逻辑是在 github.com/btraceio/bt… 中。方法调用如下:
client.compile
client.attach
client.submit
具体来说,针对官网给出的示例进行说明,有以下脚本:
import com.sun.btrace.annotations.*;import static com.sun.btrace.BTraceUtils.*;@BTracepublic class HelloWorld { @OnMethod( clazz="java.lang.Thread", method="start" ) public static void func() { println("about to start a thread!"); } }复制代码
@OnMethod告诉Btrace解析引擎需要代理的类和方法。 这个例子的作用是在需要监控的程序中的java.lang.Thread类的任意一个对象调用 start 方法后,都会调用func方法。首先client会编译上述脚本,然后client.attach使用java的attach api将agent动态attach到目标jvm进程中,最后client的submit方法,会向agent发送监控命令以及传递对应code的字节码。总的来说其实BTrace就是使用了java attach api附加agent.jar,然后使用脚本解析引擎+asm来重写指定类的字节码,再使用instrument实现对原有类的替换。
3 安装并启动btrace
1. 安装BTrace,下载tar.gz包,在服务器上解压即可运行。
2. 确认需要监控的java应用,获取进程PID。
3. 使用java语言写一个BTrace脚本,如Demo.java。
进入$BTRACE_HOME/bin目录
执行 ./btrace [目标进程的PID] Demo.java
4 btrace实例
首先我们编写一个简单的java程序作为被监控的对象:
import java.util.Random; public class HelloWorld { public static void main(String[] args) throws Exception { //CaseObject object = new CaseObject(); while (true) { Random random = new Random(); execute(random.nextInt(4000)); //object.execute(random.nextInt(4000)); } } public static Integer execute(int sleepTime) { try { Thread.sleep(sleepTime); } catch (Exception e) { } System.out.println("sleep time is=>"+sleepTime); return 0; } }复制代码
其次需要编写一个btrace脚本,如下所示:
import static com.sun.btrace.BTraceUtils.println;import static com.sun.btrace.BTraceUtils.str;import static com.sun.btrace.BTraceUtils.strcat;import static com.sun.btrace.BTraceUtils.timeMillis;import com.sun.btrace.annotations.BTrace;import com.sun.btrace.annotations.Kind;import com.sun.btrace.annotations.Location;import com.sun.btrace.annotations.OnMethod;import com.sun.btrace.annotations.ProbeClassName;import com.sun.btrace.annotations.ProbeMethodName;import com.sun.btrace.annotations.TLS;@BTracepublic class TraceHelloWorld { @TLS private static long startTime = 0; @OnMethod(clazz = "my.app.test.HelloWorld", method = "execute") public static void startMethod(){ startTime = timeMillis(); } @OnMethod(clazz = "my.app.test.HelloWorld", method = "execute", location = @Location(Kind.RETURN)) public static void endMethod(){ println(strcat("the class method execute time=>", str(timeMillis()-startTime))); println("-------------------------------------------"); } @OnMethod(clazz = "my.app.test.HelloWorld", method = "execute", location = @Location(Kind.RETURN)) public static void traceExecute(@ProbeClassName String name,@ProbeMethodName String method,int sleepTime){ println(strcat("the class name=>", name)); println(strcat("the class method=>", method)); println(strcat("the class method params=>", str(sleepTime))); } }复制代码
以下是需要注意的几个点:复制代码
1、@btrace这个annotation表明这个类是btrace脚本,
2、@OnMethod(clazz = "my.app.test.HelloWorld", method = "execute")
中clazz标明要监控那个类,也可以用正则匹配的方式,method标明要监控类的哪个方法
3、其中用到的几个方法timeMillis(),获取时间,println(str)输出
其中clazz=... method=... 这两个属性,指定了这个BTrace方法的注入位置,这个注入的位置叫probe point, 具体来讲,excute()方法叫probed method, TraceHelloWorld类叫probed class。 也就是说, Btrace脚本中的方法endMethod()会注入在目标JVM的com.netease.qa.btrace.Demo1.add()调用处。注入操作是通过修改excute()方法的字节码实现的。
一旦注入成功,action method会在被监控应用执行到probe point的时候被触发执行。但是,这里具体是add()方法调用前,还是调用完成时呢? 这个由@Location注解指定,这里的value=Kind.RETURN,指定了endMethod方法会在调用结束后执行。
启动btrace脚本后,可以看到以下输出:
可以看到这样就完成了一个简单的btrace脚本的编写以及监控应用实践的工作。
免费体验云安全(易盾)内容安全、验证码等服务
更多网易技术、产品、运营经验分享请点击。