Gain Running State by Btrace Script

Btrace在github上对自己的介绍是:

BTrace is a safe, dynamic tracing tool for the Java platform.

Btrace是一款利用了Java动态织入技术来追踪已经部署在线上的应用信息状态的工具。

在线上因为应用已经被部署,一旦发生故障想排查应用里面的信息非常困难,这个时候停掉服务再去追加一些日志也不合适,而Btrace则可以用脚本和应用的进程id获取用户自己希望得到的信息。

.______   .___________..______          ___       ______  _______  __  
|   _  \  |           ||   _  \        /   \     /      ||   ____||  | 
|  |_)  | `---|  |----`|  |_)  |      /  ^  \   |  ,----'|  |__   |  | 
|   _  <      |  |     |      /      /  /_\  \  |  |     |   __|  |  | 
|  |_)  |     |  |     |  |\  \----./  _____  \ |  `----.|  |____ |__| 
|______/      |__|     | _| `._____/__/     \__\ \______||_______|(__) 
                                                                       

安装btrace

Btrace的源码在github上:https://github.com/btraceio/btrace

截止这篇文章发布前,最新版本为1.3.9.

下载之后,需要在环境变量中配置BTRACE_HOME,将btrace的安装目录赋值给BTRACE_HOME

BTRACE_HOME=export BTRACE_HOME="/usr/local/btrace/"

在命令行中输入btrace,能打印出以下内容,则说明安装成功:

➜  ~ btrace
Usage: btrace    
where possible options include:
  --version             Show the version
  -v                    Run in verbose mode
  -o              The path to store the probe output (will disable showing the output in console)
-u                    Run in unsafe mode
  -d              Dump the instrumented classes to the specified path
  -pd             The search path for the probe XML descriptors
  -classpath      Specify where to find user class files and annotation processors
  -cp             Specify where to find user class files and annotation processors
  -I              Specify where to find include files
  -p              Specify port to which the btrace agent listens for clients
  -statsd  Specify the statsd server, if any

举个栗子

假设我们有这样的一个web入口:

package demo.spring.web.action;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author whthomas
 * @date 2017/5/28
 */
@RestController
@RequestMapping("/")
public class MainController {

    @RequestMapping("/{name}")
    public String hello(@PathVariable("name") String name) {
        return "hello " + name;
    }

}

以上这段代码在线上运行之后,假设在不停服务的情况下来获取这个接口中的参数,就可以利用Btrace来截获信息。

import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class Sample{
    @OnMethod(
        clazz="demo.spring.web.action.MainController",
        method="hello",
        location= @Location(Kind.RETURN)
    )
    public static void func(@ProbeClassName String pcn,
                            @Duration long duration){

        // get the param from the method
        println("我抓到你了" + pcn + ",duration:" + duration);
    }
}

运行脚本时,按照格式执行脚本:

