监控工具 -- trace 工具

trace 工具

有btrace 和strace 2个工具,
btrace 主要监控java程序状态,获取运行时数据信息,如方法返回值,参数,调用次数,全局变量,调用堆栈等。
strace 常用来跟踪进程执行时的系统调用和所接收的信号。常用它查看mysql系统调用耗时

1
strace -cfp $(pidof mysqld)

btrace

btrace概述

这两天在调试程序时,发现一个比较好用的工具-btrace,能够线上监控程序状态,获取运行时数据信息,如方法返回值,参数,调用次数,全局变量,调用堆栈等。BTrace是Java的安全可靠的动态跟踪工具。 他的工作原理是通过 instrument + asm 来对正在运行的java程序中的class类进行动态增强。

有几篇文章介绍的不错, 推荐一下:

  • https://www.jianshu.com/p/93e94b724476 偏重如何使用
  • http://agapple.iteye.com/blog/1005918 偏重底层实现和原理
  • http://agapple.iteye.com/blog/962119 偏重底层实现

本文主要示例如何使用

命令使用

下载地址:https://github.com/btraceio/btrace

位于bin目录下面主要有6个脚本,3个windows的,另外3个是linux的,分别是btrace、btracec、btracer。具体功能如下:

btrace

功能: 用于运行BTrace跟踪程序。
命令格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
btrace [-I <include-path>] [-p <port>] [-cp <classpath>] <pid> <btrace-script> [<args>] 
参数含义:

include-path指定头文件的路径,用于脚本预处理功能,可选;

port指定BTrace agent的服务端监听端口号,用来监听clients,默认为2020,可选;

classpath用来指定类加载路径,默认为当前路径,可选;

pid表示进程号,可通过jps命令获取;

btrace-script即为BTrace脚本;btrace脚本如果以.java结尾,会先编译再提交执行。可使用btracec命令对脚本进行预编译。

args是BTrace脚本可选参数,在脚本中可通过"$"和"$length"获取参数信息。

示例:

btrace -cp build/ 1200 AllCalls1.java

btracec

功能: 用于预编译BTrace脚本,用于在编译时期验证脚本正确性。

1
btracec [-I <include-path>] [-cp <classpath>] [-d <directory>] <one-or-more-BTrace-.java-files> 

参数意义同btrace命令一致,directory表示编译结果输出目录。

btracer

功能: btracer命令同时启动应用程序和BTrace脚本,即在应用程序启动过程中使用BTrace脚本。而btrace命令针对已运行程序执行BTrace脚本。
命令格式:

1
2
3
4
5
6
7
8
9
10
11
12
btracer <pre-compiled-btrace.class> <application-main-class> <application-args> 
参数说明:

pre-compiled-btrace.class表示经过btracec编译后的BTrace脚本。

application-main-class表示应用程序代码;

application-args表示应用程序参数。

该命令的等价写法为:

java -javaagent:btrace-agent.jar=script=<pre-compiled-btrace-script1>[,<pre-compiled-btrace-script1>]* <MainClass> <AppArguments>

btrace脚本限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
In particular, a BTrace class
can not create new objects.
can not create new arrays.
can not throw exceptions.
can not catch exceptions.
can not make arbitrary instance or static method calls - only the public static methods of com.sun.btrace.BTraceUtils class may be called from a BTrace program.
can not assign to static or instance fields of target program's classes and objects. But, BTrace class can assign to it's own static fields ("trace state" can be mutated).
can not have instance fields and methods. Only static public void returning methods are allowed for a BTrace class. And all fields have to be static.
can not have outer, inner, nested or local classes.
can not have synchronized blocks or synchronized methods.
can not have loops (for, while, do..while)
can not extend arbitrary class (super class has to be java.lang.Object)
can not implement interfaces.
can not contains assert statements.
can not use class literals.

jvisualvm 插件

BTrace提供了jvisualvm插件,强烈推荐在jvisualvm中编写和测试BTrace脚本,启动、关闭、发送事件、增加classpath都非常方便。
jvisualvm

btrace实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package baby.btrace;  

