package com.github.ompc.greys.core.command; import com.github.ompc.greys.core.Advice; import com.github.ompc.greys.core.TimeFragment; import com.github.ompc.greys.core.advisor.AdviceListener; 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.TTable; import com.github.ompc.greys.core.textui.ext.TObject; import com.github.ompc.greys.core.textui.ext.TTimeFragmentDetail; import com.github.ompc.greys.core.textui.ext.TTimeFragmentTable; import com.github.ompc.greys.core.util.*; import com.github.ompc.greys.core.util.affect.RowAffect; 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.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; import static com.github.ompc.greys.core.Advice.newForAfterRetuning; import static com.github.ompc.greys.core.Advice.newForAfterThrowing; 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 com.github.ompc.greys.core.util.GaStringUtils.newString; import static java.lang.String.format; import static org.apache.commons.lang3.StringUtils.*; /** * 时光隧道命令<br/> * 参数w/d依赖于参数i所传递的记录编号<br/> * Created by oldmanpushcart@gmail.com on 14/11/15. */ @Cmd(name = "tt", sort = 5, summary = "Time Tunnel", eg = { "tt -t *StringUtils isTop", "tt -t *StringUtils isTop params[0].length==1", "tt -l", "tt -D", "tt -i 1000 -w params[0]", "tt -i 1000 -d", "tt -i 1000" }) public class TimeTunnelCommand 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, isRequired = false, name = "class-pattern", summary = "Path and classname of Pattern Matching") private String classPattern; @IndexArg(index = 1, isRequired = false, 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\n" + " #cost : the cost(ms) of method" ) private String conditionExpress; // list the TimeTunnel @NamedArg(name = "l", summary = "List all the time fragments") private boolean isList = false; @NamedArg(name = "D", summary = "Delete all the time fragments") private boolean isDeleteAll = false; // index of TimeTunnel @NamedArg(name = "i", hasValue = true, summary = "Display the detailed information from specified time fragment") private Integer index; // expend of TimeTunnel @NamedArg(name = "x", hasValue = true, summary = "Expand level of object (0 by default)") private Integer expend; // watch the index TimeTunnel @NamedArg(name = "w", hasValue = true, summary = "watch-express, watch the time fragment by OGNL express, like params[0], returnObj, throwExp and so on.", description = "" + "FOR EXAMPLE" + "\n" + " params[0]\n" + " params[0]+params[1]\n" + " returnObj\n" + " throwExp\n" + " target\n" + " clazz\n" + " method\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 watchExpress = EMPTY; @NamedArg(name = "s", hasValue = true, summary = "Search-expression, to search the time fragments by OGNL express", 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\n" + " #index : the index of time-fragment record\n" + " #processId : the process ID of time-fragment record\n" + " #cost : the cost time of time-fragment record" ) private String searchExpress = EMPTY; // play the index TimeTunnel @NamedArg(name = "p", summary = "Replay the time fragment specified by index") private boolean isPlay = false; // delete the index TimeTunnel @NamedArg(name = "d", summary = "Delete time fragment specified by index") private boolean isDelete = false; @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 void checkArguments() { // 检查d/p参数是否有i参数配套 if ((isDelete || isPlay) && null == index) { throw new IllegalArgumentException("Time fragment index is expected, please type -i to specify"); } // 在t参数下class-pattern,method-pattern if (isTimeTunnel) { if (isBlank(classPattern)) { throw new IllegalArgumentException("Class-pattern is expected, please type the wildcard expression to match"); } if (isBlank(methodPattern)) { throw new IllegalArgumentException("Method-pattern is expected, please type the wildcard expression to match"); } } // 一个参数都没有是不行滴 if (null == index && !isTimeTunnel && !isDelete && !isDeleteAll && isBlank(watchExpress) && !isList && isBlank(searchExpress) && !isPlay) { throw new IllegalArgumentException("Argument(s) is/are expected, type 'help tt' to read usage"); } } /* * do the TimeTunnel command */ private GetEnhancerAction doTimeTunnel() { return new GetEnhancerAction() { @Override public GetEnhancer action(Session session, Instrumentation inst, final Printer printer) throws Throwable { return new GetEnhancer() { private final AtomicInteger timesRef = new AtomicInteger(); @Override public PointCut getPointCut() { return new PointCut( new ClassMatcher(new PatternMatcher(isRegEx, classPattern)), new GaMethodMatcher(new PatternMatcher(isRegEx, methodPattern)) ); } @Override public AdviceListener getAdviceListener() { return new ReflectAdviceListenerAdapter() { /* * 第一次启动标记 */ private volatile boolean isFirst = true; private final InvokeCost invokeCost = new InvokeCost(); private boolean isOverThreshold(int currentTimes) { return null != threshold && currentTimes >= threshold; } private boolean isInCondition(final Advice advice, long cost) { try { return isBlank(conditionExpress) || newExpress(advice).bind("cost", cost).is(conditionExpress); } catch (ExpressException e) { return false; } } @Override public void before(Advice advice) throws Throwable { invokeCost.begin(); } @Override public void afterFinishing(Advice advice) { final long cost = invokeCost.cost(); if (!isInCondition(advice, cost)) { return; } final TimeFragment timeFragment = timeFragmentManager.append( timeFragmentManager.generateProcessId(), advice, new Date(), cost, getStack(getThreadInfo()) ); final TTimeFragmentTable view = new TTimeFragmentTable(isFirst) .turnOffBottom() // 表格控件不输出表格上边框,这样两个表格就能拼凑在一起 .add(timeFragment) // 填充表格内容 ; if (isFirst) { isFirst = false; } final boolean isF = isOverThreshold(timesRef.incrementAndGet()); if (isF) { view.turnOnBottom(); } printer.print(isF, view.rendering()); } }; } }; } }; } /* * do list timeFragmentMap */ private RowAction doList() { return new RowAction() { @Override public RowAffect action(Session session, Instrumentation inst, Printer printer) throws Throwable { final ArrayList<TimeFragment> timeFragments = timeFragmentManager.list(); printer.print(drawTimeTunnelTable(timeFragments)).finish(); return new RowAffect(timeFragments.size()); } }; } private boolean hasWatchExpress() { return isNotBlank(watchExpress); } private boolean hasSearchExpress() { return isNotBlank(searchExpress); } private boolean isNeedExpend() { return null != expend && expend > 0; } /* * do search timeFragmentMap */ private RowAction doSearch() { return new RowAction() { @Override public RowAffect action(Session session, Instrumentation inst, Printer printer) throws Throwable { // 匹配的时间片段 final ArrayList<TimeFragment> matchingTimeFragments = timeFragmentManager.search(searchExpress); // 执行watchExpress if (hasWatchExpress()) { final TTable tTable = new TTable(new TTable.ColumnDefine[]{ new TTable.ColumnDefine(TTable.Align.RIGHT), new TTable.ColumnDefine(TTable.Align.LEFT) }) .padding(1) .addRow("INDEX", "SEARCH-RESULT"); for (TimeFragment timeFragment : matchingTimeFragments) { final Object value = newExpress(timeFragment.advice).get(watchExpress); tTable.addRow( timeFragment.id, isNeedExpend() ? new TObject(value, expend).rendering() : value ); } printer.print(tTable.rendering()).finish(); } // 单纯的列表格 else { printer.print(drawTimeTunnelTable(matchingTimeFragments)).finish(); } return new RowAffect(matchingTimeFragments.size()); } }; } /* * 清除所有的记录 */ private RowAction doDeleteAll() { return new RowAction() { @Override public RowAffect action(Session session, Instrumentation inst, Printer printer) throws Throwable { final int count = timeFragmentManager.clean(); printer.println("Time fragments are cleaned.").finish(); return new RowAffect(count); } }; } /* * 查看记录信息 */ private RowAction doWatch() { return new RowAction() { @Override public RowAffect action(Session session, Instrumentation inst, Printer printer) throws Throwable { final TimeFragment timeFragment = timeFragmentManager.get(index); if (null == timeFragment) { printer.println(format("Time fragment[%d] does not exist.", index)).finish(); return new RowAffect(); } final Advice advice = timeFragment.advice; final Object value = newExpress(advice).get(watchExpress); if (isNeedExpend()) { printer.println(new TObject(value, expend).rendering()).finish(); } else { printer.println(newString(value)).finish(); } return new RowAffect(1); } }; } /* * 重放指定记录 */ private RowAction doPlay() { return new RowAction() { @Override public RowAffect action(Session session, Instrumentation inst, Printer printer) throws Throwable { final TimeFragment timeFragment = timeFragmentManager.get(index); if (null == timeFragment) { printer.println(format("Time fragment[%d] does not exist.", index)).finish(); return new RowAffect(); } final Advice advice = timeFragment.advice; final GaMethod method = advice.getMethod(); final boolean accessible = advice.getMethod().isAccessible(); final long beginTimestamp = System.currentTimeMillis(); final long cost; Advice reAdvice = null; // 注入时间片段id PlayIndexHolder.getInstance().set(timeFragment.id); try { method.setAccessible(true); final Object returnObj = method.invoke(advice.target, advice.params); reAdvice = newForAfterRetuning( advice.loader, new LazyGet<Class<?>>() { @Override protected Class<?> initialValue() throws Throwable { return advice.getClazz(); } }, new LazyGet<GaMethod>() { @Override protected GaMethod initialValue() throws Throwable { return advice.getMethod(); } }, advice.target, advice.params, returnObj ); } catch (Throwable t) { // 执行失败:输出失败异常信息 final Throwable cause; if (t instanceof InvocationTargetException) { cause = t.getCause(); } else { cause = t; } reAdvice = newForAfterThrowing( advice.loader, new LazyGet<Class<?>>() { @Override protected Class<?> initialValue() throws Throwable { return advice.getClazz(); } }, new LazyGet<GaMethod>() { @Override protected GaMethod initialValue() throws Throwable { return advice.getMethod(); } }, advice.target, advice.params, cause ); } finally { method.setAccessible(accessible); cost = System.currentTimeMillis() - beginTimestamp; // 清除时间片段id // PlayIndexHolder.getInstance().remove(); } final TimeFragment reTimeFragment = new TimeFragment( timeFragment.id, timeFragment.processId, reAdvice, timeFragment.gmtCreate, cost, timeFragment.stack ); final TTimeFragmentDetail view = new TTimeFragmentDetail(inst, reTimeFragment, expend); printer.print(view.rendering()) .println(format("Time fragment[%d] successfully replayed.", index)) .finish(); return new RowAffect(1); } }; } /* * 删除指定记录 */ private RowAction doDelete() { return new RowAction() { @Override public RowAffect action(Session session, Instrumentation inst, Printer printer) throws Throwable { final RowAffect affect = new RowAffect(); if (timeFragmentManager.delete(index) != null) { affect.rCnt(1); } printer.println(format("Time fragment[%d] successfully deleted.", index)).finish(); return affect; } }; } /* * 绘制TimeTunnel表格 */ private String drawTimeTunnelTable(final ArrayList<TimeFragment> timeFragments) { final TTimeFragmentTable view = new TTimeFragmentTable(true); for (TimeFragment timeFragment : timeFragments) { view.add(timeFragment); } return view.rendering(); } /* * 展示指定记录 */ private RowAction doShow() { return new RowAction() { @Override public RowAffect action(Session session, Instrumentation inst, Printer printer) throws Throwable { final TimeFragment timeFragment = timeFragmentManager.get(index); if (null == timeFragment) { printer.println(format("Time fragment[%d] does not exist.", index)).finish(); return new RowAffect(); } printer.print(new TTimeFragmentDetail(inst, timeFragment, expend).rendering()).finish(); return new RowAffect(1); } }; } @Override public Action getAction() { // 检查参数 checkArguments(); final Action action; if (isTimeTunnel) { action = doTimeTunnel(); } else if (isList) { action = doList(); } else if (isDeleteAll) { action = doDeleteAll(); } else if (isDelete) { action = doDelete(); } else if (isPlay) { action = doPlay(); } else if (null != index) { if (hasWatchExpress()) { action = doWatch(); } else { action = doShow(); } } else if (hasSearchExpress()) { action = doSearch(); } else { action = new SilentAction() { @Override public void action(Session session, Instrumentation inst, Printer printer) throws Throwable { throw new UnsupportedOperationException("not support operation."); } }; } return action; } }