btrace [Application's PID] Script.java

就像这样,当MainControllerhello方法被调用到之后,脚本里面的内容就会被打印出来。

➜ btrace 29338 Sample.java
我抓到你了demo.spring.web.action.MainController,duration:12354

喜极而泣。

Btrace的具体配置

所有的Btrace脚本都不能忘了把@BTrace注解加到类的头部。然后透过以下这些配置,我们可以构建非常丰富的脚本来获取线上应用的信息。

@OnMethod

刚刚的Btrace脚本中的func()使用了一个OnMethod注解。

在这个注解中可以定义这几个参数:

  • clazz:目标对象所在的类的类型
  • method:目前方法的方法名
  • type:
  • location:脚本执行的时间点

clazz不仅仅能指定特定目标,还可以使用正则表达式,拦截一系列的函数。

location参数中它可以通过@Location配置脚本被执行的时间点,比如:

  • @Location(Kind.RETURN): 在函数返回时执行
  • @Location(Kind.ENTER): 在函数进入时执行
  • @Location(value = Kind.LINE, line = 10): 函数被执行到第10行时触发
  • @Location(value = Kind.CALL, clazz = "/./", method = "/./", where = Where.AFTER): 查询当前被拦截函数中其他函数的调用情况

Kind类中其实还有很多执行点,比如Kind.Error, Kind.Throw和 Kind.Catch异常抛出(Throw),异常被捕获(Catch),异常没被捕获被抛出函数之外(Error),主要用于对某些异常情况的跟踪。详见: https://github.com/btraceio/btrace/blob/master/src/share/classes/com/sun/btrace/annotations/Kind.java

@OnTimer

这个注解,被用于执行一些定时任务。它只接受一个参数:

  • interval:执行的时间间隔

比如要求某个方法两秒被执行一次:

@OnTimer(2000)
public static void print() {
    // print the counter
    println("component count = " + count);
}

@OnTimer注解的函数和被@OnMethod注解的函数相结合,可以定期打印出某些函数的执行状态。

@OnExit

@OnExit注解的函数会在Btrace脚本停止的时候触发,而非被监控的进程停止。

@OnExit
public static void onexit(int code) {
    println("BTrace program exits!");
}

除了这种方法上的注解,在上面的例子中还能看到参数上可以使用的注解。

@ProbeClassName

获取被拦截的方法所属类的类名,因为有时候,@OnMethod会利用正则表达式拦截到一组方法。

@ProbeMethodName

获取被拦截方法的方法名,原因同上。

@Duration

@Duration注解的参数,会被注入函数执行的时间。如果要使用@Duration,被标记的参数只能是long类型,而且函数上需要被标记成location=@Location(Kind.RETURN)

@Return

@Return注解的参数,会被注入函数执行的返回值。跟@Duration一样,如果要使用@Return注解函数也需要被标记成location=@Location(Kind.RETURN)

@Self

@Self注解的参数表示被拦截的方法实例。可以通过被@Self注解的参数获取方法中的入参(param)

JDK原有类非JDK类获取参数的方式还有一些区别,非JDK类获取参数需要先用类加载器(classLoader)将类加载出来。

// JDK类
Field fdFiled = field("java.io.FileInputStream", "fd");

// 非JDK类
Field customField = field(classForName("demo.spring.web.MyObject", contextClassLoader()), "customField");
@OnMethod(
    clazz="demo.spring.web.action.MainController",
    method="hello",
    location= @Location(Kind.RETURN)
)
public static void func(@Self Object obj,
                        @ProbeClassName String pcn,
                        @Duration long duration){

    // get the param from the method
    println("拿到参数name:" + field("java.lang.String", "name"));
    println("我抓到你了" + pcn + ", duration:" + duration);
}

@TargetInstance 和 @TargetMethodOrField

这两个注解需要放到一起才好理解,假设我们有如下两个类。

class A {
    public void methodA(){
        // statement
    }
}

class B {

    A a = new A();

    public void methodB(){
        // statement
        a.methodA();
        
    }
}

Btrace中,我们构建这样的脚本:

@BTrace
public class Sample{
    @OnMethod(
        clazz="B",
        method="methodB",
        location= @Location(
            value = Kind.CALL,
            clazz = "/.*/", 
            method = "/.*/", 
            where = Where.AFTER
        )
    )
    public static void func(@Self self,
                            @TargetInstance targetInstance,
                            @TargetMethodOrField targetMethod, 
                            @Duration long duration){

        // get the param from the method
    }
}
  • targetInstance表示在methodB()方法中其他引用类对象实例。
  • targetMethod表示在methodB()方法中其他对象实例执行的方法。

值得注意的是,这两个注解都只能在这个配置下使用:

location=@Location([Kind.CALL|Kind.FIELD_GET|Kind.FIELD_SET)

当然生产环境中不建议查询一个方法下所有的调用方法,这样JVM的大概会被拖崩溃掉。

你可能感兴趣的:(Gain Running State by Btrace Script)