BTrace使用小结

简介

BTrace是一个安全的JVM动态追踪工具,最初为原Sun公司Kenai项目下面的一个子项目。

典型的使用场景是,“我要查个问题,可那个方法没有打印入口参数和返回结果日志”,“我想看某个方法的执行耗时”,“我想查看某方法如System.GC()的调用栈”等等,这些都是BTrace可以小试牛刀的地方。它的优势是,直接attach应用JVM,不用重启应用进程,可比较快速方便地定位问题。

不错的教程

如果想简单学习一下BTrace,推荐几个不错的教程,建议先看看下面几篇文章:

使用

下面是我学习BTrace的一点笔记和小结。不过还是先来个例子比较直观。

一个例子

一个简单的例子,我想查看某工程下这个方法的入参及返回值,但代码中没有打印方法返回结果,这个时候可以用BTrace试一下。

DemoView com.package.name.Demo.getDemoView(long id)

编写跟踪脚本MethodReturnTracing.java如下:

import static com.sun.btrace.BTraceUtils.println;
import static com.sun.btrace.BTraceUtils.str;

import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
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.Return;
import com.sun.btrace.annotations.Self;

/**
 * 打印方法入参及返回值
 * 
 * Created by zhouwei on 2017-6-21.
 */

@BTrace(unsafe = true) // 表示这是一个BTrace跟踪脚本,并启用unsafe模式(因为使用了BTraceUtils以外的方法,即String.valueOf(obj))
public class MethodReturnTracing {

    @OnMethod(clazz = "com.package.name.Demo", // 类的全限定名
              method = "getDemoView", // 方法名
              location = @Location(Kind.RETURN)) // 表示跟踪某个类的某个方法,位置为方法返回处
    public static void onMethodReturn(@Self Object self, long id, @Return AnyType result) { // @Return注解将上面被跟踪方法的返回值绑定到此探查方法的参数上
        
        println(BTraceUtils.Time.timestamp("yyyy-MM-dd HH:mm:ss")); // 打印时间
        
        println("method self: " + str(self));
        
        println("method params: " + id); // 打印入参
        
        println("method return: " + String.valueOf(result)); // 打印结果对象,因String.valueOf(obj)为外部方法,故需使用unsafe模式
        
        println("=========================="); // 强烈建议加上,否则会因为输出缓冲看不到最新打印结果
    }
    
}

将上面的跟踪脚本拷贝到测试服务器上,执行:

$ btrace -u 24801 MethodReturnTracing.java
2017-12-03 14:20:22
method self: com.package.name.Demo@6ae7d3b4
method params: 19261
method return: DemoView(id:19261, contactName:测试联系人, contactEmail:email, address:测试地址, ctime:1511871027, utime:1511871027, valid:1)
==========================

其中,-u表示使用BTrace的unsafe模式,24801为Java进程ID,MethodReturnTracing.java为BTrace跟踪脚本。下面是其跟踪日志,打印出了当前时间、方法入参和返回对象。

除脚本中的注释外,其它需要注意的点会在下面一一指出。

命令行启动

常用的三个命令:btrace用于将脚本attach应用Java进程,btracec用于编译脚本,btracer用于带着脚本启动Java进程并同时attach。

$ btrace <PID> <trace_script> 
      It will attach to the java application with the given PID and compile and submit the trace script.
$ btracec <trace_script> 
      It will compile the provided trace script.
$ btracer <compiled_script> <args to launch a java app> 
      It will start the specified java application with the btrace agent running and the script previously compiled by btracec loaded.

<trace_script>: Xxx.java,表示BTrace跟踪脚本。

<compiled_script>: Xxx.class,表示编译后的脚本。

常用注解介绍

下面是一些常用的注解,基本是从官网上或API文档上摘抄下来的,未作翻译。主要分两类:

  • 用于注解探查方法(Action/probe Method),上面例子MethodReturnTracing.java中的onMethodReturn即称为探查方法,作用通常是打印跟踪结果。

  • 用于注解探查方法的参数。例如上面例子MethodReturnTracing.java中的@Return AnyType result,用于将被跟踪方法的返回值绑定到该探查方法的参数上。

