package com.github.ompc.greys.core.command;
import com.github.ompc.greys.core.Advice;
import com.github.ompc.greys.core.GlobalOptions;
import com.github.ompc.greys.core.TimeFragment;
import com.github.ompc.greys.core.advisor.AdviceListener;
import com.github.ompc.greys.core.advisor.InitCallback;
import com.github.ompc.greys.core.advisor.ReflectAdviceListenerAdapter;
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.manager.TimeFragmentManager;
import com.github.ompc.greys.core.server.Session;
import com.github.ompc.greys.core.textui.TTree;
import com.github.ompc.greys.core.textui.ext.TTimeFragmentTable;
import com.github.ompc.greys.core.util.GaMethod;
import com.github.ompc.greys.core.util.InvokeCost;
import com.github.ompc.greys.core.util.PointCut;
import com.github.ompc.greys.core.util.collection.ThreadUnsafeLRUHashMap;
import com.github.ompc.greys.core.util.matcher.*;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
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.getStack;
import static com.github.ompc.greys.core.util.GaStringUtils.getThreadInfo;
import static org.apache.commons.lang3.StringUtils.isBlank;
/**
* 调用跟踪命令<br/>
* 负责输出一个类中的所有方法调用路径 Created by oldmanpushcart@gmail.com on 15/10/01.
*/
@Cmd(name = "ptrace", sort = 6, summary = "Display the detailed thread path stack of specified class and method",
eg = {
"ptrace -E org\\.apache\\.commons\\.lang\\.StringUtils isBlank org\\.apache\\.commons\\.lang\\..*",
"ptrace -E .*\\.StringUtils isBlank org\\.apache\\.commons\\.(lang|lang3)\\..*",
"ptrace org.apache.commons.lang.StringUtils isBlank org.apache.commons.lang.*",
"ptrace *StringUtils isBlank org.apache.commons.lang.*",
"ptrace *StringUtils isBlank org.apache.commons.lang.* 'params[0].length==1'",
"ptrace *StringUtils isBlank org.apache.commons.lang.* '#cost>100'"
})
public class PathTraceCommand implements Command {
// 时间片段管理
private final TimeFragmentManager timeFragmentManager = TimeFragmentManager.Factory.getInstance();
// TimeTunnel the method call
@NamedArg(name = "t", summary = "Record the method invocation within time fragments")
private boolean isTimeTunnel = false;
@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 = "tracing-path-pattern", summary = "Tracing path of Pattern Matching")
// private String tracingPathPattern;
@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 = "path", summary = "path-tracing-pattern", hasValue = true)
private Collection<String> pathTracingPatterns;
@NamedArg(name = "Epath", summary = "path-tracing-regex-pattern", hasValue = true)
private Collection<String> pathTracingRegexPatterns;
@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;
/*
* 构造追踪路径匹配
*/
private Matcher<String> newPathTracingMatcher() {
final ArrayList<Matcher<String>> matcherList = new ArrayList<Matcher<String>>();
// fill path
if (null != pathTracingPatterns) {
for (String pathTracingPattern : pathTracingPatterns) {
matcherList.add(new PatternMatcher(isRegEx, pathTracingPattern));
}
}
// fill Epath
if (null != pathTracingRegexPatterns) {
for (String pathTracingRegexPattern : pathTracingRegexPatterns) {
matcherList.add(new PatternMatcher(PatternMatcher.Strategy.REGEX, pathTracingRegexPattern));
}
}
return new GroupMatcher.Or<String>(matcherList);
}
@Override
public Action getAction() {
final Matcher<String> classNameMatcher = new CachedMatcher<String>(
new PatternMatcher(isRegEx, classPattern),
new ThreadUnsafeLRUHashMap<String, Boolean>(GlobalOptions.ptraceClassMatcherLruCapacity)
);
final Matcher<String> methodNameMatcher = new CachedMatcher<String>(
new PatternMatcher(isRegEx, methodPattern),
new ThreadUnsafeLRUHashMap<String, Boolean>(GlobalOptions.ptraceMethodMatcherLruCapacity)
);
final Matcher pathTracingMatcher = newPathTracingMatcher();
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 GroupMatcher.Or<String>(classNameMatcher, pathTracingMatcher)),
new GaMethodMatcher(new TrueMatcher<String>())
);
}
@Override
public AdviceListener getAdviceListener() {
return new ReflectAdviceListenerAdapter() {
private final InvokeCost topInvokeCost = new InvokeCost();
private final InvokeCost invokeCost = new InvokeCost();
private final ThreadLocal<PathTrace> pathTraceRef = new ThreadLocal<PathTrace>() {
@Override
protected PathTrace initialValue() {
return new PathTrace();
}
};
private volatile boolean isInit = false;
// 执行计数器
private final AtomicInteger timesRef = new AtomicInteger();
@Override
public void create() {
isInit = true;
}
@Override
public void destroy() {
isInit = false;
}
// 是否跟踪入口
private boolean isTracingEnter(Class<?> clazz, GaMethod method) {
return classNameMatcher.matching(clazz.getCanonicalName())
&& methodNameMatcher.matching(method.getName());
}
@Override
public void before(final Advice advice) throws Throwable {
if (!isInit) {
return;
}
invokeCost.begin();
final PathTrace pathTrace = pathTraceRef.get();
if (!pathTrace.isTracing) {
if (isTracingEnter(advice.getClazz(), advice.getMethod())) {
pathTrace.isTracing = true;
} else {
return;
}
}
final Entity entity = pathTrace.getEntity(new InitCallback<Entity>() {
@Override
public Entity init() {
return new Entity(advice, timeFragmentManager.generateProcessId());
}
});
// top invoke
if(entity.deep <= 0) {
topInvokeCost.begin();
}
entity.tTree.begin(advice.getClazz().getCanonicalName() + ":" + advice.getMethod().getName() + "()");
entity.deep++;
}
@Override
public void afterFinishing(Advice advice) throws Throwable {
final PathTrace pathTrace = pathTraceRef.get();
if (!isInit
|| !pathTrace.isTracing) {
return;
}
final long cost = invokeCost.cost();
final Entity entity = pathTrace.getEntity();
entity.deep--;
// add throw exception
if (advice.isThrow) {
entity.tTree
.begin("throw:" + advice.throwExp.getClass().getCanonicalName())
.end();
}
// 记录下调用过程
if (isTimeTunnel) {
final TimeFragment timeFragment = timeFragmentManager.append(
entity.processId,
advice,
new Date(),
cost,
getStack(getThreadInfo())
);
entity.tfTable.add(timeFragment);
entity.tTree.set(entity.tTree.get() + "; index=" + timeFragment.id + ";");
}
entity.tTree.end();
if (entity.deep <= 0) {
// top invoke cost
final long topCost = topInvokeCost.cost();
// 是否有匹配到条件
// 之所以在这里主要是需要照顾到上下文参数对齐
if (isInCondition(advice, topCost)) {
// 输出打印内容
if (isTimeTunnel) {
printer.println(entity.tTree.rendering() + entity.tfTable.rendering());
} else {
printer.println(entity.tTree.rendering());
}
// 超过调用限制就关闭掉跟踪
if (isOverThreshold(timesRef.incrementAndGet())) {
printer.finish();
}
}
pathTrace.isTracing = false;
pathTrace.removeEntity();
}
}
// 是否到达节制阀值
private boolean isOverThreshold(int currentTimes) {
return null != threshold
&& currentTimes >= threshold;
}
// 匹配过滤规则
private boolean isInCondition(Advice advice, long cost) {
try {
return isBlank(conditionExpress)
|| newExpress(advice).bind("cost", cost).is(conditionExpress);
} catch (ExpressException e) {
return false;
}
}
};
}
};//getEnhancer:<init>
}//action
};//return
}
/**
* 用于在ThreadLocal中传递的实体
*/
private class Entity {
private Entity(final Advice advice, final int processId) {
this.processId = processId;
this.tfTable = new TTimeFragmentTable(true);
this.tTree = new TTree(true, getTitle(advice, processId));
this.deep = 0;
}
private String getTitle(final Advice advice, final int processId) {
final StringBuilder titleSB = new StringBuilder()
.append("pTracing for : ").append(getThreadInfo())
.append("process=").append(processId).append(";");
if (advice.isTraceSupport()) {
titleSB.append("traceId=").append(advice.getTraceId()).append(";");
}
return titleSB.toString();
}
TTimeFragmentTable tfTable;
TTree tTree;
int deep;
final int processId;
}
private class PathTrace {
boolean isTracing;
Entity entity;
Entity getEntity() {
return entity;
}
Entity getEntity(InitCallback<Entity> initCallback) {
if (null == entity) {
return entity = initCallback.init();
} else {
return entity;
}
}
void removeEntity() {
entity = null;
}
}
}