博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Btrace 使用小结
阅读量:5325 次
发布时间:2019-06-14

本文共 8230 字,大约阅读时间需要 27 分钟。

BTrace是神器,每一个需要每天解决线上问题,但完全不用BTrace的Java工程师,都是可疑的。

BTrace的最大好处,是可以通过自己编写的脚本,获取应用的一切调用信息。而不需要重启应用!

只要定义脚本时不作大死(比如查看谁调用了HashMap的put方法),直接在生产环境打开也没影响。

 

1.简介

项目地址:http://github.com/btraceio/btrace

用户指南:https://kenai.com/projects/btrace/pages/UserGuide

在Release页面里下载最新Zip版,解压就能用

tar -zxvf btrace-bin-1.3.8.3.tgz

export JAVA_HOME=/opt/taobao/java
export PATH=$JAVA_HOME/bin:$PATH

./bin/btrace -cp /home/admin/projectname/target/projectname/BOOT-INF/classes  2016  SlowCall.java

./bin/btrace -cp /home/admin/projectname/target/projectname.war/WEB-INF/classes:/home/admin/projectname/target/projectname.war/WEB-INF/lib/projectname-1.0-SNAPSHOT.jar  71419  BtraceTest.java > NT12.txt

 

1.1 示例

先抄一个UserGuide里的例子:

import com.sun.btrace.annotations.*;import static com.sun.btrace.BTraceUtils.*;@BTracepublic class HelloWorld {    @OnMethod(clazz="java.lang.Thread", method="start")    public static void onThreadStart() {        println("thread start!");    }}

然后ps找出要监控的java应用的pid, ./bin/btrace $pid HelloWorld.java 就跑起来了。

通过JVM Attach API,btrace把自己绑进了被监控的进程,按HelloWorld.java里的定义,进行AOP式的代码植入。

 

1.2 约束

为了避免Btrace脚本的消耗过大影响真正业务,所以定义了一系列不允许的事情:

比如不允许调用任何类的任何方法,只能调用BTraceUtils 里的一系列方法和脚本里定义的static方法。

a. 不能创建对象

b. 不能使用数组

c. 不能抛出或捕获异常

d. 不能使用循环

e. 不能使用synchronized关键字

f. 属性和方法必须使用static修饰

更多规定看User Guide。当然,可以用-u 运行在unsafe mode来规避限制,但不推荐。

另外,BTrace植入过的代码,会一直在,直到应用重启为止。所以即使Btrace推出了,业务函数每次执行时都会多出一次Btrace是否Attach状态的判断。

 

1.3 其他命令行选项

如果在HelloWorld.java里使用了JDK外的其他类,比如Netty的:

./bin/btrace -cp .:netty-all-4.0.41.Final.jar $pid HelloWorld.java 或者

./bin/btrace -cp /home/admin/*/WEB-INF/classes $pid HelloWorld.java

但上面定义的classpath只在编译脚本时使用,而脚本里需要显式使用非JDK类的机会其实很少(后面真正用到的时候会提起)。

而在运行时,因为已经绑到目标应用的JVM里,用的是目标JVM的classpath。

 

2. 拦截方法定义,也说是 @OnMethod 注解的作用

Btrace使用@OnMethod注解定义需要分析的方法入口,在@OnMethod注解中,需要指定class、method以及location等,class表明需要监控的类,method表明需要监控的方法,指定方式如下:

 

2.1 精准定位(使用全限定名)

就是HelloWorld的例子,精确定义要监控的类与方法。如:

clazz="com.metty.rpc.common.BtraceCase", method="add"

 

2.2 正则表达式定位

可以用表达式,批量定义需要监控的类与方法。正则表达式需要写在两个 "/" 中间。

下例监控javax.swing下的所有类的所有方法....可能会非常慢,建议范围还是窄些。

@OnMethod(clazz="/javax\\.swing\\..*/", method="/.*/")public static void swingMethods( @ProbeClassName String probeClass, @ProbeMethodName String probeMethod) {   print("entered " + probeClass + "."  + probeMethod);}

通过在拦截函数的定义里注入@ProbeClassName String probeClass, @ProbeMethodName String probeMethod 参数,告诉脚本实际匹配到的类和方法名。

另一个例子,监控Statement的executeUpdate(), executeQuery() 和 executeBatch() 三个方法,见JdbcQueries.java

 

2.3 按接口,父类,Annotation定位

比如我想匹配所有的Filter类,在接口或基类的名称前面,加个+ 就行

@OnMethod(clazz="+com.vip.demo.Filter", method="doFilter")

也可以按类或方法上的annotaiton匹配,也就是注解匹配,前面加上@就行

@OnMethod(clazz="@javax.jws.WebService", method="@javax.jws.WebMethod")

 

2.4 其他

a. 构造函数的名字是 <init>,如果需要分析构造方法,需要指定method="<init>"