注解探查方法(Action/probe Method Annotations)

@OnMethod(clazz=<cname_spec>[, method=<mname_spec>]? [, type=<signature>]? [, location=<location_spec>]?)

An action method annotated by this annotation is called when the matching method(s) reaches the specified location.

cname_spec = + /regex/

class_name is a fully qualified class name.

+class_name is a fully qualified class name prepended with +; means all subclasses and implementors of the prepended class name.

/regex/ is a standard regular expression used to identify the class names.

mname_spec = /regex/

method_name is a simple method name (no signature or return type).

There is another way to abstractly specify traced class(es) and method(s). Traced classes and methods may be specified by annotation. For example, if the “clazz” attribute is specified as @javax.jws.WebService BTrace will instrument all classes that are annotated by the WebService annotation. Similarly, method level annotations may be used to specify methods abstractly.

@OnTimer

used to specify tracing actions that have to run periodically once every N milliseconds.

@OnError

used to specify actions that are run whenever any exception is thrown by tracing actions of some other probe. BTrace method annotated by this annotation is called when any exception is thrown by any of the other BTrace action methods in the same BTrace class.

@OnExit

used to specify actions that are run when BTrace code calls “exit(int)” built-in function to finish the tracing “session”.

@OnEvent

used to associate tracing methods with “external” events send by BTrace client.

@OnLowMemory

used to trace memory threshold exceed event.

@OnProbe

used to specify to avoid using implementation internal classes in BTrace scripts.

@Sampled

enables sampling for the annotated handler. To be used in conjunction with @OnMethod annotation.

其中,重点关注@OnMethod注解,最常用,用于跟踪某个方法。

  • clazz 指明要被跟踪的类的全限制名,支持正则表达式和继承,语法见说明。

  • method 指明要被跟踪的方法名,支持正则表达式。

  • type 指明要被跟踪的方法的签名。一般可以不声明,绝大部分情况下依靠clazzmethod即可确定要跟踪的方法。

  • location 指明要跟踪的方法的位置。具体可见@Location注解的说明,例如@Location(Kind.RETURN)表示方法返回处,@Location(Kind.ENTRY)表示方法入口处。

注解探查方法的参数

这类注解的作用是将被跟踪方法的相关属性(关注点,如类名、方法名、方法入参、返回值、执行时间、抛出的异常等等)绑定到探查方法的参数上,然后在探查方法内作处理,如打印出来等等。例如@Duration用来捕获方法执行时间,@Return用于捕获方法返回值(它俩都只能用于@Location(Kind.RETURN)的location下),等等。分别摘录介绍如下。

@ProbeClassName

It is used to mark a probe method argument as the receiver of the probe target class name. Applicable only for OnMethod annotation. (对应@OnMethod的clazz的名字)

@ProbeMethodName

It is used to mark a probe method argument as the receiver of the probe target method name. Applicable only for OnMethod annotation. (对应@OnMethod的method的名字)

@Duration

It is used to mark a probe method argument as the receiver of the duration value. Applicable only for OnMethod annotation with Location value of Kind.RETURN or Kind.ERROR. (long, 纳秒;或用在where.AFTER)

@Return

Marks a method parameter as the one that should contain the return value. (applicable only for Kind.RETURN)

@Self

Marks a method parameter as the one that should contain this instance. (对应@OnMethod的clazz的对象)

@TargetInstance

It is used to mark a probe method argument as the receiver of called instance in Location = Kind.CALL. (对应@Location的clazz的对象,如果是静态方法,则返回null)

@TargetMethodOrField

It is used to mark a probe method argument as the receiver of called method name in Location = Kind.CALL. (对应@Location的method的名字)

一点经验

