BTrace使用教程

6,104 阅读4分钟
原文链接: www.ezlippi.com

线下的应用遇到问题时我们可以通过改代码加日志追踪或者通过远程调试来获取运行时的程序细节,但如果应用一开始没有开启远程调试的端口,那你要修改JVM参数添加调试的参数然后重启应用,这种对于线上应用来说就不适用了,线上应用不允许随便修改代码也无法远程调试。BTrace就是一个可以在不改代码、不重启应用的情况下,动态的查看程序运行细节的工具。这篇文章主要介绍BTrace的使用场景以及如果用BTrace来输出程序的执行细节。

安装BTrace

官方下载地址:btrace
下载之后设置BTRACE_HOME环境变量,并把BTACE_HOME/bin添加到PATH环境变量中:

export BTRACE_HOME=/i/software/btrace-bin-1.3.9
export PATH=$BTRACE_HOME/bin:$PATH

Btrace的使用场景

  1. 查看某一个方法中入参和返回值

  2. 查看某一个方法的响应时间

  3. 查看某一个方法中所有外部调用的响应时间,方便定位方法响应慢的具体位置及原因

  4. 打印方法调用的线程堆栈

如何使用BTrace

使用起来比较简单,只需要执行btrace {javaPid} {btrace脚本}就行,如果要把输出调试信息到控制台,要加上-v 参数,-o 参数可以把结果输出到文件中。

如何编写BTrace脚本

BTrace脚本编写比较简单,主要用到BTraceUtils工具类提供的方法,具体的教程可以参考Btrace使用教程,在编写脚本时,最好把BTrace提供的Jar包引入到Build Path上,方便在编译期发现语法错误,BTrace的三个jar包在安装包的build目录下,如果你用的是Maven来构建项目,可以添加如下依赖,把btrace的路径对应修改即可:

 <dependency>
    <groupId>com.sun.tools.btrace</groupId>
    <artifactId>btrace-agent</artifactId>
    <version>1.3.9</version>
    <scope>system</scope>
    <systemPath>I:/software/btrace-bin-1.3.9/build/btrace-agent.jar</systemPath>
</dependency>
<dependency>
    <groupId>com.sun.tools.btrace</groupId>
    <artifactId>btrace-boot</artifactId>
    <version>1.3.9</version>
    <scope>system</scope>
    <systemPath>I:/software/btrace-bin-1.3.9/build/btrace-boot.jar</systemPath>
</dependency>
<dependency>
    <groupId>com.sun.tools.btrace</groupId>
    <artifactId>btrace-client</artifactId>
    <version>1.3.9</version>
    <scope>system</scope>
    <systemPath>I:/software/btrace-bin-1.3.9/build/btrace-client.jar</systemPath>
</dependency>

我自己写了一个StringTableTest类,这个类有个intern方法,我想用btrace脚本来追踪intern方法的入参和返回值,并把调用堆栈打印出来,StringTableTest类源码如下所示,为了让BTrance能够追踪到该进程,我先让主线程休眠了10秒钟:

package com.ezlippi;
import java.util.concurrent.TimeUnit;

public class StringTableTest {
    public static void main(String[] args) {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        StringTableTest test = new StringTableTest();
        long ms = test.intern(1000000);
        System.out.println(ms);
    }
    public long intern(int times) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < times; i++) {
            String.valueOf(i).intern();
        }
        return System.currentTimeMillis() - start;
    }
}

BTrace脚本源码如下所示,第一个方法用于打印方法的参数、返回值和执行的时间点,第二个方法用于打印是否调用了StringTableTest的第22行。

package com.ezlippi.performanace;
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TraceMethodArgsAndReturn {
    @OnMethod(
            clazz="com.ezlippi.StringTableTest",
            method="intern",
            location=@Location(Kind.RETURN)
    )
    public static void traceExecute(int sSrc,@Return long result){
        println("invoke StringTableTest.intern");
        println(strcat("input arg is:", str(sSrc)));
        // 打印函数返回结果result
        println(strcat("result is:",str(result)));
		//打印方法执行时间点
        println(strcat("time is:",str(timeMillis())));
        //打印线程堆栈
        jstack();
    }
	@OnMethod(
            clazz="com.ezlippi.StringTableTest",
            location=@Location(value=Kind.LINE,line=22)
    )
    public static void traceExecute(@ProbeClassName String pcn,@ProbeMethodName String pmn,int line){
        println(strcat(strcat(strcat("call ",pcn),"."),pmn));
    }
}

需要注意的是,如果方法参数是原生数据类型,那traceExecute方法的参数类型要与之匹配,不用用对象类型,如果参数是对象,可以用AnyType来表示,对于返回值也是类似,如果返回值为对象类型,可以用Object表示。

首先执行StringTableTest的main方法,然后通过jps命令获取jvm进程的Pid,最后调用btrace 13440 ./scripts/TraceMethodArgsAndReturn.java就会打印执行结果,我的当前目录是在btrace的bin目录下,所有脚本放在scripts目录下,BTrace官方也提供了很多样例,在samples目录下,
我把整个过程写成了一个脚本start.sh,只需要先在IDEA里执行main方法后,执行start.sh即可:

#!/bin/bash
currentDir="$(dirname $0)"
export BTRACE_HOME=$currentDir/../
export PATH=$BTRACE_HOME/bin:$PATH

pid="$(jps | grep "StringTableTest"|cut -d " " -f1)"

btrace $pid $currentDir/scripts/TraceMethodArgsAndReturn.java

最后输出结果如下所示:

call com.ezlippi.StringTableTest.intern
invoke StringTableTest.intern
input arg is:1000000
result is:628
time is:1512899327404
//线程堆栈
com.ezlippi.StringTableTest.intern(StringTableTest.java:22)
com.ezlippi.StringTableTest.main(StringTableTest.java:13)

有了BTrace之后,最终线上应用的运行细节就更方便了。