@OnMethod(clazz="java.net.ServerSocket", method="<init>")

 

b. 静态内部类的写法,是在类与内部类之间加上"$"

@OnMethod(clazz="com.vip.MyServer$MyInnerClass", method="hello")

 

c. 如果有多个同名的函数,想区分开来,可以在拦截函数上定义不同的参数列表(见4.1)。

 

3. 拦截时机, 也就@Location注解的作用

定义Btrace对方法的拦截位置,通过@Location注解指定,默认为Kind.ENTRY。可以为同一个函数的不同的Location,分别定义多个拦截函数。

 

3.1 Kind.Entry与Kind.Return

Kind.ENTRY:在进入方法时,调用Btrace脚本

Kind.RETURN:方法执行完时,调用Btrace脚本,只有把拦截位置定义为Kind.RETURN,才能获取方法的返回结果@Return和执行时间@Duration

@OnMethod( clazz="java.net.ServerSocket", method="bind" )

不写Location,默认就是刚进入函数的时候(Kind.ENTRY)。

但如果你想获得函数的返回结果或执行时间,则必须把切入点定在返回(Kind.RETURN)时。

OnMethod(clazz = "java.net.ServerSocket", method = "getLocalPort", location = @Location(Kind.RETURN))public static void onGetPort(@Return int port, @Duration long duration)

duration的单位是纳秒,要除以 1,000,000 才是毫秒。

 

3.2 Kind.Error, Kind.Throw和 Kind.Catch

异常抛出(Throw),异常被捕获(Catch),异常没被捕获被抛出函数之外(Error),主要用于对某些异常情况的跟踪。

在拦截函数的参数定义里注入一个Throwable的参数,代表异常。

@OnMethod(clazz = "java.net.ServerSocket", method = "bind", location = @Location(Kind.ERROR))public static void onBind(Throwable exception, @Duration long duration)

 

3.3 Kind.Call与Kind.Line

Kind.CALL:分析方法中调用其它方法的执行情况,比如在execute方法中,想获取add方法的执行耗时,必须把where设置成Where.AFTER.

Kind.LINE:通过设置line,可以监控代码是否执行到指定的位置.

下例定义监控bind()函数里调用的所有其他函数:

@OnMethod(clazz = "java.net.ServerSocket", method = "bind", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/", where = Where.AFTER))public static void onBind(@Self Object self, @TargetInstance Object instance, @TargetMethodOrField String method, @Duration long duration)

所调用的类及方法名所注入到@TargetInstance与 @TargetMethodOrField中。

​静态函数中,instance的值为空。如果想获得执行时间,必须把Where定义成AFTER。

注意这里,一定不要像下面这样大范围的匹配,否则这性能是神仙也没法救了:

@OnMethod(clazz = "/javax\\.swing\\..*/", method = "/.*/", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/"))

下例监控代码是否到达了Socket类的第363行。

@OnMethod(clazz = "java.net.ServerSocket", location = @Location(value = Kind.LINE, line = 363))public static void onBind4() {   println("socket bind reach line:363");}

line还可以为-1,然后每行都会打印出来,加参数int line 获得的当前行数。此时会显示函数里完整的执行路径,但肯定又非常慢。

 

4. 如何使用Btrace定位问题

 

4.1 打印this,参数 与 返回值

import com.sun.btrace.AnyType;@OnMethod(clazz = "java.io.File", method = "createTempFile", location = @Location(value = Kind.RETURN))public static void o(@Self Object self, String prefix, String suffix, @Return AnyType result)

如果想打印它们,首先按顺序定义用@Self 注释的this, 完整的参数列表,以及用@Return 注释的返回值

需要打印哪个就定义哪个,不需要的就不要定义。但定义一定要按顺序,比如参数列表不能跑到返回值的后面。

 

Self:

如果是静态函数, self为空。

前面提到,如果上述使用了非JDK的类,命令行里要指定classpath。不过,如前所述,因为BTrace里不允许调用类的方法,所以定义具体类很多时候也没意 思,所以self定义为Object就够了。

 

参数:

参数数列表要么不要定义,要定义就要定义完整,否则BTrace无法处理不同参数的同名函数

如果有些参数你实在不想引入非JDK类,又不会造成同名函数不可区分,可以用AnyType来定义(不能用Object)

如果拦截点用正则表达式中匹配了多个函数,函数之间的参数个数不一样,你又还是想把参数打印出来时,可以用AnyType[] args来定义。

但不知道是不是当前版本的bug,AnyType[] args 不能和 location=Kind.RETURN 同用,否则会进入一种奇怪的静默状态,只要有一个函数定义错了,整个 Btrace就什么都打印不出来。

 

结果:

同理,结果也可以用AnyType来定义,特别是用正则表达式匹配多个函数的时候,连void都可以表示。

 

4.2 方法执行时,查看对象的实例属性值

再次强调,为了保证性能不受影响,Btrace不允许调用任何实例方法。

比如不能调用getter方法(怕在getter里有复杂的计算),只会通过直接反射来读取属性名。

又比如,除了JDK类,其他类toString时只会打印其类名+System.IdentityHashCode。

println, printArray,都按上面的规律进行,所以只能打打基本类型。

如果想打印一个Object的属性,用printFields()来反射

如果只想反射某个属性,参照下面打印Port属性的写法。从性能考虑,应把field用静态变量缓存起来。

注意JDK类与非JDK类的区别:

import java.lang.reflect.Field;//JDK的类这样写就行private static Field fdFiled = field("java.io,FileInputStream", "fd");//非JDK的类,要给出ClassLoader,否则ClassNotFoundprivate static Field portField = field(classForName("com.vip.demo.MyObject", contextClassLoader()), "port");public static void onChannelRead(@Self Object self) {    println("port:" + getInt(portField, self));}

这样也是可以的:

@BTracepublic class HelloWorld {    @OnMethod(clazz = "java.io.File", method = "createTempFile", location = @Location(value = Kind.RETURN))    public static void o(@Self Object self, TmpTest param, @Return AnyType result){        Field field = BTraceUtils.filed("com.play.zhazah.TmpTest","data");        println("data = " + BTraceUtils.get(field, param));    }}

 

4.3.TLS,拦截函数间的通信机制

如果要多个拦截函数之间要通信,可以使用@TLS定义 ThreadLocal的变量来共享

@TLSprivate static int port = -1;@OnMethod(clazz = "java.net.ServerSocket", method = "
")public static void onServerSocket(int p){ port = p;}@OnMethod(clazz = "java.net.ServerSocket", method = "bind")public static void onBind(){ println("server socket at " + port);}

 

4.4 打印慢调用

下例打印所有用时超过1毫秒的filter。

@OnMethod(clazz = "+com.vip.demo.Filter", method = "doFilter", location = @Location(Kind.RETURN))public static void onDoFilter2(@ProbeClassName String pcn,  @Duration long duration) {    if (duration > 1000000) {        println(pcn + ",duration:" + (duration / 100000));    }}

最好能抽取了打印耗时的函数,减少代码重复度。

定位到某一个Filter慢了之后,可以直接用Location(Kind.CALL),进一步找出它里面的哪一步慢了。

 

4.5 谁调用了这个函数

比如,谁调用了System.gc() ?

@OnMethod(clazz = "java.lang.System", method = "gc")public static void onSystemGC() {    println("entered System.gc()");    jstack();}

通过查看调用栈,可以很清楚的发现哪个类哪个方法调用了System.gc()

 

4.6 捕捉异常,或进入了某个特定代码行时,this对象及参数的值

按之前的提示,自己组合一下即可。

 

4.7 统计方法的调用次数,且每隔1分钟打印调用次数

@Export static AtomicLong counter = new AtomicLong();@OnMethod(class="com.**.MyObject",method="add")public static void run(){    counter.getAndIncrement();}@OnTimer(1000*60)public static void run(){    BTraceUtils.println("count: " + connter.get());    counter.set(0);}

Btrace的@OnTimer注解可以实现定时执行脚本中的一个方法

 

4.8 打印函数的调用/慢调用的统计信息

如果你已经看到了这里,那基本也不用我再啰嗦了,自己看Samples的Histogram.java, HistoOnEvent.java

可以用AtomicInteger构造计数器,然后定时(@OnTimer),或根据事件(@OnEvent)输出结果(ctrl+c后选择发送事件)。

 

参考:

 

 

 

转载于:https://www.cnblogs.com/john8169/p/9780593.html

你可能感兴趣的文章
git安装和简单配置
查看>>
面向对象:反射,双下方法
查看>>
鼠标悬停提示文本消息最简单的做法
查看>>
课后作业-阅读任务-阅读提问-2
查看>>
面向对象设计中private,public,protected的访问控制原则及静态代码块的初始化顺序...
查看>>
fat32转ntfs ,Win7系统提示对于目标文件系统文件过大解决教程
查看>>
Awesome Adb——一份超全超详细的 ADB 用法大全
查看>>
shell cat 合并文件,合并数据库sql文件
查看>>
Android 将drawable下的图片转换成bitmap、Drawable
查看>>
介绍Win7 win8 上Java环境的配置
查看>>
移动、联通和电信,哪家的宽带好,看完你就知道该怎么选了!
查看>>
Linux设置环境变量的方法
查看>>
构建自己的项目管理方案
查看>>
利用pca分析fmri的生理噪声
查看>>
div水平居中且垂直居中
查看>>
epoll使用具体解释(精髓)
查看>>
AndroidArchitecture
查看>>
安装Endnote X6,但Word插件显示的总是Endnote Web"解决办法
查看>>
python全栈 计算机硬件管理 —— 硬件
查看>>
大数据学习
查看>>