下面是我在使用BTrace的过程中积累的一点经验,希望对大家有用。

  1. 请在已经搭好的添加过依赖的maven工程中编写跟踪脚本!Git地址如下:btrace samples。其中com.sun.btrace.samples包中的代码为官方示例脚本,强烈建议看看;me.kopite.test下面为部分其它简单示例。

  2. 将btrace上传到服务器上,并设置环境变量,将btrace等命令加入命令行PATH中:

    首先,在目标服务器(server)上执行(使用nc命令):

     $ mkdir -p ~/zhouwei/btrace-bin-1.3.9 ; cd ~/zhouwei/btrace-bin-1.3.9 ; nc -l 8080 > btrace-bin-1.3.9.tgz ; tar zxvf btrace-bin-1.3.9.tgz ; export JAVA_HOME="/usr/local/java" ; export BTRACE_HOME=~/zhouwei/btrace-bin-1.3.9 ; export PATH=$BTRACE_HOME/bin:$PATH ; cd ~/zhouwei ; ll ; which btrace
    

    如果服务器上已经有btrace包,则只需要执行上面后半部分的命令来设置环境变量即可:

     $ export JAVA_HOME="/usr/local/java" ; export BTRACE_HOME=~/zhouwei/btrace-bin-1.3.9 ; export PATH=$BTRACE_HOME/bin:$PATH ; cd ~/zhouwei ; ll ; which btrace
    

    然后,在本地机器上执行(serverIP即为目标服务器的IP地址):

     $ nc $serverIP 8080 < ~/Downloads/btrace-bin-1.3.9.tgz
    

    按自己的需要事先写好命令,即可在需要时快速上传和使用,节省时间提高效率。

    由于服务器一般有端口访问限制,请使用8080附近的端口。

    下载btrace-bin-1.3.9.tgz

  3. 用于匹配方法入参或返回类型时,因嫌麻烦不想引入外部依赖(一般也没有必要),外部类型请用AnyType代替,而不是Object!因为你可能用Object来准确匹配方法返回参数或返回类型。例如上面例子MethodReturnTracing.java中的@Return AnyType result

  4. 由于BTrace的安全和性能考虑,一般情况下不允许在探查方法中调用BTraceUtils以外的其它方法,但可使用unsafe模式。

    例如使用String.valueOf()方法来打印对象(注意不要使用obj.toString(),因为对象可能是null!),这时需要使用非安全模式@BTrace(unsafe = true),并使用-u选项btrace -u <PID> <trace_script>来启动跟踪。

    例如使用JSON.toJSONString()来打印对象(使用-cp选项来指明外部依赖的jar路径): btrace -u -cp .:$(find /apps/xxx_service -type f -name "fastjson-*.jar" 2>/dev/null | head -1) <PID> <trace_script>

    具体参考BTraceUtils.str(Object)方法说明,由非bootstrap class loader加载的对象不会调用对象的toString()方法,但List里面的对象可以打印出来(因为List对象是标准库里的类,由bootstrap class loader加载,且ListtoString()方法调用了对象的toString()方法)。

  5. 如何在thrift客户端拦截thrift接口调用?因为BTrace不支持拦截接口方法。通过查看调用栈,发现可以这么写:clazz = "xxxThriftIface$Client",即拦截的类名clazz为接口名后面加$Client

  6. 打印输出有缓冲区延迟,故需要在探查方法的最后一行打印:println("=================================");

  7. 其它:

    • 启动跟踪脚本时,请使用和启动Java进程相同的Linux账号,不然会因为权限问题而attach失败。

    • BTrace也可以用来跟踪匿名内部类的方法,只不过clazz对应的类名里面有个”$”符号,只要写对其类名即可。

    • 对象构造函数的名字是<init>,类构造器的名字是<clinit>

    • 另一个和BTrace类似的Java诊断工具greys-anatomy,由阿里释出,感兴趣的也可以学习一下。

    • 若报错”Port 2020 unavailable.”,则使用btrace -p 2021 ... 来指定其它端口。

    • Linux下已经有个命令也叫btrace,注意别用混了。

Written on December 2, 2017