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.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.server.Session; import com.github.ompc.greys.core.textui.ext.TObject; import com.github.ompc.greys.core.util.InvokeCost; import com.github.ompc.greys.core.util.LogUtil; 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 org.slf4j.Logger; 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.getCauseMessage; import static org.apache.commons.lang3.StringUtils.isBlank; @Cmd(name = "watch", sort = 4, summary = "Display the details of specified class and method", eg = { "watch -Eb org\\.apache\\.commons\\.lang\\.StringUtils isBlank params[0]", "watch -b org.apache.commons.lang.StringUtils isBlank params[0]", "watch -f org.apache.commons.lang.StringUtils isBlank returnObj", "watch -bf *StringUtils isBlank params[0]", "watch *StringUtils isBlank params[0]", "watch *StringUtils isBlank params[0] 'params[0].length==1'", "watch *StringUtils isBlank params[0] '#cost>100'", }) public class WatchCommand implements Command { private final Logger logger = LogUtil.getLogger(); @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 = "express", summary = "express, write by OGNL.", description = "" + "FOR EXAMPLE" + " 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 express; @IndexArg(index = 3, 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 = "b", summary = "Watch before invocation") private boolean isBefore = false; @NamedArg(name = "f", summary = "Watch after invocation") private boolean isFinish = false; @NamedArg(name = "e", summary = "Watch after throw exception") private boolean isException = false; @NamedArg(name = "s", summary = "Watch after successful invocation") private boolean isSuccess = false; @NamedArg(name = "x", hasValue = true, summary = "Expand level of object (0 by default)") private Integer expend; @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() { // setValue default // 如果没有强行指定b/f/e/s中任何一个,则默认为b if (!isBefore && !isFinish && !isException && !isSuccess) { isBefore = true; } 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 final InvokeCost invokeCost = new InvokeCost(); @Override public void before(Advice advice) throws Throwable { invokeCost.begin(); if (isBefore) { watching(advice); } } @Override public void afterReturning(Advice advice) throws Throwable { if (isSuccess) { watching(advice); } } @Override public void afterThrowing(Advice advice) throws Throwable { if (isException) { watching(advice); } } @Override public void afterFinishing(Advice advice) throws Throwable { if (isFinish) { watching(advice); } } private boolean isOverThreshold(int currentTimes) { return null != threshold && currentTimes >= threshold; } private boolean isInCondition(Advice advice) { try { return isBlank(conditionExpress) || newExpress(advice).bind("cost", invokeCost.cost()).is(conditionExpress); } catch (ExpressException e) { return false; } } private void watching(Advice advice) { try { if (isInCondition(advice)) { printer.println(new TObject(newExpress(advice).get(express), expend).rendering()); if (isOverThreshold(timesRef.incrementAndGet())) { printer.finish(); } } } catch (Exception e) { logger.warn("watch failed.", e); printer.println(getCauseMessage(e)); } } }; } }; } }; } }