public class CaseObject{

private static int sleepTotalTime=0;
private int sleepTotalTime2=0;

public boolean execute(int sleepTime) throws Exception{
System.out.println("sleep: "+sleepTime);
sleepTotalTime+=sleepTime;
sleepTotalTime2=sleepTotalTime+1;
sleep(sleepTime);
if(sleepTime%2==0)
return true;
else
return false;
}

public void sleep(int sleepTime) throws Exception {
Thread.sleep(sleepTime);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package baby.btrace;  

import java.util.Random;

public class CaseObjectMain {
int times = 10;

public static void main(String[] args) {
CaseObjectMain main= new CaseObjectMain();
try {
main.begin();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public void begin() throws Exception{

CaseObject object=new CaseObject();
while(true){
times++;
boolean result=doWork(object);
Thread.sleep(1000);
}
}
public boolean doWork(CaseObject object) throws Exception{
Random random=new Random();

boolean temp= object.execute(random.nextInt(1000));
return temp;
}

}
获取返回值,参数等信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* BTrace Script Template */  
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript {
/* put your code here */
/*指明要查看的方法,类*/
@OnMethod(
clazz="baby.btrace.CaseObject",
method="execute",
location=@Location(Kind.RETURN)
)
/*主要两个参数是对象自己的引用 和 返回值,其它参数都是方法调用时传入的参数*/
public static void traceExecute(@Self baby.btrace.CaseObject object,int sleepTime, @Return boolean result){
println("调用堆栈!!");
println(strcat("返回结果是:",str(result)));
jstack();
println(strcat("时间是:",str(sleepTime)));
}

}
获取对象属性值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* BTrace Script Template */  
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript {
/* put your code here */
/*指明要查看的方法,类*/
@OnMethod(
clazz="baby.btrace.CaseObject",
method="execute",
location=@Location(Kind.RETURN)
)
/*主要两个参数是对象自己的引用 和 返回值,其它参数都是方法调用时传入的参数*/
public static void traceExecute(@Self baby.btrace.CaseObject object,int sleepTime, @Return boolean result){
println("调用堆栈!!");
println(strcat("返回结果是:",str(result)));
jstack();
println(strcat("时间是:",str(sleepTime)));
}

}
获取方法执行时长
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com.sun.btrace.annotations.*;  
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript3 {
@TLS private static long startTime = 0;

@OnMethod(
clazz="baby.btrace.CaseObject",
method="execute"
)
public static void startExecute(){
startTime = timeNanos();
}

@OnMethod(
clazz="baby.btrace.CaseObject",
method="execute",
location=@Location(Kind.RETURN)
)
public static void endExecute(@Duration long duration){
long time = timeNanos() - startTime;
println(strcat("execute time(nanos): ", str(time)));
println(strcat("duration(nanos): ", str(duration)));
}
}
正则匹配和获取方法执行次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.sun.btrace.annotations.*;  
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript4 {
private static long count;

@OnMethod(
clazz="/.*/",
method="execute",
location=@Location(value=Kind.CALL, clazz="/.*/", method="sleep")
)
public static void traceExecute(@ProbeClassName String pcm, @ProbeMethodName String pmn,
@TargetInstance Object instance, @TargetMethodOrField String method){
println("====== ");
println(strcat("ProbeClassName: ",pcm));
println(strcat("ProbeMethodName: ",pmn));
println(strcat("TargetInstance: ",str(classOf(instance))));
println(strcat("TargetMethodOrField : ",str(method)));
count++;
}

@OnEvent
public static void getCount(){
println(strcat("count: ", str(count)));
}
}
正则和事件交互
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import com.sun.btrace.annotations.*;  
import static com.sun.btrace.BTraceUtils.*;

@BTrace
public class TracingScript5 {
private static long count;

@OnMethod(
clazz="/.*/",
method="execute",
location=@Location(value=Kind.CALL, clazz="/.*/", method="sleep")
)
public static void traceExecute(@ProbeClassName String pcm, @ProbeMethodName String pmn,
@TargetInstance Object instance, @TargetMethodOrField String method){
println("====== ");
println(strcat("ProbeClassName: ",pcm));
println(strcat("ProbeMethodName: ",pmn));
println(strcat("TargetInstance: ",str(classOf(instance))));
println(strcat("TargetMethodOrField : ",str(method)));
count++;
}

@OnEvent
public static void getCount(){
println(strcat("count: ", str(count)));
}
@OnEvent("A")
public static void getCountA(){
println("==AAAA==== ");
println(strcat("count: ", str(count)));
}
@OnEvent("B")
public static void getCountB(){
println("==BBB==== ");
println(strcat("count: ", str(count)));
}
}

strace

strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。

输出参数含义

每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。 strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核。

1
2
3
4
5
6
$strace cat /dev/null
execve("/bin/cat", ["cat", "/dev/null"], [/* 22 vars */]) = 0
brk(0) = 0xab1000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f29379a7000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)

参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出strace关于标准错误的调试信息.
-f 跟踪由fork调用所产生的子进程.
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column
设置返回值的输出位置.默认 为40.
-e expr
指定一个表达式,用来控制如何跟踪.格式如下:
[qualifier=][!]value1[,value2]...
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的 qualifier是 trace.感叹号是否定符号.例如:
-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none.
注意有些shell使用!来执行历史记录里的命令,所以要使用\\.
-e trace=set
只跟踪指定的系统 调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
-e trace=file
只跟踪有关文件操作的系统调用.
-e trace=process
只跟踪有关进程控制的系统调用.
-e trace=network
跟踪与网络有关的所有系统调用.
-e strace=signal
跟踪所有与系统信号有关的 系统调用
-e trace=ipc
跟踪所有与进程通讯有关的系统调用
-e abbrev=set
设定 strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.
-e raw=set
将指 定的系统调用的参数以十六进制显示.
-e signal=set
指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
-e read=set
输出从指定文件中读出 的数据.例如:
-e read=3,5
-e write=set
输出写入到指定文件中的数据.
-o filename
将strace的输出写入文件filename
-p pid
跟踪指定的进程pid.
-s strsize
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username
以username 的UID和GID执行被跟踪的命令

跟踪可执行程序

1
strace -f -F -o ~/straceout.txt myserver

-f -F选项告诉strace同时跟踪fork和vfork出来的进程,-o选项把所有strace输出写到~/straceout.txt里 面,myserver是要启动和调试的程序。

跟踪服务程序

1
strace -o output.txt -T -tt -e trace=all -p 28979

跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。