diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java index ce0cd1cf7..0f34ed032 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/ClassLoaderCommand.java @@ -190,7 +190,7 @@ public class ClassLoaderCommand extends AnnotatedCommand { if (cl instanceof URLClassLoader) { List classLoaderUrls = getClassLoaderUrls(cl); affect.rCnt(classLoaderUrls.size()); - if (classLoaderUrls.isEmpty()){ + if (classLoaderUrls.isEmpty()) { process.appendResult(new MessageModel("urls is empty.")); } else { process.appendResult(new ClassLoaderModel().setUrls(classLoaderUrls)); @@ -198,9 +198,9 @@ public class ClassLoaderCommand extends AnnotatedCommand { } } else { process.appendResult(new MessageModel("not a URLClassLoader.")); - } + } break; - } + } } process.appendResult(new RowAffectModel(affect)); process.end(); diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/EnhancerAffectVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/EnhancerAffectVO.java index 84efb2542..dff1b376b 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/model/EnhancerAffectVO.java +++ b/core/src/main/java/com/taobao/arthas/core/command/model/EnhancerAffectVO.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; /** + * Class enhance affect vo * @author gongdewei 2020/6/22 */ public class EnhancerAffectVO { diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/EnhancerModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/EnhancerModel.java new file mode 100644 index 000000000..45dcc291b --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/EnhancerModel.java @@ -0,0 +1,47 @@ +package com.taobao.arthas.core.command.model; + +import com.taobao.arthas.core.util.affect.EnhancerAffect; + +/** + * Data model of EnhancerCommand + * + * @author gongdewei 2020/7/20 + */ +public class EnhancerModel extends ResultModel { + + private final EnhancerAffectVO effect; + private boolean success; + private String message; + + public EnhancerModel(EnhancerAffect effect, boolean success) { + if (effect != null) { + this.effect = new EnhancerAffectVO(effect); + this.success = success; + } else { + this.effect = new EnhancerAffectVO(-1, 0, 0, -1); + this.success = false; + } + } + + public EnhancerModel(EnhancerAffect effect, boolean success, String message) { + this(effect, success); + this.message = message; + } + + @Override + public String getType() { + return "enhancer"; + } + + public EnhancerAffectVO getEffect() { + return effect; + } + + public boolean isSuccess() { + return success; + } + + public String getMessage() { + return message; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/GetStaticModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/GetStaticModel.java index b4c62842d..4c61f07c6 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/model/GetStaticModel.java +++ b/core/src/main/java/com/taobao/arthas/core/command/model/GetStaticModel.java @@ -3,15 +3,14 @@ package com.taobao.arthas.core.command.model; import java.util.Collection; /** + * Data model of GetStaticCommand * @author gongdewei 2020/4/20 */ public class GetStaticModel extends ResultModel { - private ObjectVO field; private Collection matchedClasses; - - //only for view - private transient int expand; + private ObjectVO field; + private int expand; public GetStaticModel() { } @@ -41,7 +40,7 @@ public class GetStaticModel extends ResultModel { this.matchedClasses = matchedClasses; } - public int expand() { + public int getExpand() { return expand; } diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/MethodNode.java b/core/src/main/java/com/taobao/arthas/core/command/model/MethodNode.java new file mode 100644 index 000000000..fca7e4b93 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/MethodNode.java @@ -0,0 +1,148 @@ +package com.taobao.arthas.core.command.model; + +/** + * Method call node of TraceCommand + * @author gongdewei 2020/4/29 + */ +public class MethodNode extends TraceNode { + + private String className; + private String methodName; + private int lineNumber; + private Boolean isThrow; + private String throwExp; + + /** + * 是否为invoke方法,true为beforeInvoke,false为方法体入口的onBefore + */ + private boolean isInvoking; + + /** + * 开始时间戳 + */ + private long beginTimestamp; + + /** + * 结束时间戳 + */ + private long endTimestamp; + + /** + * 合并统计相同调用,并计算最小\最大\总耗时 + */ + private long minCost = Long.MAX_VALUE; + private long maxCost = Long.MIN_VALUE; + private long totalCost = 0; + private long times = 0; + + + public MethodNode(String className, String methodName, int lineNumber, boolean isInvoking) { + super("method"); + this.className = className; + this.methodName = methodName; + this.lineNumber = lineNumber; + this.isInvoking = isInvoking; + } + + public void begin() { + beginTimestamp = System.nanoTime(); + } + + public void end() { + endTimestamp = System.nanoTime(); + + long cost = getCost(); + if (cost < minCost) { + minCost = cost; + } + if (cost > maxCost) { + maxCost = cost; + } + times++; + totalCost += cost; + } + + public long getCost() { + return endTimestamp - beginTimestamp; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public Boolean getThrow() { + return isThrow; + } + + public void setThrow(Boolean aThrow) { + isThrow = aThrow; + } + + public String getThrowExp() { + return throwExp; + } + + public void setThrowExp(String throwExp) { + this.throwExp = throwExp; + } + + public long getMinCost() { + return minCost; + } + + public void setMinCost(long minCost) { + this.minCost = minCost; + } + + public long getMaxCost() { + return maxCost; + } + + public void setMaxCost(long maxCost) { + this.maxCost = maxCost; + } + + public long getTotalCost() { + return totalCost; + } + + public void setTotalCost(long totalCost) { + this.totalCost = totalCost; + } + + public long getTimes() { + return times; + } + + public void setTimes(long times) { + this.times = times; + } + + public boolean isInvoking() { + return isInvoking; + } + + public void setInvoking(boolean invoking) { + isInvoking = invoking; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/MonitorModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/MonitorModel.java new file mode 100644 index 000000000..0b87817ea --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/MonitorModel.java @@ -0,0 +1,34 @@ +package com.taobao.arthas.core.command.model; + +import com.taobao.arthas.core.command.monitor200.MonitorData; + +import java.util.List; + +/** + * Data model of MonitorCommand + * @author gongdewei 2020/4/28 + */ +public class MonitorModel extends ResultModel { + + private List monitorDataList; + + public MonitorModel() { + } + + public MonitorModel(List monitorDataList) { + this.monitorDataList = monitorDataList; + } + + @Override + public String getType() { + return "monitor"; + } + + public List getMonitorDataList() { + return monitorDataList; + } + + public void setMonitorDataList(List monitorDataList) { + this.monitorDataList = monitorDataList; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ProfilerModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/ProfilerModel.java new file mode 100644 index 000000000..1847d3595 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/ProfilerModel.java @@ -0,0 +1,77 @@ +package com.taobao.arthas.core.command.model; + +import java.util.Collection; + +/** + * Data model of ProfilerCommand + * @author gongdewei 2020/4/27 + */ +public class ProfilerModel extends ResultModel { + + private String action; + private String actionArg; + private String executeResult; + private Collection supportedActions; + private String outputFile; + private Long duration; + + public ProfilerModel() { + } + + public ProfilerModel(Collection supportedActions) { + this.supportedActions = supportedActions; + } + + @Override + public String getType() { + return "profiler"; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getActionArg() { + return actionArg; + } + + public void setActionArg(String actionArg) { + this.actionArg = actionArg; + } + + public Collection getSupportedActions() { + return supportedActions; + } + + public void setSupportedActions(Collection supportedActions) { + this.supportedActions = supportedActions; + } + + public String getExecuteResult() { + return executeResult; + } + + public void setExecuteResult(String executeResult) { + this.executeResult = executeResult; + } + + public String getOutputFile() { + return outputFile; + } + + public void setOutputFile(String outputFile) { + this.outputFile = outputFile; + } + + public Long getDuration() { + return duration; + } + + public void setDuration(Long duration) { + this.duration = duration; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/StackModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/StackModel.java new file mode 100644 index 000000000..a2c0ae364 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/StackModel.java @@ -0,0 +1,107 @@ +package com.taobao.arthas.core.command.model; + +import java.util.Date; + +/** + * StackCommand result model + * @author gongdewei 2020/4/13 + */ +public class StackModel extends ResultModel { + + private Date ts; + private double cost; + private String traceId; + private String rpcId; + private String threadName; + private String threadId; + private boolean daemon; + private int priority; + /* Thread Current ClassLoader */ + private String classloader; + private StackTraceElement[] stackTrace; + + @Override + public String getType() { + return "stack"; + } + + public Date getTs() { + return ts; + } + + public void setTs(Date ts) { + this.ts = ts; + } + + public double getCost() { + return cost; + } + + public void setCost(double cost) { + this.cost = cost; + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + public String getThreadId() { + return threadId; + } + + public void setThreadId(String threadId) { + this.threadId = threadId; + } + + public boolean isDaemon() { + return daemon; + } + + public void setDaemon(boolean daemon) { + this.daemon = daemon; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public String getClassloader() { + return classloader; + } + + public void setClassloader(String classloader) { + this.classloader = classloader; + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + + public String getRpcId() { + return rpcId; + } + + public void setRpcId(String rpcId) { + this.rpcId = rpcId; + } + + public StackTraceElement[] getStackTrace() { + return stackTrace; + } + + public void setStackTrace(StackTraceElement[] stackTrace) { + this.stackTrace = stackTrace; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ThreadNode.java b/core/src/main/java/com/taobao/arthas/core/command/model/ThreadNode.java new file mode 100644 index 000000000..070e89e21 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/ThreadNode.java @@ -0,0 +1,99 @@ +package com.taobao.arthas.core.command.model; + +import java.util.Date; + +/** + * Thread root node of TraceCommand + * @author gongdewei 2020/4/29 + */ +public class ThreadNode extends TraceNode { + + private String threadName; + private long threadId; + private boolean daemon; + private int priority; + private String classloader; + private Date timestamp; + + private String traceId; + private String rpcId; + + public ThreadNode() { + super("thread"); + timestamp = new Date(); + } + + public ThreadNode(String threadName, long threadId, boolean daemon, int priority, String classloader) { + super("thread"); + this.threadName = threadName; + this.threadId = threadId; + this.daemon = daemon; + this.priority = priority; + this.classloader = classloader; + timestamp = new Date(); + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + public long getThreadId() { + return threadId; + } + + public void setThreadId(long threadId) { + this.threadId = threadId; + } + + public boolean isDaemon() { + return daemon; + } + + public void setDaemon(boolean daemon) { + this.daemon = daemon; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public String getClassloader() { + return classloader; + } + + public void setClassloader(String classloader) { + this.classloader = classloader; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + + public String getRpcId() { + return rpcId; + } + + public void setRpcId(String rpcId) { + this.rpcId = rpcId; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ThrowNode.java b/core/src/main/java/com/taobao/arthas/core/command/model/ThrowNode.java new file mode 100644 index 000000000..d234669d9 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/ThrowNode.java @@ -0,0 +1,40 @@ +package com.taobao.arthas.core.command.model; + +/** + * Throw exception info node of TraceCommand + * @author gongdewei 2020/7/21 + */ +public class ThrowNode extends TraceNode { + private String exception; + private String message; + private int lineNumber; + + public ThrowNode() { + super("throw"); + } + + public String getException() { + return exception; + } + + public void setException(String exception) { + this.exception = exception; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/TimeFragmentVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/TimeFragmentVO.java new file mode 100644 index 000000000..591b9f1cc --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/TimeFragmentVO.java @@ -0,0 +1,123 @@ +package com.taobao.arthas.core.command.model; + +import java.util.Date; + +/** + * VO for TimeFragment + * @author gongdewei 2020/4/27 + */ +public class TimeFragmentVO { + private Integer index; + private Date timestamp; + private double cost; + private boolean isReturn; + private boolean isThrow; + private String object; + private String className; + private String methodName; + private Object[] params; + private Object returnObj; + private Throwable throwExp; + + public TimeFragmentVO() { + } + + public Integer getIndex() { + return index; + } + + public TimeFragmentVO setIndex(Integer index) { + this.index = index; + return this; + } + + public Date getTimestamp() { + return timestamp; + } + + public TimeFragmentVO setTimestamp(Date timestamp) { + this.timestamp = timestamp; + return this; + } + + public double getCost() { + return cost; + } + + public TimeFragmentVO setCost(double cost) { + this.cost = cost; + return this; + } + + public boolean isReturn() { + return isReturn; + } + + public TimeFragmentVO setReturn(boolean aReturn) { + isReturn = aReturn; + return this; + } + + public boolean isThrow() { + return isThrow; + } + + public TimeFragmentVO setThrow(boolean aThrow) { + isThrow = aThrow; + return this; + } + + public String getObject() { + return object; + } + + public TimeFragmentVO setObject(String object) { + this.object = object; + return this; + } + + public String getClassName() { + return className; + } + + public TimeFragmentVO setClassName(String className) { + this.className = className; + return this; + } + + public String getMethodName() { + return methodName; + } + + public TimeFragmentVO setMethodName(String methodName) { + this.methodName = methodName; + return this; + } + + public Object[] getParams() { + return params; + } + + public TimeFragmentVO setParams(Object[] params) { + this.params = params; + return this; + } + + public Object getReturnObj() { + return returnObj; + } + + public TimeFragmentVO setReturnObj(Object returnObj) { + this.returnObj = returnObj; + return this; + } + + public Throwable getThrowExp() { + return throwExp; + } + + public TimeFragmentVO setThrowExp(Throwable throwExp) { + this.throwExp = throwExp; + return this; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/TimeTunnelModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/TimeTunnelModel.java new file mode 100644 index 000000000..f6b9c1c5b --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/TimeTunnelModel.java @@ -0,0 +1,122 @@ +package com.taobao.arthas.core.command.model; + +import java.util.List; +import java.util.Map; + +/** + * Data model of TimeTunnelCommand + * @author gongdewei 2020/4/27 + */ +public class TimeTunnelModel extends ResultModel { + + //查看列表 + private List timeFragmentList; + + //是否为第一次输出(需要加表头) + private Boolean isFirst; + + //查看单条记录 + private TimeFragmentVO timeFragment; + + //重放执行的结果 + private TimeFragmentVO replayResult; + + //重放执行的次数 + private Integer replayNo; + + private Object watchValue; + + //search: tt -s {} -w {} + private Map watchResults; + + private Integer expand; + + private Integer sizeLimit; + + + @Override + public String getType() { + return "tt"; + } + + public List getTimeFragmentList() { + return timeFragmentList; + } + + public TimeTunnelModel setTimeFragmentList(List timeFragmentList) { + this.timeFragmentList = timeFragmentList; + return this; + } + + public TimeFragmentVO getTimeFragment() { + return timeFragment; + } + + public TimeTunnelModel setTimeFragment(TimeFragmentVO timeFragment) { + this.timeFragment = timeFragment; + return this; + } + + public Integer getExpand() { + return expand; + } + + public TimeTunnelModel setExpand(Integer expand) { + this.expand = expand; + return this; + } + + public Integer getSizeLimit() { + return sizeLimit; + } + + public TimeTunnelModel setSizeLimit(Integer sizeLimit) { + this.sizeLimit = sizeLimit; + return this; + } + + public Object getWatchValue() { + return watchValue; + } + + public TimeTunnelModel setWatchValue(Object watchValue) { + this.watchValue = watchValue; + return this; + } + + public Map getWatchResults() { + return watchResults; + } + + public TimeTunnelModel setWatchResults(Map watchResults) { + this.watchResults = watchResults; + return this; + } + + public TimeFragmentVO getReplayResult() { + return replayResult; + } + + public TimeTunnelModel setReplayResult(TimeFragmentVO replayResult) { + this.replayResult = replayResult; + return this; + } + + public Integer getReplayNo() { + return replayNo; + } + + public TimeTunnelModel setReplayNo(Integer replayNo) { + this.replayNo = replayNo; + return this; + } + + public Boolean getFirst() { + return isFirst; + } + + public TimeTunnelModel setFirst(Boolean first) { + isFirst = first; + return this; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/TraceModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/TraceModel.java new file mode 100644 index 000000000..55dafaa32 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/TraceModel.java @@ -0,0 +1,39 @@ +package com.taobao.arthas.core.command.model; + +/** + * Data model of TraceCommand + * @author gongdewei 2020/4/29 + */ +public class TraceModel extends ResultModel { + private TraceNode root; + private int nodeCount; + + public TraceModel() { + } + + public TraceModel(TraceNode root, int nodeCount) { + this.root = root; + this.nodeCount = nodeCount; + } + + @Override + public String getType() { + return "trace"; + } + + public TraceNode getRoot() { + return root; + } + + public void setRoot(TraceNode root) { + this.root = root; + } + + public int getNodeCount() { + return nodeCount; + } + + public void setNodeCount(int nodeCount) { + this.nodeCount = nodeCount; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/TraceNode.java b/core/src/main/java/com/taobao/arthas/core/command/model/TraceNode.java new file mode 100644 index 000000000..a4a9e9c2e --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/TraceNode.java @@ -0,0 +1,79 @@ +package com.taobao.arthas.core.command.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Abstract Node of TraceCommand + * @author gongdewei 2020/4/28 + */ +public abstract class TraceNode { + + protected TraceNode parent; + protected List children; + + /** + * node type: method, + */ + private String type; + + /** + * 备注 + */ + private String mark; + /** + * TODO marks数量的作用?是否可以去掉 + */ + private int marks = 0; + + public TraceNode(String type) { + this.type = type; + } + + public void addChild(TraceNode child) { + if (children == null) { + children = new ArrayList(); + } + this.children.add(child); + child.setParent(this); + } + + public void setMark(String mark) { + this.mark = mark; + marks++; + } + + public String getMark() { + return mark; + } + + public Integer marks() { + return marks; + } + + public void begin() { + } + + public void end() { + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public TraceNode parent() { + return parent; + } + + public void setParent(TraceNode parent) { + this.parent = parent; + } + + public List getChildren() { + return children; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/TraceTree.java b/core/src/main/java/com/taobao/arthas/core/command/model/TraceTree.java new file mode 100644 index 000000000..f4957346b --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/TraceTree.java @@ -0,0 +1,132 @@ +package com.taobao.arthas.core.command.model; + + +import com.taobao.arthas.core.util.StringUtils; + +import java.util.List; + +/** + * Tree model of TraceCommand + * @author gongdewei 2020/4/28 + */ +public class TraceTree { + private TraceNode root; + + private TraceNode current; + private int nodeCount = 0; + + public TraceTree(ThreadNode root) { + this.root = root; + this.current = root; + } + + /** + * Begin a new method call + * @param className className of method + * @param methodName method name of the call + * @param lineNumber line number of invoke point + * @param isInvoking Whether to invoke this method in other classes + */ + public void begin(String className, String methodName, int lineNumber, boolean isInvoking) { + TraceNode child = findChild(current, className, methodName, lineNumber); + if (child == null) { + child = new MethodNode(className, methodName, lineNumber, isInvoking); + current.addChild(child); + } + child.begin(); + current = child; + nodeCount += 1; + } + + private TraceNode findChild(TraceNode node, String className, String methodName, int lineNumber) { + List childList = node.getChildren(); + if (childList != null) { + //less memory than foreach/iterator + for (int i = 0; i < childList.size(); i++) { + TraceNode child = childList.get(i); + if (matchNode(child, className, methodName, lineNumber)) { + return child; + } + } + } + return null; + } + + private boolean matchNode(TraceNode node, String className, String methodName, int lineNumber) { + if (node instanceof MethodNode) { + MethodNode methodNode = (MethodNode) node; + if (lineNumber != methodNode.getLineNumber()) return false; + if (className != null ? !className.equals(methodNode.getClassName()) : methodNode.getClassName() != null) return false; + return methodName != null ? methodName.equals(methodNode.getMethodName()) : methodNode.getMethodName() == null; + } + return false; + } + + public void end() { + current.end(); + if (current.parent() != null) { + //TODO 为什么会到达这里? 调用end次数比begin多? + current = current.parent(); + } + } + + public void end(Throwable throwable, int lineNumber) { + ThrowNode throwNode = new ThrowNode(); + throwNode.setException(throwable.getClass().getName()); + throwNode.setMessage(throwable.getMessage()); + throwNode.setLineNumber(lineNumber); + current.addChild(throwNode); + this.end(true); + } + + public void end(boolean isThrow) { + if (isThrow) { + current.setMark("throws Exception"); + if (current instanceof MethodNode) { + MethodNode methodNode = (MethodNode) current; + methodNode.setThrow(true); + } + } + this.end(); + } + + /** + * 修整树结点 + */ + public void trim() { + this.normalizeClassName(root); + } + + /** + * 转换标准类名,放在trace结束后统一转换,减少重复操作 + * @param node + */ + private void normalizeClassName(TraceNode node) { + if (node instanceof MethodNode) { + MethodNode methodNode = (MethodNode) node; + String nodeClassName = methodNode.getClassName(); + String normalizeClassName = StringUtils.normalizeClassName(nodeClassName); + methodNode.setClassName(normalizeClassName); + } + List children = node.getChildren(); + if (children != null) { + //less memory fragment than foreach + for (int i = 0; i < children.size(); i++) { + TraceNode child = children.get(i); + normalizeClassName(child); + } + } + } + + public TraceNode getRoot() { + return root; + } + + public TraceNode current() { + return current; + } + + public int getNodeCount() { + return nodeCount; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/WatchModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/WatchModel.java new file mode 100644 index 000000000..d1e8d28b5 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/WatchModel.java @@ -0,0 +1,66 @@ +package com.taobao.arthas.core.command.model; + +import java.util.Date; + +/** + * Watch command result model + * + * @author gongdewei 2020/03/26 + */ +public class WatchModel extends ResultModel { + + private Date ts; + private double cost; + private Object value; + + private Integer expand; + private Integer sizeLimit; + + public WatchModel() { + } + + @Override + public String getType() { + return "watch"; + } + + public Date getTs() { + return ts; + } + + public void setTs(Date ts) { + this.ts = ts; + } + + public double getCost() { + return cost; + } + + public Object getValue() { + return value; + } + + public void setCost(double cost) { + this.cost = cost; + } + + public void setValue(Object value) { + this.value = value; + } + + public void setExpand(Integer expand) { + this.expand = expand; + } + + public void setSizeLimit(Integer sizeLimit) { + this.sizeLimit = sizeLimit; + } + + public Integer getExpand() { + return expand; + } + + public Integer getSizeLimit() { + return sizeLimit; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/AbstractTraceAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/AbstractTraceAdviceListener.java index 553c7d0d0..e359eed43 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/AbstractTraceAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/AbstractTraceAdviceListener.java @@ -42,7 +42,7 @@ public class AbstractTraceAdviceListener extends AdviceListenerAdapter { @Override public void before(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args) throws Throwable { - threadBoundEntity.get().view.begin(clazz.getName() + ":" + method.getName() + "()"); + threadBoundEntity.get().tree.begin(clazz.getName(), method.getName(), -1, false); threadBoundEntity.get().deep++; // 开始计算本次方法调用耗时 threadLocalWatch.start(); @@ -51,7 +51,7 @@ public class AbstractTraceAdviceListener extends AdviceListenerAdapter { @Override public void afterReturning(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, Object returnObject) throws Throwable { - threadBoundEntity.get().view.end(); + threadBoundEntity.get().tree.end(); final Advice advice = Advice.newForAfterRetuning(loader, clazz, method, target, args, returnObject); finishing(advice); } @@ -60,7 +60,7 @@ public class AbstractTraceAdviceListener extends AdviceListenerAdapter { public void afterThrowing(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, Throwable throwable) throws Throwable { int lineNumber = throwable.getStackTrace()[0].getLineNumber(); - threadBoundEntity.get().view.begin("throw:" + throwable.getClass().getName() + "()" + " #" + lineNumber).end().end(); + threadBoundEntity.get().tree.end(throwable, lineNumber); final Advice advice = Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable); finishing(advice); } @@ -80,20 +80,20 @@ public class AbstractTraceAdviceListener extends AdviceListenerAdapter { } if (conditionResult) { // 满足输出条件 + process.times().incrementAndGet(); + // TODO: concurrency issues for process.write + process.appendResult(threadBoundEntity.get().getModel()); + + // 是否到达数量限制 if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) { // TODO: concurrency issue to abort process abortProcess(process, command.getNumberOfLimit()); - } else { - process.times().incrementAndGet(); - // TODO: concurrency issues for process.write - process.write(threadBoundEntity.get().view.draw() + "\n"); } } } catch (Throwable e) { logger.warn("trace failed.", e); - process.write("trace failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage() - + ", visit " + LogUtil.loggingFile() + " for more details.\n"); - process.end(); + process.end(1, "trace failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage() + + ", visit " + LogUtil.loggingFile() + " for more details."); } finally { threadBoundEntity.remove(); } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java index b92c0ccc0..056543938 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java @@ -11,6 +11,7 @@ import com.taobao.arthas.core.advisor.AdviceListener; import com.taobao.arthas.core.advisor.AdviceWeaver; import com.taobao.arthas.core.advisor.Enhancer; import com.taobao.arthas.core.advisor.InvokeTraceable; +import com.taobao.arthas.core.command.model.EnhancerModel; import com.taobao.arthas.core.shell.cli.Completion; import com.taobao.arthas.core.shell.cli.CompletionUtils; import com.taobao.arthas.core.shell.command.AnnotatedCommand; @@ -120,16 +121,21 @@ public abstract class EnhancerCommand extends AnnotatedCommand { protected void enhance(CommandProcess process) { Session session = process.session(); if (!session.tryLock()) { - process.write("someone else is enhancing classes, pls. wait.\n"); - process.end(); + String msg = "someone else is enhancing classes, pls. wait."; + process.appendResult(new EnhancerModel(null, false, msg)); + process.end(-1, msg); return; } + EnhancerAffect effect = null; int lock = session.getLock(); try { Instrumentation inst = session.getInstrumentation(); AdviceListener listener = getAdviceListenerWithId(process); if (listener == null) { - warn(process, "advice listener is null"); + logger.error("advice listener is null"); + String msg = "advice listener is null, check arthas log"; + process.appendResult(new EnhancerModel(effect, false, msg)); + process.end(-1, msg); return; } boolean skipJDKTrace = false; @@ -140,24 +146,25 @@ public abstract class EnhancerCommand extends AnnotatedCommand { Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getMethodNameMatcher()); // 注册通知监听器 process.register(listener, enhancer); - EnhancerAffect effect = enhancer.enhance(inst); + effect = enhancer.enhance(inst); if (effect.getThrowable() != null) { - process.write(effect.toString() + ", check arthas log: " + LogUtil.loggingFile() + "\n"); - process.end(1); + String msg = "error happens when enhancing class: "+effect.getThrowable().getMessage(); + process.appendResult(new EnhancerModel(effect, false, msg)); + process.end(1, msg + ", check arthas log: " + LogUtil.loggingFile()); return; } if (effect.cCnt() == 0 || effect.mCnt() == 0) { // no class effected // might be method code too large - process.write("Matched class count: " + effect.cCnt() + ", method count: " + effect.mCnt() + "\n"); - process.write("No class or method is affected, try:\n" - + "1. sm CLASS_NAME METHOD_NAME to make sure the method you are tracing actually exists (it might be in your parent class).\n" - + "2. reset CLASS_NAME and try again, your method body might be too large.\n" - + "3. check arthas log: " + LogUtil.loggingFile() + "\n" - + "4. visit https://github.com/alibaba/arthas/issues/47 for more details.\n"); - process.end(); + process.appendResult(new EnhancerModel(effect, false, "No class or method is affected")); + String msg = "No class or method is affected, try:\n" + + "1. sm CLASS_NAME METHOD_NAME to make sure the method you are tracing actually exists (it might be in your parent class).\n" + + "2. reset CLASS_NAME and try again, your method body might be too large.\n" + + "3. check arthas log: " + LogUtil.loggingFile() + "\n" + + "4. visit https://github.com/alibaba/arthas/issues/47 for more details."; + process.end(-1, msg); return; } @@ -168,9 +175,14 @@ public abstract class EnhancerCommand extends AnnotatedCommand { } } - process.write(effect + "\n"); - } catch (UnmodifiableClassException e) { - logger.error("error happens when enhancing class", e); + process.appendResult(new EnhancerModel(effect, true)); + + //异步执行,在AdviceListener中结束 + } catch (Throwable e) { + String msg = "error happens when enhancing class: "+e.getMessage(); + logger.error(msg, e); + process.appendResult(new EnhancerModel(effect, false, msg)); + process.end(-1, msg); } finally { if (session.getLock() == lock) { // enhance结束后解锁 @@ -182,13 +194,4 @@ public abstract class EnhancerCommand extends AnnotatedCommand { protected void completeArgument3(Completion completion) { super.complete(completion); } - - private static void warn(CommandProcess process, String message) { - logger.error(message); - process.write("cannot operate the current command, pls. check arthas.log\n"); - if (process.isForeground()) { - process.echoTips(Constants.Q_OR_CTRL_C_ABORT_MSG + "\n"); - } - } - } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorAdviceListener.java index 7b04598f4..bbcc7c6da 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorAdviceListener.java @@ -1,16 +1,13 @@ package com.taobao.arthas.core.command.monitor200; import com.taobao.arthas.core.advisor.AdviceListenerAdapter; +import com.taobao.arthas.core.command.model.MonitorModel; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.advisor.ArthasMethod; import com.taobao.arthas.core.util.ThreadLocalWatch; -import com.taobao.text.Decoration; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; @@ -18,7 +15,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals; -import static com.taobao.text.ui.Element.label; /** * 输出的内容格式为:
@@ -70,7 +66,7 @@ class MonitorAdviceListener extends AdviceListenerAdapter { // 输出定时任务 private Timer timer; // 监控数据 - private ConcurrentHashMap> monitorData = new ConcurrentHashMap>(); + private ConcurrentHashMap> monitorData = new ConcurrentHashMap>(); private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch(); private MonitorCommand command; private CommandProcess process; @@ -121,15 +117,15 @@ class MonitorAdviceListener extends AdviceListenerAdapter { final Key key = new Key(clazz.getName(), method.getName()); while (true) { - AtomicReference value = monitorData.get(key); + AtomicReference value = monitorData.get(key); if (null == value) { - monitorData.putIfAbsent(key, new AtomicReference(new Data())); + monitorData.putIfAbsent(key, new AtomicReference(new MonitorData())); continue; } while (true) { - Data oData = value.get(); - Data nData = new Data(); + MonitorData oData = value.get(); + MonitorData nData = new MonitorData(); nData.setCost(oData.getCost() + cost); if (isThrowing) { nData.setFailed(oData.getFailed() + 1); @@ -148,11 +144,11 @@ class MonitorAdviceListener extends AdviceListenerAdapter { } private class MonitorTimer extends TimerTask { - private Map> monitorData; + private Map> monitorData; private CommandProcess process; private int limit; - MonitorTimer(Map> monitorData, CommandProcess process, int limit) { + MonitorTimer(Map> monitorData, CommandProcess process, int limit) { this.monitorData = monitorData; this.process = process; this.limit = limit; @@ -170,53 +166,26 @@ class MonitorAdviceListener extends AdviceListenerAdapter { return; } - TableElement table = new TableElement(2, 3, 3, 1, 1, 1, 1, 1).leftCellPadding(1).rightCellPadding(1); - table.row(true, label("timestamp").style(Decoration.bold.bold()), - label("class").style(Decoration.bold.bold()), - label("method").style(Decoration.bold.bold()), - label("total").style(Decoration.bold.bold()), - label("success").style(Decoration.bold.bold()), - label("fail").style(Decoration.bold.bold()), - label("avg-rt(ms)").style(Decoration.bold.bold()), - label("fail-rate").style(Decoration.bold.bold())); + List monitorDataList = new ArrayList(monitorData.size()); + for (Map.Entry> entry : monitorData.entrySet()) { + final AtomicReference value = entry.getValue(); - for (Map.Entry> entry : monitorData.entrySet()) { - final AtomicReference value = entry.getValue(); - - Data data; + MonitorData data; while (true) { data = value.get(); - if (value.compareAndSet(data, new Data())) { + //swap monitor data to new instance + if (value.compareAndSet(data, new MonitorData())) { break; } } if (null != data) { - - final DecimalFormat df = new DecimalFormat("0.00"); - - table.row( - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), - entry.getKey().getClassName(), - entry.getKey().getMethodName(), - "" + data.getTotal(), - "" + data.getSuccess(), - "" + data.getFailed(), - df.format(div(data.getCost(), data.getTotal())), - df.format(100.0d * div(data.getFailed(), data.getTotal())) + "%" - ); - + data.setClassName(entry.getKey().getClassName()); + data.setMethodName(entry.getKey().getMethodName()); + monitorDataList.add(data); } } - - process.write(RenderUtil.render(table, process.width()) + "\n"); - } - - private double div(double a, double b) { - if (b == 0) { - return 0; - } - return a / b; + process.appendResult(new MonitorModel(monitorDataList)); } } @@ -260,47 +229,4 @@ class MonitorAdviceListener extends AdviceListenerAdapter { } - /** - * 数据监控用的value - * - * @author vlinux - */ - private static class Data { - private int total; - private int success; - private int failed; - private double cost; - - public int getTotal() { - return total; - } - - public void setTotal(int total) { - this.total = total; - } - - public int getSuccess() { - return success; - } - - public void setSuccess(int success) { - this.success = success; - } - - public int getFailed() { - return failed; - } - - public void setFailed(int failed) { - this.failed = failed; - } - - public double getCost() { - return cost; - } - - public void setCost(double cost) { - this.cost = cost; - } - } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorData.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorData.java new file mode 100644 index 000000000..9646dcfd7 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorData.java @@ -0,0 +1,63 @@ +package com.taobao.arthas.core.command.monitor200; + +/** + * 数据监控用的value for MonitorCommand + * + * @author vlinux + */ +public class MonitorData { + private String className; + private String methodName; + private int total; + private int success; + private int failed; + private double cost; + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + + public int getSuccess() { + return success; + } + + public void setSuccess(int success) { + this.success = success; + } + + public int getFailed() { + return failed; + } + + public void setFailed(int failed) { + this.failed = failed; + } + + public double getCost() { + return cost; + } + + public void setCost(double cost) { + this.cost = cost; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java index 96c6a3bb0..96dcfa4f8 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ProfilerCommand.java @@ -16,6 +16,7 @@ import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.common.OSUtils; import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.model.ProfilerModel; import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.cli.Completion; @@ -221,7 +222,7 @@ public class ProfilerCommand extends AnnotatedCommand { * https://github.com/jvm-profiling-tools/async-profiler/blob/v1.6/src/arguments.cpp#L34 * */ - enum ProfilerAction { + public enum ProfilerAction { execute, start, stop, resume, list, version, status, load, dumpCollapsed, dumpFlat, dumpTraces, getSamples, @@ -270,12 +271,11 @@ public class ProfilerCommand extends AnnotatedCommand { @Override public void process(final CommandProcess process) { - int status = 0; try { ProfilerAction profilerAction = ProfilerAction.valueOf(action); if (ProfilerAction.actions.equals(profilerAction)) { - process.write("Supported Actions: " + actions() + "\n"); + process.appendResult(new ProfilerModel(actions())); return; } @@ -283,54 +283,54 @@ public class ProfilerCommand extends AnnotatedCommand { if (ProfilerAction.execute.equals(profilerAction)) { if (actionArg == null) { - process.write("actionArg can not be empty.\n"); - status = 1; + process.end(1, "actionArg can not be empty."); return; } String result = execute(asyncProfiler, this.actionArg); - process.write(result); + appendExecuteResult(process, result); } else if (ProfilerAction.start.equals(profilerAction)) { String executeArgs = executeArgs(ProfilerAction.start); String result = execute(asyncProfiler, executeArgs); - process.write(result); + ProfilerModel profilerModel = createProfilerModel(result); if (this.duration != null) { final String outputFile = outputFile(); - final String stopExecuteArgs = executeArgs(ProfilerAction.stop); - process.write(String.format("profiler will silent stop after %d seconds.\n", this.duration.longValue())); - process.write("profiler output file will be: " + new File(outputFile).getAbsolutePath() + "\n"); + profilerModel.setOutputFile(outputFile); + profilerModel.setDuration(duration); + + // 延时执行stop ArthasBootstrap.getInstance().getScheduledExecutorService().schedule(new Runnable() { @Override public void run() { + //在异步线程执行,profiler命令已经结束,不能输出到客户端 try { - logger.info("profiler output file: " + new File(outputFile).getAbsolutePath() + "\n"); - String result = execute(asyncProfiler, stopExecuteArgs); - logger.info("profiler stop result: " + result); + logger.info("stopping profiler ..."); + ProfilerModel model = processStop(asyncProfiler); + logger.info("profiler output file: " + model.getOutputFile()); + logger.info("stop profiler successfully."); } catch (Throwable e) { - logger.error("", e); + logger.error("stop profiler failure", e); } } }, this.duration, TimeUnit.SECONDS); } + process.appendResult(profilerModel); } else if (ProfilerAction.stop.equals(profilerAction)) { - String outputFile = outputFile(); - process.write("profiler output file: " + new File(outputFile).getAbsolutePath() + "\n"); - String executeArgs = executeArgs(ProfilerAction.stop); - String result = execute(asyncProfiler, executeArgs); - process.write(result); + ProfilerModel profilerModel = processStop(asyncProfiler); + process.appendResult(profilerModel); } else if (ProfilerAction.resume.equals(profilerAction)) { String executeArgs = executeArgs(ProfilerAction.resume); String result = execute(asyncProfiler, executeArgs); - process.write(result); + appendExecuteResult(process, result); } else if (ProfilerAction.list.equals(profilerAction)) { String result = asyncProfiler.execute("list"); - process.write(result); + appendExecuteResult(process, result); } else if (ProfilerAction.version.equals(profilerAction)) { String result = asyncProfiler.execute("version"); - process.write(result); + appendExecuteResult(process, result); } else if (ProfilerAction.status.equals(profilerAction)) { String result = asyncProfiler.execute("status"); - process.write(result); + appendExecuteResult(process, result); } else if (ProfilerAction.dumpCollapsed.equals(profilerAction)) { if (actionArg == null) { actionArg = "TOTAL"; @@ -338,10 +338,10 @@ public class ProfilerCommand extends AnnotatedCommand { actionArg = actionArg.toUpperCase(); if ("TOTAL".equals(actionArg) || "SAMPLES".equals(actionArg)) { String result = asyncProfiler.dumpCollapsed(Counter.valueOf(actionArg)); - process.write(result); + appendExecuteResult(process, result); } else { - process.write("ERROR: dumpCollapsed argumment should be TOTAL or SAMPLES. \n"); - status = 1; + process.end(1, "ERROR: dumpCollapsed argumment should be TOTAL or SAMPLES. "); + return; } } else if (ProfilerAction.dumpFlat.equals(profilerAction)) { int maxMethods = 0; @@ -349,27 +349,35 @@ public class ProfilerCommand extends AnnotatedCommand { maxMethods = Integer.valueOf(actionArg); } String result = asyncProfiler.dumpFlat(maxMethods); - process.write(result); + appendExecuteResult(process, result); } else if (ProfilerAction.dumpTraces.equals(profilerAction)) { int maxTraces = 0; if (actionArg != null) { maxTraces = Integer.valueOf(actionArg); } String result = asyncProfiler.dumpTraces(maxTraces); - process.write(result); + appendExecuteResult(process, result); } else if (ProfilerAction.getSamples.equals(profilerAction)) { String result = "" + asyncProfiler.getSamples() + "\n"; - process.write(result); + appendExecuteResult(process, result); } + process.end(); } catch (Throwable e) { - process.write(e.getMessage()).write("\n"); logger.error("AsyncProfiler error", e); - status = 1; - } finally { - process.end(status); + process.end(1, "AsyncProfiler error: "+e.getMessage()); } } + private ProfilerModel processStop(AsyncProfiler asyncProfiler) throws IOException { + String outputFile = outputFile(); + String executeArgs = executeArgs(ProfilerAction.stop); + String result = execute(asyncProfiler, executeArgs); + + ProfilerModel profilerModel = createProfilerModel(result); + profilerModel.setOutputFile(outputFile); + return profilerModel; + } + private String outputFile() { if (this.file == null) { this.file = new File("arthas-output", @@ -378,6 +386,19 @@ public class ProfilerCommand extends AnnotatedCommand { return file; } + private void appendExecuteResult(CommandProcess process, String result) { + ProfilerModel profilerModel = createProfilerModel(result); + process.appendResult(profilerModel); + } + + private ProfilerModel createProfilerModel(String result) { + ProfilerModel profilerModel = new ProfilerModel(); + profilerModel.setAction(action); + profilerModel.setActionArg(actionArg); + profilerModel.setExecuteResult(result); + return profilerModel; + } + private List events() { List result = new ArrayList(); diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/StackAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/StackAdviceListener.java index 64426883c..6a6636f5d 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/StackAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/StackAdviceListener.java @@ -1,23 +1,25 @@ package com.taobao.arthas.core.command.monitor200; import com.taobao.arthas.core.advisor.AdviceListenerAdapter; +import com.taobao.arthas.core.command.model.StackModel; import com.taobao.arthas.core.shell.command.CommandProcess; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.advisor.Advice; import com.taobao.arthas.core.advisor.ArthasMethod; -import com.taobao.arthas.core.util.DateUtils; import com.taobao.arthas.core.util.LogUtil; import com.taobao.arthas.core.util.ThreadLocalWatch; import com.taobao.arthas.core.util.ThreadUtil; +import java.util.Date; + /** * @author beiwei30 on 29/11/2016. */ public class StackAdviceListener extends AdviceListenerAdapter { private static final Logger logger = LoggerFactory.getLogger(StackAdviceListener.class); - private final ThreadLocal stackThreadLocal = new ThreadLocal(); + private final ThreadLocal stackThreadLocal = new ThreadLocal(); private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch(); private StackCommand command; private CommandProcess process; @@ -31,7 +33,7 @@ public class StackAdviceListener extends AdviceListenerAdapter { @Override public void before(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args) throws Throwable { - stackThreadLocal.set(ThreadUtil.getThreadStack(Thread.currentThread())); + stackThreadLocal.set(ThreadUtil.getThreadStackModel(Thread.currentThread())); // 开始计算本次方法调用耗时 threadLocalWatch.start(); } @@ -60,7 +62,10 @@ public class StackAdviceListener extends AdviceListenerAdapter { } if (conditionResult) { // TODO: concurrency issues for process.write - process.write("ts=" + DateUtils.getCurrentDate() + ";" + stackThreadLocal.get() + "\n"); + // TODO: should clear stackThreadLocal? + StackModel stackModel = stackThreadLocal.get(); + stackModel.setTs(new Date()); + process.appendResult(stackModel); process.times().incrementAndGet(); if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) { abortProcess(process, command.getNumberOfLimit()); @@ -68,9 +73,8 @@ public class StackAdviceListener extends AdviceListenerAdapter { } } catch (Throwable e) { logger.warn("stack failed.", e); - process.write("stack failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage() - + ", visit " + LogUtil.loggingFile() + " for more details.\n"); - process.end(); + process.end(-1, "stack failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage() + + ", visit " + LogUtil.loggingFile() + " for more details."); } } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java index 24b9e24fb..b5dead6a1 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java @@ -1,22 +1,19 @@ package com.taobao.arthas.core.command.monitor200; -import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.createTable; -import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.fillTableHeader; -import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.fillTableRow; - -import java.util.Date; - import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.advisor.Advice; -import com.taobao.arthas.core.advisor.ArthasMethod; import com.taobao.arthas.core.advisor.AdviceListenerAdapter; +import com.taobao.arthas.core.advisor.ArthasMethod; import com.taobao.arthas.core.command.express.ExpressException; +import com.taobao.arthas.core.command.model.TimeFragmentVO; +import com.taobao.arthas.core.command.model.TimeTunnelModel; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.LogUtil; import com.taobao.arthas.core.util.ThreadLocalWatch; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; + +import java.util.Arrays; +import java.util.Date; /** * @author beiwei30 on 30/11/2016. @@ -81,9 +78,8 @@ public class TimeTunnelAdviceListener extends AdviceListenerAdapter { } } catch (ExpressException e) { logger.warn("tt failed.", e); - process.write("tt failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage() - + ", visit " + LogUtil.loggingFile() + " for more details.\n"); - process.end(); + process.end(-1, "tt failed, condition is: " + command.getConditionExpress() + ", " + e.getMessage() + + ", visit " + LogUtil.loggingFile() + " for more details."); } if (!match) { @@ -91,20 +87,17 @@ public class TimeTunnelAdviceListener extends AdviceListenerAdapter { } int index = command.putTimeTunnel(timeTunnel); - TableElement table = createTable(); + + TimeFragmentVO timeFragmentVO = TimeTunnelCommand.createTimeFragmentVO(index, timeTunnel); + TimeTunnelModel timeTunnelModel = new TimeTunnelModel() + .setTimeFragmentList(Arrays.asList(timeFragmentVO)) + .setFirst(isFirst); + process.appendResult(timeTunnelModel); if (isFirst) { isFirst = false; - - // 填充表格头部 - fillTableHeader(table); } - // 填充表格内容 - fillTableRow(table, index, timeTunnel); - - // TODO: concurrency issues for process.write - process.write(RenderUtil.render(table, process.width())); process.times().incrementAndGet(); if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) { abortProcess(process, command.getNumberOfLimit()); diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelCommand.java index cb9bf2ded..a48894a64 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelCommand.java @@ -9,6 +9,10 @@ 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.MessageModel; +import com.taobao.arthas.core.command.model.RowAffectModel; +import com.taobao.arthas.core.command.model.TimeFragmentVO; +import com.taobao.arthas.core.command.model.TimeTunnelModel; 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; @@ -17,16 +21,16 @@ 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.arthas.core.view.ObjectView; import com.taobao.middleware.cli.annotations.Description; import com.taobao.middleware.cli.annotations.Name; import com.taobao.middleware.cli.annotations.Option; import com.taobao.middleware.cli.annotations.Summary; import com.taobao.middleware.cli.annotations.Argument; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; +import java.util.ArrayList; +import java.util.Date; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -49,6 +53,7 @@ import static java.lang.String.format; " 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 { @@ -226,10 +231,6 @@ public class TimeTunnelCommand extends EnhancerCommand { return !StringUtils.isEmpty(searchExpress); } - private boolean isNeedExpand() { - return null != expand && expand > 0; - } - /** * 检查参数是否合法 */ @@ -323,27 +324,22 @@ public class TimeTunnelCommand extends EnhancerCommand { try { TimeFragment tf = timeFragmentMap.get(index); if (null == tf) { - process.write(format("Time fragment[%d] does not exist.", index)).write("\n"); + process.end(1, format("Time fragment[%d] does not exist.", index)); return; } - Advice advice = tf.getAdvice(); - String className = advice.getClazz().getName(); - String methodName = advice.getMethod().getName(); - String objectAddress = advice.getTarget() == null ? "NULL" : "0x" + toHexString(advice.getTarget().hashCode()); - - TableElement table = TimeTunnelTable.createDefaultTable(); - TimeTunnelTable.drawTimeTunnel(tf, index, table); - TimeTunnelTable.drawMethod(advice, className, methodName, objectAddress, table); - TimeTunnelTable.drawParameters(advice, table, isNeedExpand(), expand); - TimeTunnelTable.drawReturnObj(advice, table, isNeedExpand(), expand, sizeLimit); - TimeTunnelTable.drawThrowException(advice, table, isNeedExpand(), expand); - - process.write(RenderUtil.render(table, process.width())); + TimeFragmentVO timeFragmentVO = createTimeFragmentVO(index, tf); + TimeTunnelModel timeTunnelModel = new TimeTunnelModel() + .setTimeFragment(timeFragmentVO) + .setExpand(expand) + .setSizeLimit(sizeLimit); + process.appendResult(timeTunnelModel); affect.rCnt(1); - } finally { - process.write(affect.toString()).write("\n"); + 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"); } } @@ -353,25 +349,24 @@ public class TimeTunnelCommand extends EnhancerCommand { try { final TimeFragment tf = timeFragmentMap.get(index); if (null == tf) { - process.write(format("Time fragment[%d] does not exist.", index)).write("\n"); + process.end(1, format("Time fragment[%d] does not exist.", index)); return; } Advice advice = tf.getAdvice(); Object value = ExpressFactory.threadLocalExpress(advice).get(watchExpress); - if (isNeedExpand()) { - process.write(new ObjectView(value, expand, sizeLimit).draw()).write("\n"); - } else { - process.write(StringUtils.objectToString(value)).write("\n"); - } + TimeTunnelModel timeTunnelModel = new TimeTunnelModel() + .setWatchValue(value) + .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.write(e.getMessage() + ", visit " + LogUtil.loggingFile() + " for more detail\n"); - } finally { - process.write(affect.toString()).write("\n"); - process.end(); + process.end(1, e.getMessage() + ", visit " + LogUtil.loggingFile() + " for more detail"); } } @@ -394,22 +389,29 @@ public class TimeTunnelCommand extends EnhancerCommand { if (hasWatchExpress()) { // 执行watchExpress - TableElement table = TimeTunnelTable.createDefaultTable(); - TimeTunnelTable.drawWatchTableHeader(table); - TimeTunnelTable.drawWatchExpress(matchingTimeSegmentMap, table, watchExpress, isNeedExpand(), expand, sizeLimit); - process.write(RenderUtil.render(table, process.width())); + Map searchResults = new LinkedHashMap(); + for (Map.Entry entry : matchingTimeSegmentMap.entrySet()) { + Object value = ExpressFactory.threadLocalExpress(entry.getValue().getAdvice()).get(watchExpress); + searchResults.put(entry.getKey(), value); + } + + TimeTunnelModel timeTunnelModel = new TimeTunnelModel() + .setWatchResults(searchResults) + .setExpand(expand) + .setSizeLimit(sizeLimit); + process.appendResult(timeTunnelModel); } else { // 单纯的列表格 - process.write(RenderUtil.render(TimeTunnelTable.drawTimeTunnelTable(matchingTimeSegmentMap), process.width())); + List 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.write(e.getMessage() + ", visit " + LogUtil.loggingFile() + " for more detail\n"); - } finally { - process.write(affect.toString()).write("\n"); - process.end(); + process.end(1, e.getMessage() + ", visit " + LogUtil.loggingFile() + " for more detail"); } } @@ -419,8 +421,8 @@ public class TimeTunnelCommand extends EnhancerCommand { if (timeFragmentMap.remove(index) != null) { affect.rCnt(1); } - process.write(format("Time fragment[%d] successfully deleted.", index)).write("\n"); - process.write(affect.toString()).write("\n"); + process.appendResult(new MessageModel(format("Time fragment[%d] successfully deleted.", index))); + process.appendResult(new RowAffectModel(affect)); process.end(); } @@ -428,33 +430,57 @@ public class TimeTunnelCommand extends EnhancerCommand { int count = timeFragmentMap.size(); RowAffect affect = new RowAffect(count); timeFragmentMap.clear(); - process.write("Time fragments are cleaned.\n"); - process.write(affect.toString()).write("\n"); + process.appendResult(new MessageModel("Time fragments are cleaned.")); + process.appendResult(new RowAffectModel(affect)); process.end(); } private void processList(CommandProcess process) { RowAffect affect = new RowAffect(); - process.write(RenderUtil.render(TimeTunnelTable.drawTimeTunnelTable(timeFragmentMap), process.width())); + List timeFragmentList = createTimeTunnelVOList(timeFragmentMap); + process.appendResult(new TimeTunnelModel().setTimeFragmentList(timeFragmentList).setFirst(true)); affect.rCnt(timeFragmentMap.size()); - process.write(affect.toString()).write("\n"); + process.appendResult(new RowAffectModel(affect)); process.end(); } + private List createTimeTunnelVOList(Map timeFragmentMap) { + List timeFragmentList = new ArrayList(timeFragmentMap.size()); + for (Map.Entry entry : timeFragmentMap.entrySet()) { + timeFragmentList.add(createTimeFragmentVO(entry.getKey(), entry.getValue())); + } + return timeFragmentList; + } + + public static TimeFragmentVO createTimeFragmentVO(Integer index, TimeFragment tf) { + 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()) + .setReturn(advice.isAfterReturning()) + .setReturnObj(advice.getReturnObj()) + .setThrow(advice.isAfterThrowing()) + .setThrowExp(advice.getThrowExp()) + .setObject(object) + .setClassName(advice.getClazz().getName()) + .setMethodName(advice.getMethod().getName()); + } + /** * 重放指定记录 */ private void processPlay(CommandProcess process) { TimeFragment tf = timeFragmentMap.get(index); if (null == tf) { - process.write(format("Time fragment[%d] does not exist.", index) + "\n"); - process.end(); + process.end(1, format("Time fragment[%d] does not exist.", index)); return; } Advice advice = tf.getAdvice(); - String className = advice.getClazz().getName(); - String methodName = advice.getMethod().getName(); - String objectAddress = advice.getTarget() == null ? "NULL" : "0x" + toHexString(advice.getTarget().hashCode()); ArthasMethod method = advice.getMethod(); boolean accessible = advice.getMethod().isAccessible(); try { @@ -470,30 +496,44 @@ public class TimeTunnelCommand extends EnhancerCommand { } } long beginTime = System.nanoTime(); - TableElement table = TimeTunnelTable.createDefaultTable(); - if (i != 0) { - // empty line separator - process.write("\n"); - } - TimeTunnelTable.drawPlayHeader(className, methodName, objectAddress, index, table); - TimeTunnelTable.drawParameters(advice, table, isNeedExpand(), expand); + + //copy from tt record + TimeFragmentVO replayResult = createTimeFragmentVO(index, tf); + replayResult.setTimestamp(new Date()) + .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; - TimeTunnelTable.drawPlayResult(table, returnObj, isNeedExpand(), expand, sizeLimit, cost); + replayResult.setCost(cost) + .setReturn(true) + .setReturnObj(returnObj); } catch (Throwable t) { - TimeTunnelTable.drawPlayException(table, t, isNeedExpand(), expand); + //throw exp + double cost = (System.nanoTime() - beginTime) / 1000000.0; + replayResult.setCost(cost) + .setThrow(true) + .setThrowExp(t); } - process.write(RenderUtil.render(table, process.width())) - .write(format("Time fragment[%d] successfully replayed %d times.", index, i+1)) - .write("\n"); + + 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); - process.end(); } } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelTable.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelTable.java index e19d192c2..3b38a6af7 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelTable.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelTable.java @@ -1,8 +1,6 @@ package com.taobao.arthas.core.command.monitor200; -import com.taobao.arthas.core.advisor.Advice; -import com.taobao.arthas.core.command.express.ExpressException; -import com.taobao.arthas.core.command.express.ExpressFactory; +import com.taobao.arthas.core.command.model.TimeFragmentVO; import com.taobao.arthas.core.util.StringUtils; import com.taobao.arthas.core.view.ObjectView; import com.taobao.text.Decoration; @@ -15,10 +13,10 @@ import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import java.util.Map; import static com.taobao.text.ui.Element.label; -import static java.lang.Integer.toHexString; /** * @author beiwei30 on 30/11/2016. @@ -53,7 +51,7 @@ public class TimeTunnelTable { return new TableElement(TABLE_COL_WIDTH).leftCellPadding(1).rightCellPadding(1); } - static TableElement createDefaultTable() { + public static TableElement createDefaultTable() { return new TableElement().leftCellPadding(1).rightCellPadding(1); } @@ -67,54 +65,49 @@ public class TimeTunnelTable { } // 绘制TimeTunnel表格 - static Element drawTimeTunnelTable(Map timeTunnelMap) { - TableElement table = fillTableHeader(createTable()); - for (Map.Entry entry : timeTunnelMap.entrySet()) { - final int index = entry.getKey(); - final TimeFragment tf = entry.getValue(); - fillTableRow(table, index, tf); + public static Element drawTimeTunnelTable(List timeFragmentList, boolean withHeader){ + TableElement table = createTable(); + if (withHeader) { + fillTableHeader(table); + } + for (TimeFragmentVO tf : timeFragmentList) { + fillTableRow(table, tf); } return table; } // 填充表格行 - static TableElement fillTableRow(TableElement table, int index, TimeFragment tf) { - Advice advice = tf.getAdvice(); + static TableElement fillTableRow(TableElement table, TimeFragmentVO tf) { return table.row( - "" + index, - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tf.getGmtCreate()), + "" + tf.getIndex(), + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tf.getTimestamp()), "" + tf.getCost(), - "" + advice.isAfterReturning(), - "" + advice.isAfterThrowing(), - advice.getTarget() == null - ? "NULL" - : "0x" + toHexString(advice.getTarget().hashCode()), - StringUtils.substringAfterLast("." + advice.getClazz().getName(), "."), - advice.getMethod().getName() + "" + tf.isReturn(), + "" + tf.isThrow(), + tf.getObject(), + StringUtils.substringAfterLast("." + tf.getClassName(), "."), + tf.getMethodName() ); } - static void drawTimeTunnel(TimeFragment tf, Integer index, TableElement table) { + public static void drawTimeTunnel(TableElement table, TimeFragmentVO tf) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - table.row("INDEX", "" + index) - .row("GMT-CREATE", sdf.format(tf.getGmtCreate())) - .row("COST(ms)", "" + tf.getCost()); + table.row("INDEX", "" + tf.getIndex()) + .row("GMT-CREATE", sdf.format(tf.getTimestamp())) + .row("COST(ms)", "" + tf.getCost()) + .row("OBJECT", tf.getObject()) + .row("CLASS", tf.getClassName()) + .row("METHOD", tf.getMethodName()) + .row("IS-RETURN", "" + tf.isReturn()) + .row("IS-EXCEPTION", "" + tf.isThrow()); } - static void drawMethod(Advice advice, String className, String methodName, String objectAddress, TableElement table) { - table.row("OBJECT", objectAddress) - .row("CLASS", className) - .row("METHOD", methodName) - .row("IS-RETURN", "" + advice.isAfterReturning()) - .row("IS-EXCEPTION", "" + advice.isAfterThrowing()); - } - - static void drawThrowException(Advice advice, TableElement table, boolean isNeedExpand, int expandLevel) { - if (advice.isAfterThrowing()) { + public static void drawThrowException(TableElement table, TimeFragmentVO tf, boolean isNeedExpand, Integer expandLevel) { + if (tf.isThrow()) { //noinspection ThrowableResultOfMethodCallIgnored - Throwable throwable = advice.getThrowExp(); + Throwable throwable = tf.getThrowExp(); if (isNeedExpand) { - table.row("THROW-EXCEPTION", new ObjectView(advice.getThrowExp(), expandLevel).draw()); + table.row("THROW-EXCEPTION", new ObjectView(throwable, expandLevel).draw()); } else { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); @@ -128,22 +121,20 @@ public class TimeTunnelTable { } } - static void drawReturnObj(Advice advice, TableElement table, boolean isNeedExpand, int expandLevel, int sizeLimit) { - // fill the returnObj - if (advice.isAfterReturning()) { + public static void drawReturnObj(TableElement table, TimeFragmentVO tf, boolean isNeedExpand, Integer expandLevel, Integer sizeLimit) { + if (tf.isReturn()) { if (isNeedExpand) { - table.row("RETURN-OBJ", new ObjectView(advice.getReturnObj(), expandLevel, sizeLimit).draw()); + table.row("RETURN-OBJ", new ObjectView(tf.getReturnObj(), expandLevel, sizeLimit).draw()); } else { - table.row("RETURN-OBJ", "" + StringUtils.objectToString(advice.getReturnObj())); + table.row("RETURN-OBJ", "" + StringUtils.objectToString(tf.getReturnObj())); } } } - static void drawParameters(Advice advice, TableElement table, boolean isNeedExpand, int expandLevel) { - // fill the parameters - if (null != advice.getParams()) { + public static void drawParameters(TableElement table, Object[] params, boolean isNeedExpand, Integer expandLevel) { + if (params != null) { int paramIndex = 0; - for (Object param : advice.getParams()) { + for (Object param : params) { if (isNeedExpand) { table.row("PARAMETERS[" + paramIndex++ + "]", new ObjectView(param, expandLevel).draw()); } else { @@ -153,22 +144,21 @@ public class TimeTunnelTable { } } - static void drawWatchTableHeader(TableElement table) { + public static void drawWatchTableHeader(TableElement table) { table.row(true, label("INDEX").style(Decoration.bold.bold()), label("SEARCH-RESULT") .style(Decoration.bold.bold())); } - static void drawWatchExpress(Map matchingTimeSegmentMap, TableElement table, - String watchExpress, boolean isNeedExpand, int expandLevel, int sizeLimit) - throws ExpressException { - for (Map.Entry entry : matchingTimeSegmentMap.entrySet()) { - Object value = ExpressFactory.threadLocalExpress(entry.getValue().getAdvice()).get(watchExpress); + public static void drawWatchResults(TableElement table, Map watchResults, boolean isNeedExpand, + Integer expandLevel, Integer sizeLimit) { + for (Map.Entry entry : watchResults.entrySet()) { + Object value = entry.getValue(); table.row("" + entry.getKey(), "" + (isNeedExpand ? new ObjectView(value, expandLevel, sizeLimit).draw() : StringUtils.objectToString(value))); } } - static TableElement drawPlayHeader(String className, String methodName, String objectAddress, int index, + public static TableElement drawPlayHeader(String className, String methodName, String objectAddress, int index, TableElement table) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return table.row("RE-INDEX", "" + index) @@ -178,7 +168,7 @@ public class TimeTunnelTable { .row("METHOD", methodName); } - static void drawPlayResult(TableElement table, Object returnObj, boolean isNeedExpand, int expandLevel, + public static void drawPlayResult(TableElement table, Object returnObj, boolean isNeedExpand, int expandLevel, int sizeLimit, double cost) { // 执行成功:输出成功状态 table.row("IS-RETURN", "" + true); @@ -193,7 +183,7 @@ public class TimeTunnelTable { } } - static void drawPlayException(TableElement table, Throwable t, boolean isNeedExpand, int expandLevel) { + public static void drawPlayException(TableElement table, Throwable t, boolean isNeedExpand, int expandLevel) { // 执行失败:输出失败状态 table.row("IS-RETURN", "" + false); table.row("IS-EXCEPTION", "" + true); @@ -219,4 +209,5 @@ public class TimeTunnelTable { } } } + } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TraceAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TraceAdviceListener.java index df29e8a4d..0873bdd8a 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TraceAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TraceAdviceListener.java @@ -23,20 +23,20 @@ public class TraceAdviceListener extends AbstractTraceAdviceListener implements @Override public void invokeBeforeTracing(String tracingClassName, String tracingMethodName, String tracingMethodDesc, int tracingLineNumber) throws Throwable { - threadBoundEntity.get().view.begin( - StringUtils.normalizeClassName(tracingClassName) + ":" + tracingMethodName + "()" + " #" + tracingLineNumber); + // normalize className later + threadBoundEntity.get().tree.begin(tracingClassName, tracingMethodName, tracingLineNumber, true); } @Override public void invokeAfterTracing(String tracingClassName, String tracingMethodName, String tracingMethodDesc, int tracingLineNumber) throws Throwable { - threadBoundEntity.get().view.end(); + threadBoundEntity.get().tree.end(); } @Override public void invokeThrowTracing(String tracingClassName, String tracingMethodName, String tracingMethodDesc, int tracingLineNumber) throws Throwable { - threadBoundEntity.get().view.end("throws Exception"); + threadBoundEntity.get().tree.end(true); } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TraceEntity.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TraceEntity.java index 97aedd7f7..44dd172b2 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TraceEntity.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TraceEntity.java @@ -1,8 +1,8 @@ package com.taobao.arthas.core.command.monitor200; -import com.taobao.arthas.core.util.DateUtils; +import com.taobao.arthas.core.command.model.TraceModel; +import com.taobao.arthas.core.command.model.TraceTree; import com.taobao.arthas.core.util.ThreadUtil; -import com.taobao.arthas.core.view.TreeView; /** * 用于在ThreadLocal中传递的实体 @@ -10,22 +10,14 @@ import com.taobao.arthas.core.view.TreeView; */ public class TraceEntity { - protected TreeView view; + protected TraceTree tree; protected int deep; public TraceEntity() { - this.view = createTreeView(); + this.tree = createTraceTree(); this.deep = 0; } - public TreeView getView() { - return view; - } - - public void setView(TreeView view) { - this.view = view; - } - public int getDeep() { return deep; } @@ -34,8 +26,12 @@ public class TraceEntity { this.deep = deep; } - private TreeView createTreeView() { - String threadTitle = "ts=" + DateUtils.getCurrentDate()+ ";" + ThreadUtil.getThreadTitle(Thread.currentThread()); - return new TreeView(true, threadTitle); + private TraceTree createTraceTree() { + return new TraceTree(ThreadUtil.getThreadNode(Thread.currentThread())); + } + + public TraceModel getModel() { + tree.trim(); + return new TraceModel(tree.getRoot(), tree.getNodeCount()); } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java index fa2db6982..6318c7fd3 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java @@ -6,12 +6,12 @@ import com.taobao.arthas.core.GlobalOptions; import com.taobao.arthas.core.advisor.Advice; import com.taobao.arthas.core.advisor.ArthasMethod; import com.taobao.arthas.core.advisor.AdviceListenerAdapter; +import com.taobao.arthas.core.command.model.WatchModel; import com.taobao.arthas.core.shell.command.CommandProcess; -import com.taobao.arthas.core.util.DateUtils; import com.taobao.arthas.core.util.LogUtil; -import com.taobao.arthas.core.util.StringUtils; import com.taobao.arthas.core.util.ThreadLocalWatch; -import com.taobao.arthas.core.view.ObjectView; + +import java.util.Date; /** * @author beiwei30 on 29/11/2016. @@ -71,10 +71,7 @@ class WatchAdviceListener extends AdviceListenerAdapter { } } - private boolean isNeedExpand() { - Integer expand = command.getExpand(); - return null != expand && expand >= 0; - } + private void watching(Advice advice) { try { @@ -87,9 +84,15 @@ class WatchAdviceListener extends AdviceListenerAdapter { if (conditionResult) { // TODO: concurrency issues for process.write Object value = getExpressionResult(command.getExpress(), advice, cost); - String result = StringUtils.objectToString( - isNeedExpand() ? new ObjectView(value, command.getExpand(), command.getSizeLimit()).draw() : value); - process.write("ts=" + DateUtils.getCurrentDate() + "; [cost=" + cost + "ms] result=" + result + "\n"); + + WatchModel model = new WatchModel(); + model.setTs(new Date()); + model.setCost(cost); + model.setValue(value); + model.setExpand(command.getExpand()); + model.setSizeLimit(command.getSizeLimit()); + + process.appendResult(model); process.times().incrementAndGet(); if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) { abortProcess(process, command.getNumberOfLimit()); @@ -97,10 +100,9 @@ class WatchAdviceListener extends AdviceListenerAdapter { } } catch (Throwable e) { logger.warn("watch failed.", e); - process.write("watch failed, condition is: " + command.getConditionExpress() + ", express is: " + process.end(-1, "watch failed, condition is: " + command.getConditionExpress() + ", express is: " + command.getExpress() + ", " + e.getMessage() + ", visit " + LogUtil.loggingFile() - + " for more details.\n"); - process.end(); + + " for more details."); } } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/EnhancerView.java b/core/src/main/java/com/taobao/arthas/core/command/view/EnhancerView.java new file mode 100644 index 000000000..271a54e87 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/EnhancerView.java @@ -0,0 +1,18 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.EnhancerModel; +import com.taobao.arthas.core.shell.command.CommandProcess; + +/** + * Term view for EnhancerModel + * @author gongdewei 2020/7/21 + */ +public class EnhancerView extends ResultView { + @Override + public void draw(CommandProcess process, EnhancerModel result) { + // ignore enhance result status, judge by the following output + if (result.getEffect() != null) { + process.write(ViewRenderUtil.renderEnhancerAffect(result.getEffect())); + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/GetStaticView.java b/core/src/main/java/com/taobao/arthas/core/command/view/GetStaticView.java index 8be21d435..b7f81e913 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/view/GetStaticView.java +++ b/core/src/main/java/com/taobao/arthas/core/command/view/GetStaticView.java @@ -16,7 +16,7 @@ public class GetStaticView extends ResultView { @Override public void draw(CommandProcess process, GetStaticModel result) { - int expand = result.expand(); + int expand = result.getExpand(); if (result.getField() != null) { ObjectVO field = result.getField(); String valueStr = StringUtils.objectToString(expand >= 0 ? new ObjectView(field.getValue(), expand).draw() : field.getValue()); diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/MonitorView.java b/core/src/main/java/com/taobao/arthas/core/command/view/MonitorView.java new file mode 100644 index 000000000..de99a0a8d --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/MonitorView.java @@ -0,0 +1,58 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.MonitorModel; +import com.taobao.arthas.core.command.monitor200.MonitorData; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.text.Decoration; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import static com.taobao.text.ui.Element.label; + +/** + * Term view for MonitorModel + * @author gongdewei 2020/4/28 + */ +public class MonitorView extends ResultView { + @Override + public void draw(CommandProcess process, MonitorModel result) { + TableElement table = new TableElement(2, 3, 3, 1, 1, 1, 1, 1).leftCellPadding(1).rightCellPadding(1); + table.row(true, label("timestamp").style(Decoration.bold.bold()), + label("class").style(Decoration.bold.bold()), + label("method").style(Decoration.bold.bold()), + label("total").style(Decoration.bold.bold()), + label("success").style(Decoration.bold.bold()), + label("fail").style(Decoration.bold.bold()), + label("avg-rt(ms)").style(Decoration.bold.bold()), + label("fail-rate").style(Decoration.bold.bold())); + + final DecimalFormat df = new DecimalFormat("0.00"); + + for (MonitorData data : result.getMonitorDataList()) { + table.row( + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), + data.getClassName(), + data.getMethodName(), + "" + data.getTotal(), + "" + data.getSuccess(), + "" + data.getFailed(), + df.format(div(data.getCost(), data.getTotal())), + df.format(100.0d * div(data.getFailed(), data.getTotal())) + "%" + ); + } + + process.write(RenderUtil.render(table, process.width()) + "\n"); + + } + + private double div(double a, double b) { + if (b == 0) { + return 0; + } + return a / b; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ProfilerView.java b/core/src/main/java/com/taobao/arthas/core/command/view/ProfilerView.java new file mode 100644 index 000000000..e67d03170 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ProfilerView.java @@ -0,0 +1,42 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.ProfilerModel; +import com.taobao.arthas.core.command.monitor200.ProfilerCommand.ProfilerAction; +import com.taobao.arthas.core.shell.command.CommandProcess; + + +/** + * Term view for ProfilerModel + * + * @author gongdewei 2020/4/27 + */ +public class ProfilerView extends ResultView { + @Override + public void draw(CommandProcess process, ProfilerModel model) { + if (model.getSupportedActions() != null) { + process.write("Supported Actions: " + model.getSupportedActions()).write("\n"); + return; + } + + drawExecuteResult(process, model); + + if (ProfilerAction.start.name().equals(model.getAction())) { + if (model.getDuration() != null) { + process.write(String.format("profiler will silent stop after %d seconds.\n", model.getDuration().longValue())); + process.write("profiler output file will be: " + model.getOutputFile() + "\n"); + } + } else if (ProfilerAction.stop.name().equals(model.getAction())) { + process.write("profiler output file: " + model.getOutputFile() + "\n"); + } + + } + + private void drawExecuteResult(CommandProcess process, ProfilerModel model) { + if (model.getExecuteResult() != null) { + process.write(model.getExecuteResult()); + if (!model.getExecuteResult().endsWith("\n")) { + process.write("\n"); + } + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java b/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java index 4df0afbe4..123f464d4 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java @@ -68,6 +68,13 @@ public class ResultViewResolver { registerView(MBeanView.class); registerView(PerfCounterView.class); registerView(ThreadView.class); + registerView(ProfilerView.class); + registerView(EnhancerView.class); + registerView(MonitorView.class); + registerView(StackView.class); + registerView(TimeTunnelView.class); + registerView(TraceView.class); + registerView(WatchView.class); } catch (Throwable e) { logger.error("register result view failed", e); diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/StackView.java b/core/src/main/java/com/taobao/arthas/core/command/view/StackView.java new file mode 100644 index 000000000..2ec5a7d3d --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/StackView.java @@ -0,0 +1,41 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.StackModel; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.DateUtils; +import com.taobao.arthas.core.util.ThreadUtil; + +/** + * Term view for StackModel + * @author gongdewei 2020/4/13 + */ +public class StackView extends ResultView { + + @Override + public void draw(CommandProcess process, StackModel result) { + StringBuilder sb = new StringBuilder(); + sb.append(ThreadUtil.getThreadTitle(result)).append("\n"); + + StackTraceElement[] stackTraceElements = result.getStackTrace(); + StackTraceElement locationStackTraceElement = stackTraceElements[0]; + String locationString = String.format(" @%s.%s()", locationStackTraceElement.getClassName(), + locationStackTraceElement.getMethodName()); + sb.append(locationString).append("\n"); + + int skip = 1; + for (int index = skip; index < stackTraceElements.length; index++) { + StackTraceElement ste = stackTraceElements[index]; + sb.append(" at ") + .append(ste.getClassName()) + .append(".") + .append(ste.getMethodName()) + .append("(") + .append(ste.getFileName()) + .append(":") + .append(ste.getLineNumber()) + .append(")\n"); + } + process.write("ts=" + DateUtils.formatDate(result.getTs()) + ";" + sb.toString() + "\n"); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/TimeTunnelView.java b/core/src/main/java/com/taobao/arthas/core/command/view/TimeTunnelView.java new file mode 100644 index 000000000..51c9ad533 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/TimeTunnelView.java @@ -0,0 +1,81 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.TimeFragmentVO; +import com.taobao.arthas.core.command.model.TimeTunnelModel; +import com.taobao.arthas.core.command.monitor200.TimeTunnelTable; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.arthas.core.view.ObjectView; +import com.taobao.text.ui.Element; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + +import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.*; +import static java.lang.String.format; + +/** + * Term view for TimeTunnelCommand + * @author gongdewei 2020/4/27 + */ +public class TimeTunnelView extends ResultView { + + @Override + public void draw(CommandProcess process, TimeTunnelModel timeTunnelModel) { + Integer expand = timeTunnelModel.getExpand(); + boolean isNeedExpand = isNeedExpand(expand); + Integer sizeLimit = timeTunnelModel.getSizeLimit(); + + if (timeTunnelModel.getTimeFragmentList() != null) { + //show list table: tt -l / tt -t + Element table = drawTimeTunnelTable(timeTunnelModel.getTimeFragmentList(), timeTunnelModel.getFirst()); + process.write(RenderUtil.render(table, process.width())); + + } else if (timeTunnelModel.getTimeFragment() != null) { + //show detail of single TimeFragment: tt -i 1000 + TimeFragmentVO tf = timeTunnelModel.getTimeFragment(); + TableElement table = TimeTunnelTable.createDefaultTable(); + TimeTunnelTable.drawTimeTunnel(table, tf); + TimeTunnelTable.drawParameters(table, tf.getParams(), isNeedExpand, expand); + TimeTunnelTable.drawReturnObj(table, tf, isNeedExpand, expand, sizeLimit); + TimeTunnelTable.drawThrowException(table, tf, isNeedExpand, expand); + process.write(RenderUtil.render(table, process.width())); + + } else if (timeTunnelModel.getWatchValue() != null) { + //watch single TimeFragment: tt -i 1000 -w 'params' + Object value = timeTunnelModel.getWatchValue(); + if (isNeedExpand) { + process.write(new ObjectView(value, expand, sizeLimit).draw()).write("\n"); + } else { + process.write(StringUtils.objectToString(value)).write("\n"); + } + + } else if (timeTunnelModel.getWatchResults() != null) { + //search & watch: tt -s 'returnObj!=null' -w 'returnObj' + TableElement table = TimeTunnelTable.createDefaultTable(); + TimeTunnelTable.drawWatchTableHeader(table); + TimeTunnelTable.drawWatchResults(table, timeTunnelModel.getWatchResults(), isNeedExpand, expand, sizeLimit); + process.write(RenderUtil.render(table, process.width())); + + } else if (timeTunnelModel.getReplayResult() != null) { + //replay: tt -i 1000 -p + TimeFragmentVO replayResult = timeTunnelModel.getReplayResult(); + Integer replayNo = timeTunnelModel.getReplayNo(); + TableElement table = TimeTunnelTable.createDefaultTable(); + TimeTunnelTable.drawPlayHeader(replayResult.getClassName(), replayResult.getMethodName(), replayResult.getObject(), replayResult.getIndex(), table); + TimeTunnelTable.drawParameters(table, replayResult.getParams(), isNeedExpand, expand); + if (replayResult.isReturn()) { + TimeTunnelTable.drawPlayResult(table, replayResult.getReturnObj(), isNeedExpand, expand, sizeLimit, replayResult.getCost()); + } else { + TimeTunnelTable.drawPlayException(table, replayResult.getThrowExp(), isNeedExpand, expand); + } + process.write(RenderUtil.render(table, process.width())) + .write(format("Time fragment[%d] successfully replayed %d times.", replayResult.getIndex(), replayNo)) + .write("\n\n"); + } + } + + private boolean isNeedExpand(Integer expand) { + return null != expand && expand > 0; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/TraceView.java b/core/src/main/java/com/taobao/arthas/core/command/view/TraceView.java new file mode 100644 index 000000000..f7e58185d --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/TraceView.java @@ -0,0 +1,201 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.MethodNode; +import com.taobao.arthas.core.command.model.ThreadNode; +import com.taobao.arthas.core.command.model.ThrowNode; +import com.taobao.arthas.core.command.model.TraceModel; +import com.taobao.arthas.core.command.model.TraceNode; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.DateUtils; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.arthas.core.view.Ansi; + +import java.util.List; + +import static java.lang.String.format; + +/** + * Term view for TraceModel + * @author gongdewei 2020/4/29 + */ +public class TraceView extends ResultView { + private static final String STEP_FIRST_CHAR = "`---"; + private static final String STEP_NORMAL_CHAR = "+---"; + private static final String STEP_HAS_BOARD = "| "; + private static final String STEP_EMPTY_BOARD = " "; + private static final String TIME_UNIT = "ms"; + + // 是否输出耗时 + private boolean isPrintCost = true; + private MethodNode maxCostNode; + + @Override + public void draw(CommandProcess process, TraceModel result) { + process.write(drawTree(result.getRoot())).write("\n"); + } + + public String drawTree(TraceNode root) { + + //reset status + maxCostNode = null; + findMaxCostNode(root); + + final StringBuilder treeSB = new StringBuilder(2048); + + final Ansi highlighted = Ansi.ansi().fg(Ansi.Color.RED); + + recursive(0, true, "", root, new Callback() { + + @Override + public void callback(int deep, boolean isLast, String prefix, TraceNode node) { + treeSB.append(prefix).append(isLast ? STEP_FIRST_CHAR : STEP_NORMAL_CHAR); + renderNode(treeSB, node, highlighted); + if (!StringUtils.isBlank(node.getMark())) { + treeSB.append(" [").append(node.getMark()).append(node.marks() > 1 ? "," + node.marks() : "").append("]"); + } + treeSB.append("\n"); + } + + }); + + return treeSB.toString(); + } + + private void renderNode(StringBuilder sb, TraceNode node, Ansi highlighted) { + //render cost: [0.366865ms] + if (isPrintCost && node instanceof MethodNode) { + MethodNode methodNode = (MethodNode) node; + + String costStr = renderCost(methodNode); + if (node == maxCostNode) { + // the node with max cost will be highlighted + sb.append(highlighted.a(costStr).reset().toString()); + } else { + sb.append(costStr); + } + } + + //render method name + if (node instanceof MethodNode) { + MethodNode methodNode = (MethodNode) node; + //clazz.getName() + ":" + method.getName() + "()" + sb.append(methodNode.getClassName()).append(":").append(methodNode.getMethodName()).append("()"); + // #lineNumber + if (methodNode.getLineNumber()!= -1) { + sb.append(" #").append(methodNode.getLineNumber()); + } + } else if (node instanceof ThreadNode) { + //render thread info + ThreadNode threadNode = (ThreadNode) node; + //ts=2020-04-29 10:34:00;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@18b4aac2 + sb.append(format("ts=%s;thread_name=%s;id=%s;is_daemon=%s;priority=%d;TCCL=%s", + DateUtils.formatDate(threadNode.getTimestamp()), + threadNode.getThreadName(), + Long.toHexString(threadNode.getThreadId()), + threadNode.isDaemon(), + threadNode.getPriority(), + threadNode.getClassloader())); + + //trace_id + if (threadNode.getTraceId() != null) { + sb.append(";trace_id="+threadNode.getTraceId()); + } + if (threadNode.getRpcId() != null) { + sb.append(";rpc_id="+threadNode.getRpcId()); + } + } else if (node instanceof ThrowNode) { + ThrowNode throwNode = (ThrowNode) node; + sb.append("throw:").append(throwNode.getException()) + .append(" #").append(throwNode.getLineNumber()) + .append(" [").append(throwNode.getMessage()).append("]"); + + } else { + throw new UnsupportedOperationException("unknown trace node: " + node.getClass()); + } + } + + private String renderCost(MethodNode node) { + StringBuilder sb = new StringBuilder(); + if (node.getTimes() <= 1) { + sb.append("[").append(nanoToMillis(node.getCost())).append(TIME_UNIT).append("] "); + } else { + sb.append("[min=").append(nanoToMillis(node.getMinCost())).append(TIME_UNIT).append(",max=") + .append(nanoToMillis(node.getMaxCost())).append(TIME_UNIT).append(",total=") + .append(nanoToMillis(node.getTotalCost())).append(TIME_UNIT).append(",count=") + .append(node.getTimes()).append("] "); + } + return sb.toString(); + } + + /** + * 递归遍历 + */ + private void recursive(int deep, boolean isLast, String prefix, TraceNode node, Callback callback) { + callback.callback(deep, isLast, prefix, node); + if (!isLeaf(node)) { + List children = node.getChildren(); + if (children == null) { + return; + } + final int size = children.size(); + for (int index = 0; index < size; index++) { + final boolean isLastFlag = index == size - 1; + final String currentPrefix = isLast ? prefix + STEP_EMPTY_BOARD : prefix + STEP_HAS_BOARD; + recursive( + deep + 1, + isLastFlag, + currentPrefix, + children.get(index), + callback + ); + } + } + } + + /** + * 查找耗时最大的节点,便于后续高亮展示 + * @param node + */ + private void findMaxCostNode(TraceNode node) { + if (node instanceof MethodNode && !isRoot(node) && !isRoot(node.parent())) { + MethodNode aNode = (MethodNode) node; + if (maxCostNode == null || maxCostNode.getTotalCost() < aNode.getTotalCost()) { + maxCostNode = aNode; + } + } + if (!isLeaf(node)) { + List children = node.getChildren(); + if (children != null) { + for (TraceNode n: children) { + findMaxCostNode(n); + } + } + } + } + + private boolean isRoot(TraceNode node) { + return node.parent() == null; + } + + private boolean isLeaf(TraceNode node) { + List children = node.getChildren(); + return children == null || children.isEmpty(); + } + + + /** + * convert nano-seconds to milli-seconds + */ + double nanoToMillis(long nanoSeconds) { + return nanoSeconds / 1000000.0; + } + + /** + * 遍历回调接口 + */ + private interface Callback { + + void callback(int deep, boolean isLast, String prefix, TraceNode node); + + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/WatchView.java b/core/src/main/java/com/taobao/arthas/core/command/view/WatchView.java new file mode 100644 index 000000000..efacab63f --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/WatchView.java @@ -0,0 +1,29 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.WatchModel; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.DateUtils; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.arthas.core.view.ObjectView; + +/** + * Term view for WatchModel + * + * @author gongdewei 2020/3/27 + */ +public class WatchView extends ResultView { + + @Override + public void draw(CommandProcess process, WatchModel model) { + Object value = model.getValue(); + String result = StringUtils.objectToString( + isNeedExpand(model) ? new ObjectView(value, model.getExpand(), model.getSizeLimit()).draw() : value); + + process.write("ts=" + DateUtils.formatDate(model.getTs()) + "; [cost=" + model.getCost() + "ms] result=" + result + "\n"); + } + + private boolean isNeedExpand(WatchModel model) { + Integer expand = model.getExpand(); + return null != expand && expand >= 0; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/util/DateUtils.java b/core/src/main/java/com/taobao/arthas/core/util/DateUtils.java index 2749911c9..9c07b4f28 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/DateUtils.java +++ b/core/src/main/java/com/taobao/arthas/core/util/DateUtils.java @@ -19,4 +19,8 @@ public class DateUtils { public static String getCurrentDate() { return dataFormat.get().format(new Date()); } + + public static String formatDate(Date date) { + return dataFormat.get().format(date); + } } diff --git a/core/src/main/java/com/taobao/arthas/core/util/ThreadUtil.java b/core/src/main/java/com/taobao/arthas/core/util/ThreadUtil.java index 0831ada24..ac54a0904 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/ThreadUtil.java +++ b/core/src/main/java/com/taobao/arthas/core/util/ThreadUtil.java @@ -1,6 +1,8 @@ package com.taobao.arthas.core.util; import com.taobao.arthas.core.command.model.BlockingLockInfo; +import com.taobao.arthas.core.command.model.StackModel; +import com.taobao.arthas.core.command.model.ThreadNode; import com.taobao.arthas.core.view.Ansi; import java.arthas.SpyAPI; @@ -19,6 +21,9 @@ abstract public class ThreadUtil { private static final BlockingLockInfo EMPTY_INFO = new BlockingLockInfo(); private static ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + private static Field threadLocalsField; + private static Field threadLocalsTableFiled; + private static Field threadLocalEntryValueField; public static ThreadGroup getRoot() { ThreadGroup group = Thread.currentThread().getThreadGroup(); @@ -393,6 +398,31 @@ abstract public class ThreadUtil { return builder.toString(); } + /** + * 获取方法执行堆栈信息 + * + * @return 方法堆栈信息 + */ + public static StackModel getThreadStackModel(Thread currentThread) { + StackModel stackModel = new StackModel(); + stackModel.setThreadName(currentThread.getName()); + stackModel.setThreadId(Long.toHexString(currentThread.getId())); + stackModel.setDaemon(currentThread.isDaemon()); + stackModel.setPriority(currentThread.getPriority()); + stackModel.setClassloader(getTCCL(currentThread)); + + getEagleeyeTraceInfo(currentThread, stackModel); + + + //stack + StackTraceElement[] stackTraceElementArray = currentThread.getStackTrace(); + int magicStackDepth = findTheSpyAPIDepth(stackTraceElementArray); + StackTraceElement[] actualStackFrames = new StackTraceElement[stackTraceElementArray.length - magicStackDepth]; + System.arraycopy(stackTraceElementArray, magicStackDepth , actualStackFrames, 0, actualStackFrames.length); + stackModel.setStackTrace(actualStackFrames); + return stackModel; + } + public static String getThreadTitle(Thread currentThread) { StringBuilder sb = new StringBuilder("thread_name="); sb.append(currentThread.getName()) @@ -400,7 +430,47 @@ abstract public class ThreadUtil { .append(";is_daemon=").append(currentThread.isDaemon()) .append(";priority=").append(currentThread.getPriority()) .append(";TCCL=").append(getTCCL(currentThread)); - getEagleeyeTraceInfo(currentThread, sb); + + StackModel stackModel = new StackModel(); + getEagleeyeTraceInfo(currentThread, stackModel); + if (stackModel.getTraceId() != null) { + sb.append(";trace_id=").append(stackModel.getTraceId()); + } + if (stackModel.getRpcId() != null) { + sb.append(";rpc_id=").append(stackModel.getRpcId()); + } + return sb.toString(); + } + + public static ThreadNode getThreadNode(Thread currentThread) { + ThreadNode threadNode = new ThreadNode(); + threadNode.setThreadId(currentThread.getId()); + threadNode.setThreadName(currentThread.getName()); + threadNode.setDaemon(currentThread.isDaemon()); + threadNode.setPriority(currentThread.getPriority()); + threadNode.setClassloader(getTCCL(currentThread)); + + //trace_id + StackModel stackModel = new StackModel(); + getEagleeyeTraceInfo(currentThread, stackModel); + threadNode.setTraceId(stackModel.getTraceId()); + threadNode.setRpcId(stackModel.getRpcId()); + return threadNode; + } + + public static String getThreadTitle(StackModel stackModel) { + StringBuilder sb = new StringBuilder("thread_name="); + sb.append(stackModel.getThreadName()) + .append(";id=").append(stackModel.getThreadId()) + .append(";is_daemon=").append(stackModel.isDaemon()) + .append(";priority=").append(stackModel.getPriority()) + .append(";TCCL=").append(stackModel.getClassloader()); + if (stackModel.getTraceId() != null) { + sb.append(";trace_id=").append(stackModel.getTraceId()); + } + if (stackModel.getRpcId() != null) { + sb.append(";rpc_id=").append(stackModel.getRpcId()); + } return sb.toString(); } @@ -408,41 +478,52 @@ abstract public class ThreadUtil { if (null == currentThread.getContextClassLoader()) { return "null"; } else { - return currentThread.getContextClassLoader().getClass().getName() + - "@" + Integer.toHexString(currentThread.getContextClassLoader().hashCode()); + String classloaderClassName = currentThread.getContextClassLoader().getClass().getName(); + StringBuilder sb = new StringBuilder(classloaderClassName.length()+10); + sb.append(classloaderClassName) + .append("@") + .append(Integer.toHexString(currentThread.getContextClassLoader().hashCode())); + return sb.toString(); } } - private static void getEagleeyeTraceInfo(Thread currentThread, StringBuilder sb) { + private static void getEagleeyeTraceInfo(Thread currentThread, StackModel stackModel) { try { // access to Thread#threadlocals field - Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); + if (threadLocalsField == null) { + threadLocalsField = Thread.class.getDeclaredField("threadLocals"); + } threadLocalsField.setAccessible(true); Object threadLocalMap = threadLocalsField.get(currentThread); + // access to ThreadLocal$ThreadLocalMap#table filed - Field tableFiled = threadLocalMap.getClass().getDeclaredField("table"); - tableFiled.setAccessible(true); - Object[] tableEntries = (Object[])tableFiled.get(threadLocalMap); + if (threadLocalsTableFiled == null) { + threadLocalsTableFiled = threadLocalMap.getClass().getDeclaredField("table"); + } + threadLocalsTableFiled.setAccessible(true); + Object[] tableEntries = (Object[]) threadLocalsTableFiled.get(threadLocalMap); for (Object entry: tableEntries) { if (entry == null) { continue; } // access to ThreadLocal$ThreadLocalMap$Entry#value field - Field valueField = entry.getClass().getDeclaredField("value"); - valueField.setAccessible(true); - Object threadLocalValue = valueField.get(entry); + if (threadLocalEntryValueField == null) { + threadLocalEntryValueField = entry.getClass().getDeclaredField("value"); + } + threadLocalEntryValueField.setAccessible(true); + Object threadLocalValue = threadLocalEntryValueField.get(entry); if (threadLocalValue != null && "com.taobao.eagleeye.RpcContext_inner".equals(threadLocalValue.getClass().getName())) { // finally we got the chance to access trace id Method getTraceIdMethod = threadLocalValue.getClass().getMethod("getTraceId"); getTraceIdMethod.setAccessible(true); String traceId = (String)getTraceIdMethod.invoke(threadLocalValue); - sb.append(";trace_id=").append(traceId); + stackModel.setTraceId(traceId); // get rpc id Method getRpcIdMethod = threadLocalValue.getClass().getMethod("getRpcId"); getTraceIdMethod.setAccessible(true); String rpcId = (String)getRpcIdMethod.invoke(threadLocalValue); - sb.append(";rpc_id=").append(rpcId); + stackModel.setRpcId(rpcId); return; } }