当程序结果出现异常情况,但是没有日志报错的情况下,btrace成了最后的救命稻草。本文对btrace的使用进行总结。
首先推荐官方用户手册:https://kenai.com/projects/btrace/pages/UserGuide
btrace使用方法
命令格式:
btrace [-I <include-path>] [-p <port>] [-cp <classpath>] <pid> <btrace-script> [<args>]
示例:
btrace -cp common.jar 1200 AllCalls1.java
注: common.jar不是必须的,如果只是打印耗时不需要
参数含义:
- include-path指定头文件的路径,用于脚本预处理功能,可选;
- port指定BTrace agent的服务端监听端口号,用来监听clients,默认为2020,可选;
- classpath用来指定类加载路径,默认为当前路径,可选;
- pid表示进程号,可通过jps命令获取;
- btrace-script即为BTrace脚本;btrace脚本如果以.java结尾,会先编译再提交执行。可使用btracec命令对脚本进行预编译。
- args是BTrace脚本参数,在脚本中可通过"$"和"$length"获取参数信息,可选;
btrace案例
常见使用场景
0.最常用的情况,查看某个方法耗时:
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Return;
import com.sun.btrace.annotations.Self;
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
@BTrace
public class IndexSearcher {
@TLS static long beginTime=0;
@OnMethod(clazz="com.taobao.tlog.servlet.DataChartAction_Curve", method="execute")
public static void test(){
beginTime=timeMillis();
}
@OnMethod(clazz="com.taobao.tlog.servlet.DataChartAction_Curve", method="execute",location=@Location(Kind.RETURN))
public static void test_end()throws Exception {
println(strcat(strcat("Search .execute time is:",str(timeMillis()-beginTime)),"ms"));
}
}
如果没有打印可能出现的问题是:
1. interface是不会打印的,clazz请填写具体的实现类(已代码验证过)
1.获取方法参数值和返回值
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript {
@OnMethod(
clazz="com.learnworld.Counter",
method="add",
location=@Location(Kind.RETURN)
)
public static void traceExecute(int num,@Return int result){
println("====== ");
println(strcat("parameter num: ",str(num)));
println(strcat("return value:",str(result)));
}
}
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Return;
import com.sun.btrace.annotations.Self;
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
import com.taobao.eagleeye.model.RpcBased;
@BTrace(unsafe=true)
public class EgTest2 {
@OnMethod(clazz="com.taobao.eagleeye.data.ip.IpAppMapper", method="getAppName",location=@Location(Kind.RETURN))
public static void search(String ip,@Return String result){
println(strcat(ip,result));
}
}
2.定时获取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)));
}
}
3. 获取add方法执行时间。
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript {
@TLS private static long startTime = 0;
@OnMethod(
clazz="com.learnworld.Counter",
method="add"
)
public static void startExecute(){
startTime = timeNanos();
}
@OnMethod(
clazz="com.learnworld.Counter",
method="add",
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)));
}
}
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Return;
import com.sun.btrace.annotations.Self;
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
import org.apache.lucene.search.*;
import org.apache.lucene.index.*;
@BTrace
public class MultiTermQueryWrapperFilter {
@TLS static long beginTime=0;
@OnMethod(clazz="org.apache.lucene.search.MultiTermQueryWrapperFilter", method="getDocIdSet")
public static void traceExecuteBegin(){
beginTime=timeMillis();
}
@OnMethod(clazz="org.apache.lucene.search.MultiTermQueryWrapperFilter", method="getDocIdSet",location=@Location(Kind.RETURN))
public static void mergeArray()throws Exception {
println(strcat(strcat("MultiTermQueryWrapperFilter .execute time is:",str(timeMillis()-beginTime)),"ms"));
}
}
4. 获取add()方法调用方法sleep()次数。
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript {
private static long count;
@OnMethod(
clazz="/.*/",
method="add",
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)));
}
}
3. 获取参数的成员变量
@BTrace
public class OracleTimeoutTracer {
@OnMethod(clazz = "oracle.net.nt.ConnStrategy", method = "createSocketOptions", location = @Location(value = Kind.ENTRY))
public static void strategy(@Self Object self, Properties paramProperties) {
println(str(paramProperties));
}
@OnMethod(clazz = "oracle.net.nt.TcpNTAdapter", method = "connect", location = @Location(value = Kind.ENTRY))
public static void adapter(@Self Object self) {
Field socketTimeoutField = field("oracle.net.nt.TcpNTAdapter", "sockTimeout");
Field hostField = field("oracle.net.nt.TcpNTAdapter", "host");
Field portField = field("oracle.net.nt.TcpNTAdapter", "port");
int scoketTimeout = (Integer) get(socketTimeoutField, self);
int port = (Integer) get(portField, self);
String host = (String) get(hostField, self);
println(strcat(strcat(strcat(strcat(strcat("host : ", host), ", port : "), str(port)), " , socketTimeout : "),
str(scoketTimeout)));
}
}
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Return;
import com.sun.btrace.annotations.Self;
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
import com.taobao.eagleeye.model.RpcBased;
@BTrace(unsafe=true)
public class EgTest1 {
@OnMethod(clazz="com.taobao.eagleeye.digest.RealtimeDigester", method="generateStat")
public static void search(RpcBased rpcBased){
Object clientAppName = get(field("com.taobao.eagleeye.model.RpcBased","clientAppName"), rpcBased);
Object serverAppName = get(field("com.taobao.eagleeye.model.RpcBased","serverAppName"), rpcBased);
Object serviceId = get(field("com.taobao.eagleeye.model.RpcBased","serviceId"), rpcBased);
Object host = get(field("com.taobao.eagleeye.model.RpcBased","host"), rpcBased);
String a = strcat(str(clientAppName),",");
String b = strcat(str(serverAppName),",");
String c = strcat(str(serviceId),",");
String e = strcat(str(host),",");
String n1 = strcat(a,b);
String n2 = strcat(c,e);
println(strcat(n1,n2));
}
}
问题总结
1. btrace报错:Port 2020 unavailable
admin@eagleeyetlog128031 bin]$ ./btrace -cp eagleeye-common.jar 122810 EgTest1.java
Port 2020 unavailable.
原因是btrace会在remote JVM(pid:122810)agent开启一个远程socketserver,默认端口是2020,正好我这台服务器有多个JVM实例,当我在第一个JVM上执行之后,第一个就会报错。正确的做法是:./btrace -cp eagleeye-common.jar -p 2021 53523 EgTest1.java 。
2. btrace报错:method calls are not allowed - only calls to BTraceUtils are allowed
原因是btrace存在安全限制,不允许调用被agent对象的方法。晚上说可以通过设计Dcom.sun.btrace.unsafe=true破解btrace的安全限制,但我试过不行。
3. 私有方法的访问
private method可以被直接访问到的
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Return;
import com.sun.btrace.annotations.Self;
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
import com.taobao.eagleeye.model.RpcBased;
import com.taobao.tlog.keyvalue.KVRecord;
import com.taobao.eagleeye.rt.DigestLogContext;
@BTrace(unsafe=true)
public class EgTest4 {
@OnMethod(clazz="com.taobao.eagleeye.digest.RealtimeDigester", method="fastFilter")
public static void search(DigestLogContext context){
println("haha");
}
}
这个方法是private,但会打印