Commit 5d7c4150 authored by shengnan hu's avatar shengnan hu
Browse files

init

parents
Pipeline #4715 failed with stage
in 30 seconds
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.BusyThreadInfo;
import com.taobao.arthas.core.command.model.ThreadModel;
import com.taobao.arthas.core.command.model.ThreadVO;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.ThreadUtil;
import com.taobao.text.ui.LabelElement;
import com.taobao.text.util.RenderUtil;
import java.util.List;
import java.util.Map;
/**
* View of 'thread' command
*
* @author gongdewei 2020/4/26
*/
public class ThreadView extends ResultView<ThreadModel> {
@Override
public void draw(CommandProcess process, ThreadModel result) {
if (result.getThreadInfo() != null) {
// no cpu usage info
String content = ThreadUtil.getFullStacktrace(result.getThreadInfo());
process.write(content);
} else if (result.getBusyThreads() != null) {
List<BusyThreadInfo> threadInfos = result.getBusyThreads();
for (BusyThreadInfo info : threadInfos) {
String stacktrace = ThreadUtil.getFullStacktrace(info, -1, -1);
process.write(stacktrace).write("\n");
}
} else if (result.getBlockingLockInfo() != null) {
String stacktrace = ThreadUtil.getFullStacktrace(result.getBlockingLockInfo());
process.write(stacktrace);
} else if (result.getThreadStateCount() != null) {
Map<Thread.State, Integer> threadStateCount = result.getThreadStateCount();
List<ThreadVO> threadStats = result.getThreadStats();
//sum total thread count
int total = 0;
for (Integer value : threadStateCount.values()) {
total += value;
}
int internalThreadCount = 0;
for (ThreadVO thread : threadStats) {
if (thread.getId() <= 0) {
internalThreadCount += 1;
}
}
total += internalThreadCount;
StringBuilder threadStat = new StringBuilder();
threadStat.append("Threads Total: ").append(total);
for (Thread.State s : Thread.State.values()) {
Integer count = threadStateCount.get(s);
threadStat.append(", ").append(s.name()).append(": ").append(count);
}
if (internalThreadCount > 0) {
threadStat.append(", Internal threads: ").append(internalThreadCount);
}
String stat = RenderUtil.render(new LabelElement(threadStat), process.width());
//thread stats
int height;
if (result.isAll()) {
height = threadStats.size() + 1;
} else {
height = Math.max(5, process.height() - 2);
//remove blank lines
height = Math.min(height, threadStats.size() + 2);
}
String content = ViewRenderUtil.drawThreadInfo(threadStats, process.width(), height);
process.write(stat + content);
}
}
}
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.ObjectVO;
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<TimeTunnelModel> {
@Override
public void draw(CommandProcess process, TimeTunnelModel timeTunnelModel) {
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());
TimeTunnelTable.drawReturnObj(table, tf, sizeLimit);
TimeTunnelTable.drawThrowException(table, tf);
process.write(RenderUtil.render(table, process.width()));
} else if (timeTunnelModel.getWatchValue() != null) {
//watch single TimeFragment: tt -i 1000 -w 'params'
ObjectVO valueVO = timeTunnelModel.getWatchValue();
if (valueVO.needExpand()) {
process.write(new ObjectView(sizeLimit, valueVO).draw()).write("\n");
} else {
process.write(StringUtils.objectToString(valueVO.getObject())).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(), 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());
if (replayResult.isReturn()) {
TimeTunnelTable.drawPlayResult(table, replayResult.getReturnObj(), sizeLimit, replayResult.getCost());
} else {
TimeTunnelTable.drawPlayException(table, replayResult.getThrowExp());
}
process.write(RenderUtil.render(table, process.width()))
.write(format("Time fragment[%d] successfully replayed %d times.", replayResult.getIndex(), replayNo))
.write("\n\n");
}
}
}
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<TraceModel> {
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 static final char PERCENTAGE = '%';
// 是否输出耗时
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=").append(threadNode.getTraceId());
}
if (threadNode.getRpcId() != null) {
sb.append(";rpc_id=").append(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) {
if(node.parent() instanceof ThreadNode) {
sb.append('[').append(nanoToMillis(node.getCost())).append(TIME_UNIT).append("] ");
}else {
MethodNode parentNode = (MethodNode) node.parent();
String percentage = String.format("%.2f", node.getCost()*100.0/parentNode.getTotalCost());
sb.append('[').append(percentage).append(PERCENTAGE).append(" ").append(nanoToMillis(node.getCost())).append(TIME_UNIT).append(" ").append("] ");
}
} else {
if(node.parent() instanceof ThreadNode) {
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("] ");
}else {
MethodNode parentNode = (MethodNode) node.parent();
String percentage = String.format("%.2f",node.getTotalCost()*100.0/parentNode.getTotalCost());
sb.append('[').append(percentage).append(PERCENTAGE).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<TraceNode> 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<TraceNode> 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<TraceNode> 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);
}
}
package com.taobao.arthas.core.command.view;
import com.sun.management.VMOption;
import com.taobao.arthas.core.command.model.VMOptionModel;
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.util.List;
import static com.taobao.text.ui.Element.label;
/**
* @author gongdewei 2020/4/15
*/
public class VMOptionView extends ResultView<VMOptionModel> {
@Override
public void draw(CommandProcess process, VMOptionModel result) {
if (result.getVmOptions() != null) {
process.write(renderVMOptions(result.getVmOptions(), process.width()));
} else if (result.getChangeResult() != null) {
TableElement table = ViewRenderUtil.renderChangeResult(result.getChangeResult());
process.write(RenderUtil.render(table, process.width()));
}
}
private static String renderVMOptions(List<VMOption> diagnosticOptions, int width) {
TableElement table = new TableElement(1, 1, 1, 1).leftCellPadding(1).rightCellPadding(1);
table.row(true, label("KEY").style(Decoration.bold.bold()),
label("VALUE").style(Decoration.bold.bold()),
label("ORIGIN").style(Decoration.bold.bold()),
label("WRITEABLE").style(Decoration.bold.bold()));
for (VMOption option : diagnosticOptions) {
table.row(option.getName(), option.getValue(), "" + option.getOrigin(), "" + option.isWriteable());
}
return RenderUtil.render(table, width);
}
}
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.VersionModel;
import com.taobao.arthas.core.shell.command.CommandProcess;
/**
* @author gongdewei 2020/3/27
*/
public class VersionView extends ResultView<VersionModel> {
@Override
public void draw(CommandProcess process, VersionModel result) {
writeln(process, result.getVersion());
}
}
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.ChangeResultVO;
import com.taobao.arthas.core.command.model.EnhancerAffectVO;
import com.taobao.arthas.core.command.model.ThreadVO;
import com.taobao.arthas.core.util.StringUtils;
import com.taobao.text.Color;
import com.taobao.text.Decoration;
import com.taobao.text.Style;
import com.taobao.text.ui.LabelElement;
import com.taobao.text.ui.Overflow;
import com.taobao.text.ui.RowElement;
import com.taobao.text.ui.TableElement;
import com.taobao.text.util.RenderUtil;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import static com.taobao.text.ui.Element.label;
import static java.lang.String.format;
/**
* view render util for term/tty
* @author gongdewei 2020/6/22
*/
public class ViewRenderUtil {
/** Thread State Colors */
public static final EnumMap<Thread.State, Color> colorMapping = new EnumMap<Thread.State, Color>(Thread.State.class);
static {
colorMapping.put(Thread.State.NEW, Color.cyan);
colorMapping.put(Thread.State.RUNNABLE, Color.green);
colorMapping.put(Thread.State.BLOCKED, Color.red);
colorMapping.put(Thread.State.WAITING, Color.yellow);
colorMapping.put(Thread.State.TIMED_WAITING, Color.magenta);
colorMapping.put(Thread.State.TERMINATED, Color.blue);
}
/**
* Render key-value table
* @param map
* @param width
* @return
*/
public static String renderKeyValueTable(Map<String, String> map, int width) {
TableElement table = new TableElement(1, 4).leftCellPadding(1).rightCellPadding(1);
table.row(true, label("KEY").style(Decoration.bold.bold()), label("VALUE").style(Decoration.bold.bold()));
for (Map.Entry<String, String> entry : map.entrySet()) {
table.row(entry.getKey(), entry.getValue());
}
return RenderUtil.render(table, width);
}
/**
* Render change result vo
* @param result
* @return
*/
public static TableElement renderChangeResult(ChangeResultVO result) {
TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1);
table.row(true, label("NAME").style(Decoration.bold.bold()),
label("BEFORE-VALUE").style(Decoration.bold.bold()),
label("AFTER-VALUE").style(Decoration.bold.bold()));
table.row(result.getName(), StringUtils.objectToString(result.getBeforeValue()),
StringUtils.objectToString(result.getAfterValue()));
return table;
}
/**
* Render EnhancerAffectVO
* @param affectVO
* @return
*/
public static String renderEnhancerAffect(EnhancerAffectVO affectVO) {
final StringBuilder infoSB = new StringBuilder();
List<String> classDumpFiles = affectVO.getClassDumpFiles();
if (classDumpFiles != null) {
for (String classDumpFile : classDumpFiles) {
infoSB.append("[dump: ").append(classDumpFile).append("]\n");
}
}
List<String> methods = affectVO.getMethods();
if (methods != null) {
for (String method : methods) {
infoSB.append("[Affect method: ").append(method).append("]\n");
}
}
infoSB.append(format("Affect(class count: %d , method count: %d) cost in %s ms, listenerId: %d",
affectVO.getClassCount(),
affectVO.getMethodCount(),
affectVO.getCost(),
affectVO.getListenerId()));
if (affectVO.getThrowable() != null) {
infoSB.append("\nEnhance error! exception: ").append(affectVO.getThrowable());
}
infoSB.append("\n");
return infoSB.toString();
}
public static String drawThreadInfo(List<ThreadVO> threads, int width, int height) {
TableElement table = new TableElement(1, 6, 3, 2, 2, 2, 2, 2, 2, 2).overflow(Overflow.HIDDEN).rightCellPadding(1);
// Header
table.add(
new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add(
"ID",
"NAME",
"GROUP",
"PRIORITY",
"STATE",
"%CPU",
"DELTA_TIME",
"TIME",
"INTERRUPTED",
"DAEMON"
)
);
int count = 0;
for (ThreadVO thread : threads) {
Color color = colorMapping.get(thread.getState());
String time = formatTimeMills(thread.getTime());
String deltaTime = formatTimeMillsToSeconds(thread.getDeltaTime());
double cpu = thread.getCpu();
LabelElement daemonLabel = new LabelElement(thread.isDaemon());
if (!thread.isDaemon()) {
daemonLabel.setStyle(Style.style(Color.magenta));
}
LabelElement stateElement;
if (thread.getState() != null) {
stateElement = new LabelElement(thread.getState()).style(color.fg());
} else {
stateElement = new LabelElement("-");
}
table.row(
new LabelElement(thread.getId()),
new LabelElement(thread.getName()),
new LabelElement(thread.getGroup() != null ? thread.getGroup() : "-"),
new LabelElement(thread.getPriority()),
stateElement,
new LabelElement(cpu),
new LabelElement(deltaTime),
new LabelElement(time),
new LabelElement(thread.isInterrupted()),
daemonLabel
);
if (++count >= height) {
break;
}
}
return RenderUtil.render(table, width, height);
}
private static String formatTimeMills(long timeMills) {
long seconds = timeMills / 1000;
long mills = timeMills % 1000;
long min = seconds / 60;
seconds = seconds % 60;
//return String.format("%d:%d.%03d", min, seconds, mills);
String str;
if (mills >= 100) {
str = min + ":" + seconds + "." + mills;
} else if (mills >= 10) {
str = min + ":" + seconds + ".0" + mills;
} else {
str = min + ":" + seconds + ".00" + mills;
}
return str;
}
private static String formatTimeMillsToSeconds(long timeMills) {
long seconds = timeMills / 1000;
long mills = timeMills % 1000;
//return String.format("%d.%03d", seconds, mills);
String str;
if (mills >= 100) {
str = seconds + "." + mills;
} else if (mills >= 10) {
str = seconds + ".0" + mills;
} else {
str = seconds + ".00" + mills;
}
return str;
}
}
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.ObjectVO;
import com.taobao.arthas.core.command.model.VmToolModel;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.StringUtils;
import com.taobao.arthas.core.view.ObjectView;
/**
*
* @author hengyunabc 2022-04-24
*
*/
public class VmToolView extends ResultView<VmToolModel> {
@Override
public void draw(CommandProcess process, VmToolModel model) {
if (model.getMatchedClassLoaders() != null) {
process.write("Matched classloaders: \n");
ClassLoaderView.drawClassLoaders(process, model.getMatchedClassLoaders(), false);
process.write("\n");
return;
}
ObjectVO objectVO = model.getValue();
String resultStr = StringUtils.objectToString(objectVO.needExpand() ? new ObjectView(objectVO).draw() : objectVO.getObject());
process.write(resultStr).write("\n");
}
}
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.ObjectVO;
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<WatchModel> {
@Override
public void draw(CommandProcess process, WatchModel model) {
ObjectVO objectVO = model.getValue();
String result = StringUtils.objectToString(
objectVO.needExpand() ? new ObjectView(model.getSizeLimit(), objectVO).draw() : objectVO.getObject());
process.write("method=" + model.getClassName() + "." + model.getMethodName() + " location=" + model.getAccessPoint() + "\n");
process.write("ts=" + DateUtils.formatDate(model.getTs()) + "; [cost=" + model.getCost() + "ms] result=" + result + "\n");
}
}
package com.taobao.arthas.core.config;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.taobao.arthas.core.env.Environment;
/**
*
* @author hengyunabc 2020-01-10
*
*/
public class BinderUtils {
public static void inject(Environment environment, Object instance) {
inject(environment, null, null, instance);
}
public static void inject(Environment environment, String prefix, Object instance) {
inject(environment, null, prefix, instance);
}
public static void inject(Environment environment, String parentPrefix, String prefix, Object instance) {
if (prefix == null) {
prefix = "";
}
Class<? extends Object> type = instance.getClass();
try {
Config annotation = type.getAnnotation(Config.class);
if (annotation == null) {
prefix = parentPrefix + '.' + prefix;
} else {
prefix = annotation.prefix();
if (prefix != null) {
if (parentPrefix != null && parentPrefix.length() > 0) {
prefix = parentPrefix + '.' + prefix;
}
}
}
Method[] declaredMethods = type.getDeclaredMethods();
// 获取到所有setter方法,再提取出field。根据前缀从 properties里取出值,再尝试用setter方法注入到对象里
for (Method method : declaredMethods) {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1 && methodName.startsWith("set") && methodName.length() > "set".length()) {
String field = getFieldNameFromSetterMethod(methodName);
String configKey = prefix + '.' + field;
if (environment.containsProperty(configKey)) {
Object reslovedValue = environment.getProperty(prefix + '.' + field, parameterTypes[0]);
if (reslovedValue != null) {
method.invoke(instance, new Object[] { reslovedValue });
}
}
}
}
} catch (Exception e) {
throw new RuntimeException("inject error. prefix: " + prefix + ", instance: " + instance, e);
}
// process @NestedConfig
Field[] fields = type.getDeclaredFields();
for (Field field : fields) {
NestedConfig nestedConfig = field.getAnnotation(NestedConfig.class);
if (nestedConfig != null) {
String prefixForField = field.getName();
if (parentPrefix != null && prefix.length() > 0) {
prefixForField = prefix + '.' + prefixForField;
}
field.setAccessible(true);
try {
Object fieldValue = field.get(instance);
if (fieldValue == null) {
fieldValue = field.getType().newInstance();
}
inject(environment, prefix, prefixForField, fieldValue);
field.set(instance, fieldValue);
} catch (Exception e) {
throw new RuntimeException("process @NestedConfig error, field: " + field + ", prefix: "
+ prefix + ", instance: " + instance, e);
}
}
}
}
/**
* 从setter方法获取到field的String。比如 setHost, 则获取到的是host。
*
* @param methodName
* @return
*/
private static String getFieldNameFromSetterMethod(String methodName) {
String field = methodName.substring("set".length());
String startPart = field.substring(0, 1).toLowerCase();
String endPart = field.substring(1);
field = startPart + endPart;
return field;
}
}
package com.taobao.arthas.core.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author hengyunabc 2019-08-05
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Config {
String prefix() default "";
}
\ No newline at end of file
package com.taobao.arthas.core.config;
import com.taobao.arthas.core.shell.ShellServerOptions;
import com.taobao.arthas.core.util.reflect.ArthasReflectUtils;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import static java.lang.reflect.Modifier.isStatic;
/**
* <pre>
* 配置类。
* 注意本类里的所有字段不能有默认值,否则会出现配置混乱。
* 在 com.taobao.arthas.core.Arthas#attach 里会调用 Configure#toStrig
* <pre>
*
* @author vlinux
* @author hengyunabc 2018-11-12
*/
@Config(prefix = "arthas")
public class Configure {
private String ip;
private Integer telnetPort;
private Integer httpPort;
private Long javaPid;
private String arthasCore;
private String arthasAgent;
private String tunnelServer;
private String agentId;
private String username;
private String password;
/**
* @see com.taobao.arthas.common.ArthasConstants#ARTHAS_OUTPUT
*/
private String outputPath;
/**
* 需要被增强的ClassLoader的全类名,多个用英文 , 分隔
*/
private String enhanceLoaders;
/**
* <pre>
* 1. 如果显式传入 arthas.agentId ,则直接使用
* 2. 如果用户没有指定,则自动尝试在查找应用的 appname,加为前缀,比如 system properties设置 project.name是 demo,则
* 生成的 agentId是 demo-xxxx
* </pre>
*/
private String appName;
/**
* report executed command
*/
private String statUrl;
/**
* session timeout seconds
* @see ShellServerOptions#DEFAULT_SESSION_TIMEOUT
*/
private Long sessionTimeout;
/**
* disabled commands
*/
private String disabledCommands;
/**
* 本地连接不需要鉴权,即使配置了password。arthas.properties 里默认为true
*/
private Boolean localConnectionNonAuth;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getTelnetPort() {
return telnetPort;
}
public void setTelnetPort(int telnetPort) {
this.telnetPort = telnetPort;
}
public void setHttpPort(int httpPort) {
this.httpPort = httpPort;
}
public Integer getHttpPort() {
return httpPort;
}
public long getJavaPid() {
return javaPid;
}
public void setJavaPid(long javaPid) {
this.javaPid = javaPid;
}
public String getArthasAgent() {
return arthasAgent;
}
public void setArthasAgent(String arthasAgent) {
this.arthasAgent = arthasAgent;
}
public String getArthasCore() {
return arthasCore;
}
public void setArthasCore(String arthasCore) {
this.arthasCore = arthasCore;
}
public Long getSessionTimeout() {
return sessionTimeout;
}
public void setSessionTimeout(long sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public String getTunnelServer() {
return tunnelServer;
}
public void setTunnelServer(String tunnelServer) {
this.tunnelServer = tunnelServer;
}
public String getAgentId() {
return agentId;
}
public void setAgentId(String agentId) {
this.agentId = agentId;
}
public String getStatUrl() {
return statUrl;
}
public void setStatUrl(String statUrl) {
this.statUrl = statUrl;
}
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getEnhanceLoaders() {
return enhanceLoaders;
}
public void setEnhanceLoaders(String enhanceLoaders) {
this.enhanceLoaders = enhanceLoaders;
}
public String getOutputPath() {
return outputPath;
}
public void setOutputPath(String outputPath) {
this.outputPath = outputPath;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDisabledCommands() {
return disabledCommands;
}
public void setDisabledCommands(String disabledCommands) {
this.disabledCommands = disabledCommands;
}
public boolean isLocalConnectionNonAuth() {
return localConnectionNonAuth != null && localConnectionNonAuth;
}
public void setLocalConnectionNonAuth(boolean localConnectionNonAuth) {
this.localConnectionNonAuth = localConnectionNonAuth;
}
/**
* 序列化成字符串
*
* @return 序列化字符串
*/
@Override
public String toString() {
final Map<String, String> map = new HashMap<String, String>();
for (Field field : ArthasReflectUtils.getFields(Configure.class)) {
// 过滤掉静态类
if (isStatic(field.getModifiers())) {
continue;
}
// 非静态的才需要纳入非序列化过程
try {
Object fieldValue = ArthasReflectUtils.getFieldValueByField(this, field);
if (fieldValue != null) {
map.put(field.getName(), String.valueOf(fieldValue));
}
} catch (Throwable t) {
//
}
}
return FeatureCodec.DEFAULT_COMMANDLINE_CODEC.toString(map);
}
/**
* 反序列化字符串成对象
*
* @param toString 序列化字符串
* @return 反序列化的对象
*/
public static Configure toConfigure(String toString) throws IllegalAccessException {
final Configure configure = new Configure();
final Map<String, String> map = FeatureCodec.DEFAULT_COMMANDLINE_CODEC.toMap(toString);
for (Map.Entry<String, String> entry : map.entrySet()) {
final Field field = ArthasReflectUtils.getField(Configure.class, entry.getKey());
if (null != field && !isStatic(field.getModifiers())) {
ArthasReflectUtils.set(field, ArthasReflectUtils.valueOf(field.getType(), entry.getValue()), configure);
}
}
return configure;
}
}
package com.taobao.arthas.core.config;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals;
import static com.taobao.arthas.core.util.ArthasCheckUtils.isIn;
import static com.taobao.arthas.core.util.StringUtils.isBlank;
/**
* Feature编解器(线程安全)<br/>
* <p/>
* 用于封装系统内部features/attribute等扩展字段的管理
* Created by dukun on 15/3/31.
*/
public class FeatureCodec {
// 对象的编码解码器
public final static FeatureCodec DEFAULT_COMMANDLINE_CODEC = new FeatureCodec(';', '=');
/**
* KV片段分割符<br/>
* KV片段定义为一个完整的KV对,例如字符串<span>;k1=v1;k2=v2;</span>
* 其中<b>;</b>即为KV片段分隔符
*/
private final char kvSegmentSeparator;
/**
* KV分割符<br/>
* KV定义为一个KV对区分K和V的分割符号,例如字符串<span>k1=v1</span>
* 其中<b>=</b>即为KV分隔符
*/
private final char kvSeparator;
/**
* 转义前缀符
*/
private static final char ESCAPE_PREFIX_CHAR = '\\';
/**
* 使用指定的KV分割符构造FeatureParser<br/>
*
* @param kvSegmentSeparator KV对之间的分隔符
* @param kvSeparator K与V之间的分隔符
*/
public FeatureCodec(final char kvSegmentSeparator, final char kvSeparator) {
// 分隔符禁止与转义前缀符相等
if (isIn(ESCAPE_PREFIX_CHAR, kvSegmentSeparator, kvSeparator)) {
throw new IllegalArgumentException("separator can not init to '" + ESCAPE_PREFIX_CHAR + "'.");
}
this.kvSegmentSeparator = kvSegmentSeparator;
this.kvSeparator = kvSeparator;
}
/**
* map集合转换到feature字符串
*
* @param map map集合
* @return feature字符串
*/
public String toString(final Map<String, String> map) {
final StringBuilder featureSB = new StringBuilder().append(kvSegmentSeparator);
if (null == map
|| map.isEmpty()) {
return featureSB.toString();
}
for (Map.Entry<String, String> entry : map.entrySet()) {
featureSB
.append(escapeEncode(entry.getKey()))
.append(kvSeparator)
.append(escapeEncode(entry.getValue()))
.append(kvSegmentSeparator)
;
}
return featureSB.toString();
}
/**
* feature字符串转换到map集合
*
* @param featureString the feature string
* @return the map
*/
public Map<String, String> toMap(final String featureString) {
final Map<String, String> map = new HashMap<String, String>();
if (isBlank(featureString)) {
return map;
}
for (String kv : escapeSplit(featureString, kvSegmentSeparator)) {
if (isBlank(kv)) {
// 过滤掉为空的字符串片段
continue;
}
final String[] ar = escapeSplit(kv, kvSeparator);
if (ar.length != 2) {
// 过滤掉不符合K:V单目的情况
continue;
}
final String k = ar[0];
final String v = ar[1];
if (!isBlank(k)
&& !isBlank(v)) {
map.put(escapeDecode(k), escapeDecode(v));
}
}
return map;
}
/**
* 转义编码
*
* @param string 原始字符串
* @return 转义编码后的字符串
*/
private String escapeEncode(final String string) {
final StringBuilder returnSB = new StringBuilder();
for (final char c : string.toCharArray()) {
if (isIn(c, kvSegmentSeparator, kvSeparator, ESCAPE_PREFIX_CHAR)) {
returnSB.append(ESCAPE_PREFIX_CHAR);
}
returnSB.append(c);
}
return returnSB.toString();
}
/**
* 转义解码
*
* @param string 编码字符串
* @return 转义解码后的字符串
*/
private String escapeDecode(String string) {
final StringBuilder segmentSB = new StringBuilder();
final int stringLength = string.length();
for (int index = 0; index < stringLength; index++) {
final char c = string.charAt(index);
if (isEquals(c, ESCAPE_PREFIX_CHAR)
&& index < stringLength - 1) {
final char nextChar = string.charAt(++index);
// 下一个字符是转义符
if (isIn(nextChar, kvSegmentSeparator, kvSeparator, ESCAPE_PREFIX_CHAR)) {
segmentSB.append(nextChar);
}
// 如果不是转义字符,则需要两个都放入
else {
segmentSB.append(c);
segmentSB.append(nextChar);
}
} else {
segmentSB.append(c);
}
}
return segmentSB.toString();
}
/**
* 编码字符串拆分
*
* @param string 编码字符串
* @param splitEscapeChar 分割符
* @return 拆分后的字符串数组
*/
private String[] escapeSplit(String string, char splitEscapeChar) {
final ArrayList<String> segmentArrayList = new ArrayList<String>();
final Stack<Character> decodeStack = new Stack<Character>();
final int stringLength = string.length();
for (int index = 0; index < stringLength; index++) {
boolean isArchive = false;
final char c = string.charAt(index);
// 匹配到转义前缀符
if (isEquals(c, ESCAPE_PREFIX_CHAR)) {
decodeStack.push(c);
if (index < stringLength - 1) {
final char nextChar = string.charAt(++index);
decodeStack.push(nextChar);
}
}
// 匹配到分割符
else if (isEquals(c, splitEscapeChar)) {
isArchive = true;
}
// 匹配到其他字符
else {
decodeStack.push(c);
}
if (isArchive
|| index == stringLength - 1) {
final StringBuilder segmentSB = new StringBuilder(decodeStack.size());
while (!decodeStack.isEmpty()) {
segmentSB.append(decodeStack.pop());
}
segmentArrayList.add(
segmentSB
.reverse() // 因为堆栈中是逆序的,所以需要对逆序的字符串再次逆序
.toString() // toString
.trim() // 考虑到字符串片段可能会出现首尾空格的场景,这里做一个过滤
);
}
}
return segmentArrayList.toArray(new String[0]);
}
}
\ No newline at end of file
package com.taobao.arthas.core.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author hengyunabc 2019-08-05
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NestedConfig {
}
\ No newline at end of file
package com.taobao.arthas.core.distribution;
/**
* 复合结果分发器,将消息同时分发给其包含的多个真实分发器
* @author gongdewei 2020/4/30
*/
public interface CompositeResultDistributor extends ResultDistributor {
void addDistributor(ResultDistributor distributor);
void removeDistributor(ResultDistributor distributor);
}
package com.taobao.arthas.core.distribution;
/**
* 命令结果分发器选项
* @author gongdewei 2020/5/18
*/
public class DistributorOptions {
/**
* ResultConsumer的结果队列长度,用于控制内存缓存的命令结果数据量
*/
public static int resultQueueSize = 50;
}
package com.taobao.arthas.core.distribution;
import com.taobao.arthas.core.command.model.ResultModel;
import java.util.List;
public interface PackingResultDistributor extends ResultDistributor {
/**
* Get results of command
*/
List<ResultModel> getResults();
}
package com.taobao.arthas.core.distribution;
import com.taobao.arthas.core.command.model.ResultModel;
import java.util.List;
/**
* Command result consumer
* @author gongdewei 2020-03-26
*/
public interface ResultConsumer {
/**
* Append the phased result to queue
* @param result a phased result of the command
* @return true means distribution success, return false means discard data
*/
boolean appendResult(ResultModel result);
/**
* Retrieves and removes a pack of results from the head
* @return a pack of results
*/
List<ResultModel> pollResults();
long getLastAccessTime();
void close();
boolean isClosed();
boolean isPolling();
String getConsumerId();
void setConsumerId(String consumerId);
/**
* Retrieves the consumer's health status
* @return
*/
boolean isHealthy();
}
package com.taobao.arthas.core.distribution;
import com.alibaba.fastjson.JSON;
import com.taobao.arthas.core.command.model.Countable;
import com.taobao.arthas.core.command.model.ResultModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 命令结果模型辅助类
*
* @author gongdewei 2020/5/18
*/
public class ResultConsumerHelper {
private static final Logger logger = LoggerFactory.getLogger(ResultConsumerHelper.class);
private static ConcurrentHashMap<String, List<Field>> modelFieldMap = new ConcurrentHashMap<String, List<Field>>();
/**
* 估算命令执行结果的item数量,目的是提供一个度量值,作为Consumer分发时进行切片的参考依据,避免单次发送大量数据。
* 注意:此方法调用频繁,避免产生内存碎片
*
* @param model
* @return
*/
public static int getItemCount(ResultModel model) {
//如果实现Countable接口,则认为model自己统计元素数量
if (model instanceof Countable) {
return ((Countable) model).size();
}
//对于普通的Model,通过类反射统计容器类字段统计元素数量
//缓存Field对象,避免产生内存碎片
Class modelClass = model.getClass();
List<Field> fields = modelFieldMap.get(modelClass.getName());
if (fields == null) {
fields = new ArrayList<Field>();
Field[] declaredFields = modelClass.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
Field field = declaredFields[i];
Class<?> fieldClass = field.getType();
//如果是List/Map/Array/Countable类型的字段,则缓存起来后面统计数量
if (Collection.class.isAssignableFrom(fieldClass)
|| Map.class.isAssignableFrom(fieldClass)
|| Countable.class.isAssignableFrom(fieldClass)
|| fieldClass.isArray()) {
field.setAccessible(true);
fields.add(field);
}
}
List<Field> old_fields = modelFieldMap.putIfAbsent(modelClass.getName(), fields);
if (old_fields != null) {
fields = old_fields;
}
}
//统计Model对象的item数量
int count = 0;
try {
for (int i = 0; i < fields.size(); i++) {
Field field = fields.get(i);
if (!field.isAccessible()) {
field.setAccessible(true);
}
Object value = field.get(model);
if (value != null) {
if (value instanceof Collection) {
count += ((Collection) value).size();
} else if (value.getClass().isArray()) {
count += Array.getLength(value);
} else if (value instanceof Map) {
count += ((Map) value).size();
} else if (value instanceof Countable) {
count += ((Countable) value).size();
}
}
}
} catch (Exception e) {
logger.error("get item count of result model failed, model: {}", JSON.toJSONString(model), e);
}
return count > 0 ? count : 1;
}
}
package com.taobao.arthas.core.distribution;
import com.taobao.arthas.core.command.model.ResultModel;
/**
* Command result distributor, sending results to consumers who joins in the same session.
* @author gongdewei 2020-03-26
*/
public interface ResultDistributor {
/**
* Append the phased result to queue
* @param result a phased result of the command
*/
void appendResult(ResultModel result);
/**
* Close result distribtor, release resources
*/
void close();
}
package com.taobao.arthas.core.distribution;
import java.util.List;
public interface SharingResultDistributor extends ResultDistributor {
/**
* Add consumer to sharing session
* @param consumer
*/
void addConsumer(ResultConsumer consumer);
/**
* Remove consumer from sharing session
* @param consumer
*/
void removeConsumer(ResultConsumer consumer);
/**
* Get all consumers of session
* @return
*/
List<ResultConsumer> getConsumers();
/**
* Get consumer by id
* @param consumerId
* @return
*/
ResultConsumer getConsumer(String consumerId);
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment