package com.github.ompc.greys.core.command;
import com.github.ompc.greys.core.Advice;
import com.github.ompc.greys.core.advisor.AdviceListener;
import com.github.ompc.greys.core.advisor.ReflectAdviceTracingListenerAdapter;
import com.github.ompc.greys.core.command.annotation.Cmd;
import com.github.ompc.greys.core.command.annotation.IndexArg;
import com.github.ompc.greys.core.command.annotation.NamedArg;
import com.github.ompc.greys.core.exception.ExpressException;
import com.github.ompc.greys.core.server.Session;
import com.github.ompc.greys.core.textui.TTree;
import com.github.ompc.greys.core.util.InvokeCost;
import com.github.ompc.greys.core.util.PointCut;
import com.github.ompc.greys.core.util.matcher.ClassMatcher;
import com.github.ompc.greys.core.util.matcher.GaMethodMatcher;
import com.github.ompc.greys.core.util.matcher.PatternMatcher;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.atomic.AtomicInteger;
import static com.github.ompc.greys.core.util.Express.ExpressFactory.newExpress;
import static com.github.ompc.greys.core.util.GaStringUtils.getThreadInfo;
import static com.github.ompc.greys.core.util.GaStringUtils.tranClassName;
import static org.apache.commons.lang3.StringUtils.isBlank;
/**
* 调用跟踪命令<br/>
* 负责输出一个类中的所有方法调用路径 Created by oldmanpushcart@gmail.com on 15/5/27.
*/
@Cmd(name = "trace", sort = 6, summary = "Display the detailed thread stack of specified class and method",
eg = {
"trace -E org\\.apache\\.commons\\.lang\\.StringUtils isBlank",
"trace org.apache.commons.lang.StringUtils isBlank",
"trace *StringUtils isBlank",
"trace *StringUtils isBlank params[0].length==1",
"trace *StringUtils isBlank '#cost>100'",
"trace -n 2 *StringUtils isBlank",
})
public class TraceCommand implements Command {
@IndexArg(index = 0, name = "class-pattern", summary = "Path and classname of Pattern Matching")
private String classPattern;
@IndexArg(index = 1, name = "method-pattern", summary = "Method of Pattern Matching")
private String methodPattern;
@IndexArg(index = 2, name = "condition-express", isRequired = false,
summary = "Conditional expression by OGNL",
description = "" +
"FOR EXAMPLE" +
"\n" +
" TRUE : 1==1\n" +
" TRUE : true\n" +
" FALSE : false\n" +
" TRUE : params.length>=0\n" +
" FALSE : 1==2\n" +
"\n" +
"THE STRUCTURE" +
"\n" +
" target : the object \n" +
" clazz : the object's class\n" +
" method : the constructor or method\n" +
" params[0..n] : the parameters of method\n" +
" returnObj : the returned object of method\n" +
" throwExp : the throw exception of method\n" +
" isReturn : the method ended by return\n" +
" isThrow : the method ended by throwing exception"
)
private String conditionExpress;
@NamedArg(name = "E", summary = "Enable regular expression to match (wildcard matching by default)")
private boolean isRegEx = false;
@NamedArg(name = "n", hasValue = true, summary = "Threshold of execution times")
private Integer threshold;
@Override
public Action getAction() {
return new GetEnhancerAction() {
@Override
public GetEnhancer action(Session session, Instrumentation inst, final Printer printer) throws Throwable {
return new GetEnhancer() {
@Override
public PointCut getPointCut() {
return new PointCut(
new ClassMatcher(new PatternMatcher(isRegEx, classPattern)),
new GaMethodMatcher(new PatternMatcher(isRegEx, methodPattern)),
// don't include the sub class when tracing...
// fixed for #94
// GlobalOptions.isTracingSubClass
// include sub class when tracing now
true
);
}
@Override
public AdviceListener getAdviceListener() {
return new ReflectAdviceTracingListenerAdapter() {
private final AtomicInteger timesRef = new AtomicInteger();
private final InvokeCost invokeCost = new InvokeCost();
private final ThreadLocal<Trace> traceRef = new ThreadLocal<Trace>();
@Override
public void tracingInvokeBefore(
Integer tracingLineNumber,
String tracingClassName,
String tracingMethodName,
String tracingMethodDesc) throws Throwable {
final Trace trace = traceRef.get();
if (null == tracingLineNumber) {
trace.tTree.begin(tranClassName(tracingClassName) + ":" + tracingMethodName + "()");
} else {
trace.tTree.begin(tranClassName(tracingClassName) + ":" + tracingMethodName + "(@" + tracingLineNumber + ")");
}
}
@Override
public void tracingInvokeAfter(
Integer tracingLineNumber,
String tracingClassName,
String tracingMethodName,
String tracingMethodDesc) throws Throwable {
final Trace trace = traceRef.get();
if (!trace.tTree.isTop()) {
trace.tTree.end();
}
}
@Override
public void tracingInvokeThrowing(
Integer tracingLineNumber,
String tracingClassName,
String tracingMethodName,
String tracingMethodDesc,
String throwException) throws Throwable {
final Trace trace = traceRef.get();
if (!trace.tTree.isTop()) {
trace.tTree.set(trace.tTree.get() + "[throw " + throwException + "]").end();
}
}
private String getTitle(final Advice advice) {
final StringBuilder titleSB = new StringBuilder("Tracing for : ")
.append(getThreadInfo());
if (advice.isTraceSupport()) {
titleSB.append(";traceId=").append(advice.getTraceId()).append(";");
}
return titleSB.toString();
}
@Override
public void before(Advice advice) throws Throwable {
invokeCost.begin();
traceRef.set(
new Trace(
new TTree(true, getTitle(advice))
.begin(advice.getClazz().getName() + ":" + advice.getMethod().getName() + "()")
)
);
}
@Override
public void afterReturning(Advice advice) throws Throwable {
final Trace trace = traceRef.get();
if (!trace.tTree.isTop()) {
trace.tTree.end();
}
}
@Override
public void afterThrowing(Advice advice) throws Throwable {
final Trace trace = traceRef.get();
trace.tTree.begin("throw:" + advice.throwExp.getClass().getName() + "()").end();
if (!trace.tTree.isTop()) {
trace.tTree.end();
}
// 这里将堆栈的end全部补上
//while (entity.tracingDeep-- >= 0) {
// entity.tTree.end();
//}
}
private boolean isInCondition(Advice advice, long cost) {
try {
return isBlank(conditionExpress)
|| newExpress(advice).bind("cost", cost).is(conditionExpress);
} catch (ExpressException e) {
return false;
}
}
private boolean isOverThreshold(int currentTimes) {
return null != threshold
&& currentTimes >= threshold;
}
@Override
public void afterFinishing(Advice advice) throws Throwable {
final long cost = invokeCost.cost();
if (isInCondition(advice, cost)) {
final Trace trace = traceRef.get();
printer.println(trace.tTree.rendering());
if (isOverThreshold(timesRef.incrementAndGet())) {
printer.finish();
}
}
}
};
}
};
}
};
}
private class Trace {
private final TTree tTree;
private Trace(TTree tTree) {
this.tTree = tTree;
}
}
}