基于Btrace的监控调试
Posted weixin_42412601
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Btrace的监控调试相关的知识,希望对你有一定的参考价值。
目录
简介
1、Btrace是什么?
在生产环境中经常遇到格式各样的问题,如OOM或者莫名其妙的进程死掉。一般情况下是通过修改程序,添加打印日志;然后重新发布程序来完成。然而,这不仅麻烦,而且带来很多不可控的因素。有没有一种方式,在不修改原有运行程序的情况下获取运行时的数据信息呢?如方法参数、返回值、全局变量、堆栈信息等。Btrace就是这样一个工具,它可以在不修改原有代码的情况下动态地追踪java运行程序,通过hotswap技术,动态将跟踪字节码注入到运行类中,对运行代码侵入较小,对性能上的影响可以忽略不计。
2、Btrace的使用限制?
由于Btrace会把脚本逻辑直接侵入到运行的代码中,所以在使用上做很多限制:
- 不能创建对象
- 不能使用数组
- 不能抛出或捕获异常
- 不能使用循环
- 不能使用synchronized关键字
- 属性和方法必须使用static修饰
注意:BTrace植入过的代码,会一直在,直到应用重启为止。所以即使Btrace退出了,业务函数每次执行时都会执行Btrace植入的代码
3、Btrace能做什么?
1、接口性能变慢,分析每个方法的耗时情况;
2、当在Map中插入大量数据,分析其扩容情况;
3、分析哪个方法调用了System.gc(),调用栈如何;
4、执行某个方法抛出异常时,分析运行时参数
安装
1、安装包:
链接:https://pan.baidu.com/s/1MnL6wXRy0y5l573jlgo2BQ
提取码:kh9f
2、配置环境变量
linux下安装设置环境变量:
vi /etc/profile
BTRACE_HOME=/root/btrace/btrace-bin-1.3.11.3
PATH=$PATH:$BTRACE_HOME/bin
export PATH JAVA_HOME
source /etc/profile
window下安装设置环境变量:
BTRACE_HOME E:\\btrace-1.3.11.3
%BTRACE_HOME%\\bin;
设置完后:验证是否安装成功
btrace --version
BTrace v.1.3.11.3 (20181217)
使用
1、获取方法的参数值
在工程里新建一个简单的controller,用于演示如何利用BTrace脚本来实时获取方法的参数值:
@Controller
public class BtraceController
@ResponseBody
@GetMapping("/hello")
public String hello(String name)
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
return "hello "+name;
依赖:
<!--btrace start-->
<dependency>
<groupId>com.sun.btrace</groupId>
<artifactId>btrace-agent</artifactId>
<version>1.3.11</version>
<type>jar</type>
<scope>system</scope>
<systemPath>E:/btrace-1.3.11.3/build/btrace-agent.jar</systemPath>
</dependency>
<dependency>
<groupId>com.sun.btrace</groupId>
<artifactId>btrace-boot</artifactId>
<version>1.3.11</version>
<type>jar</type>
<scope>system</scope>
<systemPath>E:/btrace-1.3.11.3/build/btrace-boot.jar</systemPath>
</dependency>
<dependency>
<groupId>com.sun.btrace</groupId>
<artifactId>btrace-client</artifactId>
<version>1.3.11</version>
<type>jar</type>
<scope>system</scope>
<systemPath>E:/btrace-1.3.11.3/build/btrace-client.jar</systemPath>
</dependency>
<!--btrace end-->
clean install一下,部署到服务器里,模仿生产环境。
编写Btrace脚本:
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.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
// 注明这是一个BTrace脚本
@BTrace
public class PrintArgSimple
// 指定需要拦截的方法
@OnMethod(
// 类的路径
clazz="com.example.designpattern.controller.BtraceController",
// 方法名
method="hello",
// 在什么时候进行拦截
location=@Location(Kind.ENTRY)
)
// 被拦截的类名;被拦截的方法名;被拦截的方法的参数值
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args)
// 打印数组
BTraceUtils.printArray(args);
// 打印行
BTraceUtils.println(pcn+","+pmn);
BTraceUtils.println();
把脚本放到服务器里执行:
[root@localhost btrace]# jps -l
21557 /root/project/designpattern-0.0.1-SNAPSHOT.jar
21621 sun.tools.jps.Jps
[root@localhost btrace]# btrace 21557 PrintArgSimple.java
[zhangsan, ]
com.example.designpattern.controller.BtraceController,hello
访问接口:http://192.168.169.129:8019/hello?name=zhangsan
2、拦截构造函数、同名函数
2.1 拦截构造函数
添加controller,重新部署
@ResponseBody
@RequestMapping(value = "/constructor")
public Teacher constructor(Teacher teacher)
return teacher;
public class Teacher
private Integer id;
private Integer age;
private String name;
public Teacher(Integer id, Integer age, String name)
this.id = id;
this.age = age;
this.name = name;
public Integer getId()
return id;
public void setId(Integer id)
this.id = id;
public Integer getAge()
return age;
public void setAge(Integer age)
this.age = age;
public String getName()
return name;
public void setName(String name)
this.name = name;
btrace脚本:
改类的相对路径
@BTrace
public class PrintConstructor
@OnMethod(
//拦截的类
clazz="com.example.designpattern.controller.Teacher",
//指定拦截构造函数
method="<init>"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args)
//类名
BTraceUtils.println("ClassName: " + pcn);
//方法名
BTraceUtils.println("MethodName: " + pmn);
//参数名
BTraceUtils.printArray(args);
BTraceUtils.println();
启动btrace脚本并访问接口:
[root@localhost btrace]# jps -l
22729 /root/project/designpattern-0.0.1-SNAPSHOT.jar
22794 sun.tools.jps.Jps[root@localhost btrace]# btrace 22729 PrintConstructor.java
ClassName: com.example.designpattern.controller.Teacher
MethodName: <init>
[1, 14, zhangsan, ]
2.1 拦截同名方法
controller:
/**
* 测试拦截同名函数
* @param name
* @return
*/
@ResponseBody
@RequestMapping("/same1")
public String same(@RequestParam("name")String name)
return "hello,"+name;
@ResponseBody
@RequestMapping("/same2")
public String same(@RequestParam("name")String name,@RequestParam("id")int id)
return "hello,"+name+","+id;
btrace脚本:
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintSame
@OnMethod(
clazz = "com.example.designpattern.controller.BtraceController",
method = "same"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name)
BTraceUtils.println("ClassName: " + pcn);
BTraceUtils.println("MethodName: " + pmn);
BTraceUtils.println("name: " + name);
BTraceUtils.println();
@OnMethod(
//拦截的类
clazz = "com.example.designpattern.controller.BtraceController",
//方法
method = "same"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name,int id )
BTraceUtils.println("ClassName: " + pcn);
BTraceUtils.println("MethodName: " + pmn);
BTraceUtils.println("id: " + id);
BTraceUtils.println("name: " + name);
BTraceUtils.println();
从以上的BTrace脚本中可以看到,其实拦截重载方法的话,只需要在BTrace脚本的方法中声明与之对应的参数即可,顺序也要一致。
测试:
[root@localhost btrace]# btrace 24450 PrintSame.java
ClassName: com.example.designpattern.controller.BtraceController
MethodName: same
name: aa
ClassName: com.example.designpattern.controller.BtraceController
MethodName: same
id: 123
name: aa
3、拦截返回值、异常、行号
Kind.ENTRY // 入口拦截,默认值
Kind.RETURN // 拦截返回值
Kind.THROW // 发生异常时拦截
Kind.LINE // 拦截某一行
1、拦截返回值
bstace脚本:
@BTrace
public class PrintReturn
@OnMethod(
clazz="com.example.designpattern.controller.BtraceController",
method="hello",
location=@Location(Kind.RETURN) // 拦截返回值
)
public static void anyRead(@ProbeClassName String pcn,
@ProbeMethodName String pmn,
@Return AnyType result )
BTraceUtils.println("ClassName: " + pcn);
BTraceUtils.println("MethodName: " + pmn);
BTraceUtils.println("ResultValue: " + result);
BTraceUtils.println();
访问hello接口
[root@localhost btrace]# jps -l
20460 /root/project/designpattern-0.0.1-SNAPSHOT.jar
20492 sun.tools.jps.Jps
[root@localhost btrace]# btrace 20460 PrintReturn.java
ClassName: com.example.designpattern.controller.BtraceController
MethodName: hello
ResultValue: hello 23
2、异常
已经工作了的小伙伴们,应该会遇到一个常见的现象。那就是在很多旧项目的遗留代码中,总是能看到很多不妥的处理异常的方式。例如经常能看到把异常使用try-catch包起来,但是又不打印异常堆栈,也不抛出去。结果就是把异常隐藏了起来,没能让异常终止逻辑,导致了代码继续执行,还无法定位问题的所在。就如同以下这个方法一样:
/**
* 测试拦截时机--异常
* @return
*/
@RequestMapping("exception")
public String exception()
try
System.out.println("start....");
System.out.println(1/0);
System.out.println("end....");
catch(Exception e)
return "success";
我们访问以上这个接口,会看到返回的值是正确的,但是也会发现有部分逻辑没被执行,查看日志或控制台输出也没有异常信息。当出现这种情况,就真的两眼一抹黑了,根本无法排查问题(这里作为演示代码很简单,但是实际的项目代码可不是这样)。
还好我们现在知道了BTrace这样的调试工具,那么就可以利用BTrace脚本来拦截异常并打印异常堆栈,从而定位问题。
新建一个BTrace脚本,代码如下,这是我直接从官方示例里拿来的代码:
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.Self;
import com.sun.btrace.annotations.TLS;
@BTrace
public class PrintOnThrow
// store current exception in a thread local
// variable (@TLS annotation). Note that we can't
// store it in a global variable!
@TLS
static Throwable currentException;
// introduce probe into every constructor of java.lang.Throwable
// class and store "this" in the thread local variable.
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow(@Self Throwable self) //new Throwable()
currentException = self;
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow1(@Self Throwable self, String s) //new Throwable(String msg)
currentException = self;
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow1(@Self Throwable self, String s, Throwable cause) //new Throwable(String msg, Throwable cause)
currentException = self;
@OnMethod(
clazz="java.lang.Throwable",
method="<init>"
)
public static void onthrow2(@Self Throwable self, Throwable cause) //new Throwable(Throwable cause)
currentException = self;
// when any constructor of java.lang.Throwable returns
// print the currentException's stack trace.
@OnMethod(
clazz="java.lang.Throwable",
method="<init>",
location=@Location(Kind.RETURN)
)
public static void onthrowreturn()
if (currentException != null)
BTraceUtils.Threads.jstack(currentException);
BTraceUtils.println("=====================");
currentException = null;
[root@localhost btrace]# btrace 20871 PrintOnThrow.java >1.log
3、行号
我们还可以使用BTrace拦截某一行代码,以此判断该行代码是否有被执行。在btrace包下新建一个BTrace脚本,代码如下:
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.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintLine
@OnMethod(
clazz="org.zero01.monitor_tuning.controller.BTraceController",
method="exception",
location=@Location(value=Kind.LINE, line=43) // 拦截第43行
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line)
BTraceUtils.println("ClassName: " + pcn);
BTraceUtils.println("MethodName: " + pmn);
BTraceUtils.println("line: " + line);
BTraceUtils.println();
注意:line=-1 打印所有行号
在命令行里运行该脚本,访问相应的接口后,输出的信息如下:
E:\\Java_IDEA\\monitor_tuning\\src\\main\\java\\org\\zero01\\monitor_tuning\\btrace>btrace 14952 PrintLine.java
ClassName: org.zero01.monitor_tuning.controller.BTraceController
MethodName: exception
line: 43
如果没有任何输出的话,就代表那一行没有被执行到,所以没被拦截。这种拦截某一行的方式,不适用于判断是否有异常,只能单纯用于判断某一行是否被执行了。
5、拦截复杂参数、环境变量、正则匹配拦截
5.1、拦截复杂参数
拦截复杂参数就是拦截实体对象类型的参数,在 BTraceController 类里,增加如下方法:
@RequestMapping("arg2")
public Teacher arg2(Teacher teacher)
return teacher;
使用BTrace拦截复杂参数,需要使用反射的方式进行拦截,也就是需要传递包名+属性名。在btrace包下新建一个BTrace脚本,代码如下:
import java.lang.reflect.Field;
import com.example.designpattern.controller.Teacher;
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.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintArgComplex
@OnMethod(
clazz="com.example.designpattern.controller.BtraceController",
method="arg2",
location=@Location(Kind.ENTRY)
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, Teacher teacher)
BTraceUtils.printFields(teacher);
Field filed2 = BTraceUtils.field("com.example.designpattern.controller.Teacher", "name");
BTraceUtils.println(BTraceUtils.get(filed2, teacher));
BTraceUtils.println(pcn+","+pmn);
BTraceUtils.println();
在命令行里运行该脚本,访问相应的接口后,输出的信息如下:
F:\\ideaWebProject\\designpattern\\src\\main\\java\\com\\example\\designpattern\\btrace>btrace -cp "F:\\ideaWebProject\\designpattern\\target\\classes" 1668 PrintArgComplex.java
id=1, age=22, name=aaa,
aaa
com.example.designpattern.controller.BtraceController,arg2
注:这里使用到了一个 -cp 参数,该参数表示指定一个classpath路径
如果是linux下,在执行btrace脚本的时候,需要将User对象加入到classpath里面来。如果某个对象是jdk原生的对象,则无需要这个操作。因此,需要将jdk原生类型之外的类型加入到classpath里面来。
adog@E531:chapter4$ btrace -cp /home/adog/IdeaProjects/monitor_tuning0822/target/classes 7571 PrintArgComplex.java
5.2、环境变量
通过编写BTrace脚本我们可以打印出JVM的信息以及环境变量等,就类似于jinfo命令一样。代码示例:
@BTrace
public class PrintJinfo
static
// 打印系统属性
BTraceUtils.println("System Properties:");
BTraceUtils.printProperties();
// 打印JVM参数
BTraceUtils.println("VM Flags:");
BTraceUtils.printVmArguments();
// 打印环境变量
BTraceUtils.println("OS Enviroment:");
BTraceUtils.printEnv();
// 退出脚本
BTraceUtils.exit(0);
5.3、正则匹配拦截
其实我们在编写BTrace脚本时,是可以使用正则表达式匹配类名和方法名的,并非必须要指定一个完整的名称。如下示例:
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintRegex
@OnMethod(
// 类名也可以使用正则表达式进行匹配
clazz = "org.zero01.monitor_tuning.controller.BTraceController",
// 正则表达式需要写在两个斜杠内。
//拦截所有方法
method = "/.*/"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn)
BTraceUtils.println("ClassName: " + pcn);
BTraceUtils.println("MethodName: " + pmn);
BTraceUtils.println();
6、获取方法的执行时间
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TraceMethodTime
//将字段声明为TheadLocal字段,每个线程拥有自己独立的字段
@TLS
private static long startTime = 0;
@OnMethod(
clazz = "com.example.designpattern.controller.BtraceController",
method ="/.*/",
location=@Location(Kind.ENTRY)
)
public static void methodStart()
startTime = timeNanos();
@OnMethod(
clazz = "com.example.designpattern.controller.BtraceController",
method ="/.*/",
location=@Location(Kind.RETURN)
)
//@Duration 执行时间,单位纳秒,一般同 Kind.RETURN 和 Kind.ERROR 配合使用
public static void methodEnd(@Duration long uration)
//1秒=1000000000纳秒
long endTime = timeNanos();
println(strcat("execute time(nanos): ", str(endTime-startTime)));
println("spend time:"+(endTime-startTime));
7、定时获取Counter类的属性值totalCount。
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript
private static Object totalCount = 0;
@OnMethod(
clazz="com.learnworld.Counter",
method="add",
location=@Location(Kind.RETURN)
)
public static void traceExecute(@Self com.learnworld.Counter counter)
totalCount = get(field("com.learnworld.Counter","totalCount"), counter);
@OnTimer(1000)
public static void print()
println("====== ");
println(strcat("totalCount: ",str(totalCount)));
8、定位某个超过阈值的函数
BTrace脚本
**
* 探测某个包路径下的方法执行时间是否超过某个阈值的程序,如果超过了该阀值,则打印当前线程的栈信息。
*/
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class PrintDurationTracer
@OnMethod(clazz = "/com\\\\.techstar\\\\.monitordemo\\\\..*/", method = "/.*/", location = @Location(Kind.RETURN))
public static void trace(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long duration)
//duration的单位是纳秒
if (duration > 1000 * 1000 * 2)
BTraceUtils.println(Strings.strcat(Strings.strcat(pcn, "."), pmn));
BTraceUtils.print(" 耗时:");
BTraceUtils.print(duration);
BTraceUtils.println("纳秒,堆栈信息如下");
jstack();
拦截结果:
192:Btrace apple$ btrace 39644 PrintDurationTracer.java
com.techstar.monitordemo.controller.Adder.execute 耗时:1715294657纳秒,堆栈信息如下
com.techstar.monitordemo.controller.Adder.execute(Adder.java:13)
com.techstar.monitordemo.controller.Main.main(Main.java:10)
com.techstar.monitordemo.controller.Adder.execute 耗时:893795666纳秒,堆栈信息如下
com.techstar.monitordemo.controller.Adder.execute(Adder.java:13)
com.techstar.monitordemo.controller.Main.main(Main.java:10)
com.techstar.monitordemo.controller.Adder.execute 耗时:1331363658纳秒,堆栈信息如下
com.techstar.monitordemo.controller.Adder.execute(Adder.java:13)
9、性能分析
压测的时候经常发现某一个服务变慢了,但是由于这个服务有很多的业务逻辑和方法构成,这个时候就不好定位到底慢在哪个地方。BTrace可以解决这个问题,只需要大概定位问题可能存在的地方,通过包路径模糊匹配,就可以找到问题。
BTrace脚本
/**
*
* Description:
* This script demonstrates new capabilities built into BTrace 1.2
* Shortened syntax - when omitting "public" identifier in the class
* definition one can safely omit all other modifiers when declaring methods
* and variables
* Extended syntax for @ProbeMethodName annotation - you can use
* parameter to request a fully qualified method name instead of
* the short one
* Profiling support - you can use @linkplain Profiler instance to gather
* performance data with the smallest overhead possible
*/
@BTrace
class Profiling
@Property
Profiler profiler = BTraceUtils.Profiling.newProfiler();
@OnMethod(clazz = "/com\\\\.techstar\\\\..*/", method = "/.*/")
void entry(@ProbeMethodName(fqn = true) String probeMethod)
BTraceUtils.Profiling.recordEntry(profiler, probeMethod);
@OnMethod(clazz = "/com\\\\.techstar\\\\..*/", method = "/.*/", location = @Location(value = Kind.RETURN))
void exit(@ProbeMethodName(fqn = true) String probeMethod, @Duration long duration)
BTraceUtils.Profiling.recordExit(profiler, probeMethod, duration);
@OnTimer(5000)
void timer()
BTraceUtils.Profiling.printSnapshot("Performance profile", profiler);
10、死锁排查
我们怀疑程序是否有死锁,可以通过以下的脚步扫描追踪,非常简单方便。
/**
* This BTrace program demonstrates deadlocks
* built-in function. This example prints
* deadlocks (if any) once every 4 seconds.
*/
@BTrace
public class PrintDeadlock
@OnTimer(4000)
public static void print()
deadlocks();
11、分析哪个方法调用了S
以上是关于基于Btrace的监控调试的主要内容,如果未能解决你的问题,请参考以下文章