content: package com.taobao.arthas.core.command.monitor200;
  
  import com.alibaba.arthas.deps.org.slf4j.Logger;
  import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
  import com.taobao.arthas.core.GlobalOptions;
  import com.taobao.arthas.core.advisor.Advice;
  import com.taobao.arthas.core.advisor.AdviceListener;
  import com.taobao.arthas.core.advisor.ArthasMethod;
  import com.taobao.arthas.core.command.Constants;
  import com.taobao.arthas.core.command.express.ExpressException;
  import com.taobao.arthas.core.command.express.ExpressFactory;
  import com.taobao.arthas.core.command.model.*;
  import com.taobao.arthas.core.shell.command.CommandProcess;
  import com.taobao.arthas.core.shell.handlers.command.CommandInterruptHandler;
  import com.taobao.arthas.core.shell.handlers.shell.QExitHandler;
  import com.taobao.arthas.core.util.LogUtil;
  import com.taobao.arthas.core.util.SearchUtils;
  import com.taobao.arthas.core.util.StringUtils;
  import com.taobao.arthas.core.util.affect.RowAffect;
  import com.taobao.arthas.core.util.matcher.Matcher;
  import com.taobao.middleware.cli.annotations.*;
  
  import java.time.LocalDateTime;
  import java.util.ArrayList;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.atomic.AtomicInteger;
  
  import static java.lang.Integer.toHexString;
  import static java.lang.String.format;
  
  /**
   * 时光隧道命令<br/>
   * 参数w/d依赖于参数i所传递的记录编号<br/>
   *
   * @author vlinux on 14/11/15.
   */
  @Name("tt")
  @Summary("Time Tunnel")
  @Description(Constants.EXPRESS_DESCRIPTION + Constants.EXAMPLE +
          "  tt -t *StringUtils isEmpty\n" +
          "  tt -t *StringUtils isEmpty params[0].length==1\n" +
          "  tt -l\n" +
          "  tt -i 1000\n" +
          "  tt -i 1000 -w params[0]\n" +
          "  tt -i 1000 -p \n" +
          "  tt -i 1000 -p --replay-times 3 --replay-interval 3000\n" +
          "  tt -s '{params[0] > 1}' -w '{params}' \n" +
          "  tt --delete-all\n" +
          Constants.WIKI + Constants.WIKI_HOME + "tt")
  public class TimeTunnelCommand extends EnhancerCommand {
      // 时间隧道(时间碎片的集合)
      // TODO 并非线程安全？
      private static final Map<Integer, TimeFragment> timeFragmentMap = new LinkedHashMap<Integer, TimeFragment>();
      // 时间碎片序列生成器
      private static final AtomicInteger sequence = new AtomicInteger(1000);
      // TimeTunnel the method call
      private boolean isTimeTunnel = false;
      private String classPattern;
      private String methodPattern;
      private String conditionExpress;
      // list the TimeTunnel
      private boolean isList = false;
      private boolean isDeleteAll = false;
      // index of TimeTunnel
      private Integer index;
      // expand of TimeTunnel
      private Integer expand = 1;
      // upper size limit
      private Integer sizeLimit = 10 * 1024 * 1024;
      // watch the index TimeTunnel
      private String watchExpress = com.taobao.arthas.core.util.Constants.EMPTY_STRING;
      private String searchExpress = com.taobao.arthas.core.util.Constants.EMPTY_STRING;
      // play the index TimeTunnel
      private boolean isPlay = false;
      // delete the index TimeTunnel
      private boolean isDelete = false;
      private boolean isRegEx = false;
      private int numberOfLimit = 100;
      private int replayTimes = 1;
      private long replayInterval = 1000L;
      private static final Logger logger = LoggerFactory.getLogger(TimeTunnelCommand.class);
  
      @Argument(index = 0, argName = "class-pattern", required = false)
      @Description("Path and classname of Pattern Matching")
      public void setClassPattern(String classPattern) {
          this.classPattern = classPattern;
      }
  
      @Argument(index = 1, argName = "method-pattern", required = false)
      @Description("Method of Pattern Matching")
      public void setMethodPattern(String methodPattern) {
          this.methodPattern = methodPattern;
      }
  
      @Argument(index = 2, argName = "condition-express", required = false)
      @Description(Constants.CONDITION_EXPRESS)
      public void setConditionExpress(String conditionExpress) {
          this.conditionExpress = conditionExpress;
      }
  
      @Option(shortName = "t", longName = "time-tunnel", flag = true)
      @Description("Record the method invocation within time fragments")
      public void setTimeTunnel(boolean timeTunnel) {
          isTimeTunnel = timeTunnel;
      }
  
      @Option(shortName = "l", longName = "list", flag = true)
      @Description("List all the time fragments")
      public void setList(boolean list) {
          isList = list;
      }
  
      @Option(longName = "delete-all", flag = true)
      @Description("Delete all the time fragments")
      public void setDeleteAll(boolean deleteAll) {
          isDeleteAll = deleteAll;
      }
  
      @Option(shortName = "i", longName = "index")
      @Description("Display the detailed information from specified time fragment")
      public void setIndex(Integer index) {
          this.index = index;
      }
  
      @Option(shortName = "x", longName = "expand")
      @Description("Expand level of object (1 by default)")
      public void setExpand(Integer expand) {
          this.expand = expand;
      }
  
      @Option(shortName = "M", longName = "sizeLimit")
      @Description("Upper size limit in bytes for the result (10 * 1024 * 1024 by default)")
      public void setSizeLimit(Integer sizeLimit) {
          this.sizeLimit = sizeLimit;
      }
  
      @Option(shortName = "w", longName = "watch-express")
      @Description(value = "watch the time fragment by ognl express.\n" + Constants.EXPRESS_EXAMPLES)
      public void setWatchExpress(String watchExpress) {
          this.watchExpress = watchExpress;
      }
  
      @Option(shortName = "s", longName = "search-express")
      @Description("Search-expression, to search the time fragments by ognl express.\n" +
              "The structure of 'advice' like conditional expression")
      public void setSearchExpress(String searchExpress) {
          this.searchExpress = searchExpress;
      }
  
      @Option(shortName = "p", longName = "play", flag = true)
      @Description("Replay the time fragment specified by index")
      public void setPlay(boolean play) {
          isPlay = play;
      }
  
      @Option(shortName = "d", longName = "delete", flag = true)
      @Description("Delete time fragment specified by index")
      public void setDelete(boolean delete) {
          isDelete = delete;
      }
  
      @Option(shortName = "E", longName = "regex", flag = true)
      @Description("Enable regular expression to match (wildcard matching by default)")
      public void setRegEx(boolean regEx) {
          isRegEx = regEx;
      }
  
      @Option(shortName = "n", longName = "limits")
      @Description("Threshold of execution times, default value 100")
      public void setNumberOfLimit(int numberOfLimit) {
          this.numberOfLimit = numberOfLimit;
      }
  
  
      @Option(longName = "replay-times")
      @Description("execution times when play tt")
      public void setReplayTimes(int replayTimes) {
          this.replayTimes = replayTimes;
      }
  
      @Option(longName = "replay-interval")
      @Description("replay interval  for  play tt with option r greater than 1")
      public void setReplayInterval(int replayInterval) {
          this.replayInterval = replayInterval;
      }
  
  
      public boolean isRegEx() {
          return isRegEx;
      }
  
      public String getMethodPattern() {
          return methodPattern;
      }
  
      public String getClassPattern() {
          return classPattern;
      }
  
      public String getConditionExpress() {
          return conditionExpress;
      }
  
      public int getNumberOfLimit() {
          return numberOfLimit;
      }
  
      public int getReplayTimes() {
          return replayTimes;
      }
  
      public long getReplayInterval() {
          return replayInterval;
      }
  
      public Integer getExpand() {
          return expand;
      }
  
      private boolean hasWatchExpress() {
          return !StringUtils.isEmpty(watchExpress);
      }
  
      private boolean hasSearchExpress() {
          return !StringUtils.isEmpty(searchExpress);
      }
  
      /**
       * 检查参数是否合法
       */
      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 (StringUtils.isEmpty(classPattern)) {
                  throw new IllegalArgumentException("Class-pattern is expected, please type the wildcard expression to match");
              }
              if (StringUtils.isEmpty(methodPattern)) {
                  throw new IllegalArgumentException("Method-pattern is expected, please type the wildcard expression to match");
              }
          }
  
          // 一个参数都没有是不行滴
          if (null == index && !isTimeTunnel && !isDeleteAll && StringUtils.isEmpty(watchExpress)
                  && !isList && StringUtils.isEmpty(searchExpress)) {
              throw new IllegalArgumentException("Argument(s) is/are expected, type 'help tt' to read usage");
          }
      }
  
      /*
       * 记录时间片段
       */
      int putTimeTunnel(TimeFragment tt) {
          int indexOfSeq = sequence.getAndIncrement();
          timeFragmentMap.put(indexOfSeq, tt);
          return indexOfSeq;
      }
  
      @Override
      public void process(final CommandProcess process) {
          // 检查参数
          checkArguments();
  
          // ctrl-C support
          process.interruptHandler(new CommandInterruptHandler(process));
          // q exit support
          process.stdinHandler(new QExitHandler(process));
  
          if (isTimeTunnel) {
              enhance(process);
          } else if (isPlay) {
              processPlay(process);
          } else if (isList) {
              processList(process);
          } else if (isDeleteAll) {
              processDeleteAll(process);
          } else if (isDelete) {
              processDelete(process);
          } else if (hasSearchExpress()) {
              processSearch(process);
          } else if (index != null) {
              if (hasWatchExpress()) {
                  processWatch(process);
              } else {
                  processShow(process);
              }
          }
      }
  
      @Override
      protected Matcher getClassNameMatcher() {
          if (classNameMatcher == null) {
              classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx());
          }
          return classNameMatcher;
      }
  
      @Override
      protected Matcher getClassNameExcludeMatcher() {
          if (classNameExcludeMatcher == null && getExcludeClassPattern() != null) {
              classNameExcludeMatcher = SearchUtils.classNameMatcher(getExcludeClassPattern(), isRegEx());
          }
          return classNameExcludeMatcher;
      }
  
      @Override
      protected Matcher getMethodNameMatcher() {
          if (methodNameMatcher == null) {
              methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx());
          }
          return methodNameMatcher;
      }
  
      @Override
      protected AdviceListener getAdviceListener(CommandProcess process) {
          return new TimeTunnelAdviceListener(this, process, GlobalOptions.verbose || this.verbose);
      }
  
      // 展示指定记录
      private void processShow(CommandProcess process) {
          RowAffect affect = new RowAffect();
          try {
              TimeFragment tf = timeFragmentMap.get(index);
              if (null == tf) {
                  process.end(1, format("Time fragment[%d] does not exist.", index));
                  return;
              }
  
              TimeFragmentVO timeFragmentVO = createTimeFragmentVO(index, tf, expand);
              TimeTunnelModel timeTunnelModel = new TimeTunnelModel()
                      .setTimeFragment(timeFragmentVO)
                      .setExpand(expand)
                      .setSizeLimit(sizeLimit);
              process.appendResult(timeTunnelModel);
              affect.rCnt(1);
              process.appendResult(new RowAffectModel(affect));
              process.end();
          } catch (Throwable e) {
              logger.warn("tt failed.", e);
              process.end(1, e.getMessage() + ", visit " + LogUtil.loggingFile() + " for more detail");
          }
      }
  
      // 查看记录信息
      private void processWatch(CommandProcess process) {
          RowAffect affect = new RowAffect();
          try {
              final TimeFragment tf = timeFragmentMap.get(index);
              if (null == tf) {
                  process.end(1, format("Time fragment[%d] does not exist.", index));
                  return;
              }
  
              Advice advice = tf.getAdvice();
  
  			Object value = ExpressFactory.unpooledExpress(advice.getLoader()).bind(advice).get(watchExpress);
              TimeTunnelModel timeTunnelModel = new TimeTunnelModel()
                      .setWatchValue(new ObjectVO(value, expand))
                      .setExpand(expand)
                      .setSizeLimit(sizeLimit);
              process.appendResult(timeTunnelModel);
  
              affect.rCnt(1);
              process.appendResult(new RowAffectModel(affect));
              process.end();
          } catch (ExpressException e) {
              logger.warn("tt failed.", e);
              process.end(1, e.getMessage() + ", visit " + LogUtil.loggingFile() + " for more detail");
          }
      }
  
      // do search timeFragmentMap
      private void processSearch(CommandProcess process) {
          RowAffect affect = new RowAffect();
          try {
              // 匹配的时间片段
              Map<Integer, TimeFragment> matchingTimeSegmentMap = new LinkedHashMap<Integer, TimeFragment>();
              for (Map.Entry<Integer, TimeFragment> entry : timeFragmentMap.entrySet()) {
                  int index = entry.getKey();
                  TimeFragment tf = entry.getValue();
                  Advice advice = tf.getAdvice();
  
                  // 搜索出匹配的时间片段
                  if ((ExpressFactory.threadLocalExpress(advice)).is(searchExpress)) {
                      matchingTimeSegmentMap.put(index, tf);
                  }
              }
  
              if (hasWatchExpress()) {
                  // 执行watchExpress
                  Map<Integer, ObjectVO> searchResults = new LinkedHashMap<Integer, ObjectVO>();
                  for (Map.Entry<Integer, TimeFragment> entry : matchingTimeSegmentMap.entrySet()) {
                      Object value = ExpressFactory.threadLocalExpress(entry.getValue().getAdvice()).get(watchExpress);
                      searchResults.put(entry.getKey(), new ObjectVO(value, expand));
                  }
  
                  TimeTunnelModel timeTunnelModel = new TimeTunnelModel()
                          .setWatchResults(searchResults)
                          .setExpand(expand)
                          .setSizeLimit(sizeLimit);
                  process.appendResult(timeTunnelModel);
              } else {
                  // 单纯的列表格
                  List<TimeFragmentVO> timeFragmentList = createTimeTunnelVOList(matchingTimeSegmentMap);
                  process.appendResult(new TimeTunnelModel().setTimeFragmentList(timeFragmentList).setFirst(true));
              }
  
              affect.rCnt(matchingTimeSegmentMap.size());
              process.appendResult(new RowAffectModel(affect));
              process.end();
          } catch (ExpressException e) {
              logger.warn("tt failed.", e);
              process.end(1, e.getMessage() + ", visit " + LogUtil.loggingFile() + " for more detail");
          }
      }
  
      // 删除指定记录
      private void processDelete(CommandProcess process) {
          RowAffect affect = new RowAffect();
          if (timeFragmentMap.remove(index) != null) {
              affect.rCnt(1);
          }
          process.appendResult(new MessageModel(format("Time fragment[%d] successfully deleted.", index)));
          process.appendResult(new RowAffectModel(affect));
          process.end();
      }
  
      private void processDeleteAll(CommandProcess process) {
          int count = timeFragmentMap.size();
          RowAffect affect = new RowAffect(count);
          timeFragmentMap.clear();
          process.appendResult(new MessageModel("Time fragments are cleaned."));
          process.appendResult(new RowAffectModel(affect));
          process.end();
      }
  
      private void processList(CommandProcess process) {
          RowAffect affect = new RowAffect();
          List<TimeFragmentVO> timeFragmentList = createTimeTunnelVOList(timeFragmentMap);
          process.appendResult(new TimeTunnelModel().setTimeFragmentList(timeFragmentList).setFirst(true));
          affect.rCnt(timeFragmentMap.size());
          process.appendResult(new RowAffectModel(affect));
          process.end();
      }
  
      private List<TimeFragmentVO> createTimeTunnelVOList(Map<Integer, TimeFragment> timeFragmentMap) {
          List<TimeFragmentVO> timeFragmentList = new ArrayList<TimeFragmentVO>(timeFragmentMap.size());
          for (Map.Entry<Integer, TimeFragment> entry : timeFragmentMap.entrySet()) {
              timeFragmentList.add(createTimeFragmentVO(entry.getKey(), entry.getValue(), expand));
          }
          return timeFragmentList;
      }
  
      public static TimeFragmentVO createTimeFragmentVO(Integer index, TimeFragment tf, Integer expand) {
          Advice advice = tf.getAdvice();
          String object = advice.getTarget() == null
                  ? "NULL"
                  : "0x" + toHexString(advice.getTarget().hashCode());
  
          return new TimeFragmentVO()
                  .setIndex(index)
                  .setTimestamp(tf.getGmtCreate())
                  .setCost(tf.getCost())
                  .setParams(ObjectVO.array(advice.getParams(), expand))
                  .setReturn(advice.isAfterReturning())
                  .setReturnObj(new ObjectVO(advice.getReturnObj(), expand))
                  .setThrow(advice.isAfterThrowing())
                  .setThrowExp(new ObjectVO(advice.getThrowExp(), expand))
                  .setObject(object)
                  .setClassName(advice.getClazz().getName())
                  .setMethodName(advice.getMethod().getName());
      }
  
      /**
       * 重放指定记录
       */
      private void processPlay(CommandProcess process) {
          TimeFragment tf = timeFragmentMap.get(index);
          if (null == tf) {
              process.end(1, format("Time fragment[%d] does not exist.", index));
              return;
          }
          Advice advice = tf.getAdvice();
          ArthasMethod method = advice.getMethod();
          boolean accessible = advice.getMethod().isAccessible();
          try {
              if (!accessible) {
                  method.setAccessible(true);
              }
              for (int i = 0; i < getReplayTimes(); i++) {
                  if (i > 0) {
                      //wait for the next execution
                      Thread.sleep(getReplayInterval());
                      if (!process.isRunning()) {
                          return;
                      }
                  }
                  long beginTime = System.nanoTime();
  
                  //copy from tt record
                  TimeFragmentVO replayResult = createTimeFragmentVO(index, tf, expand);
                  replayResult.setTimestamp(LocalDateTime.now())
                          .setCost(0)
                          .setReturn(false)
                          .setReturnObj(null)
                          .setThrow(false)
                          .setThrowExp(null);
  
                  try {
                      //execute successful
                      Object returnObj = method.invoke(advice.getTarget(), advice.getParams());
                      double cost = (System.nanoTime() - beginTime) / 1000000.0;
                      replayResult.setCost(cost)
                              .setReturn(true)
                              .setReturnObj(new ObjectVO(returnObj, expand));
                  } catch (Throwable t) {
                      //throw exp
                      double cost = (System.nanoTime() - beginTime) / 1000000.0;
                      replayResult.setCost(cost)
                              .setThrow(true)
                              .setThrowExp(new ObjectVO(t, expand));
                  }
  
                  TimeTunnelModel timeTunnelModel = new TimeTunnelModel()
                          .setReplayResult(replayResult)
                          .setReplayNo(i + 1)
                          .setExpand(expand)
                          .setSizeLimit(sizeLimit);
                  process.appendResult(timeTunnelModel);
              }
              process.end();
          } catch (Throwable t) {
              logger.warn("tt replay failed.", t);
              process.end(-1, "tt replay failed");
          } finally {
              method.setAccessible(accessible);
          }
      }
  }
  