From 40c2c5e039db0809f9f0959a988a19839ace88a0 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Tue, 14 Jul 2020 20:12:57 +0800 Subject: [PATCH 1/5] transform command: logger --- .../core/command/logger/LoggerCommand.java | 148 +++++------------- .../core/command/model/LoggerModel.java | 34 ++++ .../arthas/core/command/view/LoggerView.java | 98 ++++++++++++ 3 files changed, 174 insertions(+), 106 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/LoggerModel.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/LoggerView.java diff --git a/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java index a2a1d45d9..d05b56964 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/logger/LoggerCommand.java @@ -1,7 +1,5 @@ package com.taobao.arthas.core.command.logger; -import static com.taobao.text.ui.Element.label; - import java.io.IOException; import java.io.InputStream; import java.lang.instrument.Instrumentation; @@ -21,6 +19,7 @@ import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.common.IOUtils; import com.taobao.arthas.common.ReflectUtils; import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.model.LoggerModel; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.ClassLoaderUtils; @@ -29,15 +28,11 @@ 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.text.Decoration; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; /** * logger command - * - * @author hengyunabc 2019-09-04 * + * @author hengyunabc 2019-09-04 */ //@formatter:off @Name("logger") @@ -60,7 +55,7 @@ public class LoggerCommand extends AnnotatedCommand { private static Map, byte[]> classToBytesMap = new HashMap, byte[]>(); private static String arthasClassLoaderHash = ClassLoaderUtils - .classLoaderHash(LoggerCommand.class.getClassLoader()); + .classLoaderHash(LoggerCommand.class.getClassLoader()); static { LoggerHelperBytes = loadClassBytes(LoggerHelper.class); @@ -111,15 +106,11 @@ public class LoggerCommand extends AnnotatedCommand { @Override public void process(CommandProcess process) { - int status = 0; - try { - if (this.name != null && this.level != null) { - level(process); - } else { - loggers(process, name); - } - } finally { - process.end(status); + // 每个分支中调用process.end()结束执行 + if (this.name != null && this.level != null) { + level(process); + } else { + loggers(process, name); } } @@ -154,9 +145,9 @@ public class LoggerCommand extends AnnotatedCommand { } if (result) { - process.write("Update logger level success.\n"); + process.end(0, "Update logger level success."); } else { - process.write("Update logger level fail. Try to specify the classloader with the -c option. Use `sc -d CLASSNAME` to find out the classloader hashcode.\n"); + process.end(-1, "Update logger level fail. Try to specify the classloader with the -c option. Use `sc -d CLASSNAME` to find out the classloader hashcode."); } } @@ -194,97 +185,21 @@ public class LoggerCommand extends AnnotatedCommand { if (loggerTypes.contains(LoggerType.LOG4J)) { Map> loggerInfoMap = loggerInfo(classLoader, Log4jHelper.class); - String renderResult = renderLoggerInfo(loggerInfoMap, process.width()); - - process.write(renderResult); + process.appendResult(new LoggerModel(loggerInfoMap)); } if (loggerTypes.contains(LoggerType.LOGBACK)) { Map> loggerInfoMap = loggerInfo(classLoader, LogbackHelper.class); - String renderResult = renderLoggerInfo(loggerInfoMap, process.width()); - - process.write(renderResult); + process.appendResult(new LoggerModel(loggerInfoMap)); } if (loggerTypes.contains(LoggerType.LOG4J2)) { Map> loggerInfoMap = loggerInfo(classLoader, Log4j2Helper.class); - String renderResult = renderLoggerInfo(loggerInfoMap, process.width()); - - process.write(renderResult); + process.appendResult(new LoggerModel(loggerInfoMap)); } } - } - - private String renderLoggerInfo(Map> loggerInfos, int width) { - StringBuilder sb = new StringBuilder(8192); - - for (Entry> entry : loggerInfos.entrySet()) { - Map info = entry.getValue(); - - TableElement table = new TableElement(2, 10).leftCellPadding(1).rightCellPadding(1); - TableElement appendersTable = new TableElement().rightCellPadding(1); - - Class clazz = (Class) info.get(LoggerHelper.clazz); - table.row(label(LoggerHelper.name).style(Decoration.bold.bold()), label("" + info.get(LoggerHelper.name))) - .row(label(LoggerHelper.clazz).style(Decoration.bold.bold()), label("" + clazz.getName())) - .row(label(LoggerHelper.classLoader).style(Decoration.bold.bold()), - label("" + clazz.getClassLoader())) - .row(label(LoggerHelper.classLoaderHash).style(Decoration.bold.bold()), - label("" + StringUtils.classLoaderHash(clazz))) - .row(label(LoggerHelper.level).style(Decoration.bold.bold()), - label("" + info.get(LoggerHelper.level))); - if (info.get(LoggerHelper.effectiveLevel) != null) { - table.row(label(LoggerHelper.effectiveLevel).style(Decoration.bold.bold()), - label("" + info.get(LoggerHelper.effectiveLevel))); - } - - if (info.get(LoggerHelper.config) != null) { - table.row(label(LoggerHelper.config).style(Decoration.bold.bold()), - label("" + info.get(LoggerHelper.config))); - } - - table.row(label(LoggerHelper.additivity).style(Decoration.bold.bold()), - label("" + info.get(LoggerHelper.additivity))) - .row(label(LoggerHelper.codeSource).style(Decoration.bold.bold()), - label("" + info.get(LoggerHelper.codeSource))); - - @SuppressWarnings("unchecked") - List> appenders = (List>) info.get(LoggerHelper.appenders); - if (appenders != null && !appenders.isEmpty()) { - - for (Map appenderInfo : appenders) { - Class appenderClass = (Class) appenderInfo.get(LoggerHelper.clazz); - - appendersTable.row(label(LoggerHelper.name).style(Decoration.bold.bold()), - label("" + appenderInfo.get(LoggerHelper.name))); - appendersTable.row(label(LoggerHelper.clazz), label("" + appenderClass.getName())); - appendersTable.row(label(LoggerHelper.classLoader), label("" + appenderClass.getClassLoader())); - appendersTable.row(label(LoggerHelper.classLoaderHash), - label("" + StringUtils.classLoaderHash(appenderClass))); - if (appenderInfo.get(LoggerHelper.file) != null) { - appendersTable.row(label(LoggerHelper.file), label("" + appenderInfo.get(LoggerHelper.file))); - } - if (appenderInfo.get(LoggerHelper.target) != null) { - appendersTable.row(label(LoggerHelper.target), - label("" + appenderInfo.get(LoggerHelper.target))); - } - if (appenderInfo.get(LoggerHelper.blocking) != null) { - appendersTable.row(label(LoggerHelper.blocking), - label("" + appenderInfo.get(LoggerHelper.blocking))); - } - if (appenderInfo.get(LoggerHelper.appenderRef) != null) { - appendersTable.row(label(LoggerHelper.appenderRef), - label("" + appenderInfo.get(LoggerHelper.appenderRef))); - } - } - - table.row(label("appenders").style(Decoration.bold.bold()), appendersTable); - } - - sb.append(RenderUtil.render(table, width)).append('\n'); - } - return sb.toString(); + process.end(); } private static String helperClassNameWithClassLoader(ClassLoader classLoader, Class helperClass) { @@ -304,25 +219,46 @@ public class LoggerCommand extends AnnotatedCommand { } catch (ClassNotFoundException e) { try { byte[] helperClassBytes = AsmRenameUtil.renameClass(classToBytesMap.get(helperClass), - helperClass.getName(), helperClassName); + helperClass.getName(), helperClassName); ReflectUtils.defineClass(helperClassName, helperClassBytes, classLoader); } catch (Throwable e1) { logger.error("arthas loggger command try to define helper class error: " + helperClassName, - e1); + e1); } } try { Class clazz = classLoader.loadClass(helperClassName); - Method getLoggersMethod = clazz.getMethod("getLoggers", new Class[] { String.class, boolean.class }); + Method getLoggersMethod = clazz.getMethod("getLoggers", new Class[]{String.class, boolean.class}); loggers = (Map>) getLoggersMethod.invoke(null, - new Object[] { name, includeNoAppender }); + new Object[]{name, includeNoAppender}); } catch (Throwable e) { // ignore } + + //expose attributes to json: classloader, classloaderHash + for (Map loggerInfo : loggers.values()) { + Class clazz = (Class) loggerInfo.get(LoggerHelper.clazz); + loggerInfo.put(LoggerHelper.classLoader, getClassLoaderName(clazz.getClassLoader())); + loggerInfo.put(LoggerHelper.classLoaderHash, StringUtils.classLoaderHash(clazz)); + + List> appenders = (List>) loggerInfo.get(LoggerHelper.appenders); + for (Map appenderInfo : appenders) { + Class appenderClass = (Class) appenderInfo.get(LoggerHelper.clazz); + if (appenderClass != null) { + appenderInfo.put(LoggerHelper.classLoader, getClassLoaderName(appenderClass.getClassLoader())); + appenderInfo.put(LoggerHelper.classLoaderHash, StringUtils.classLoaderHash(appenderClass)); + } + } + } + return loggers; } + private String getClassLoaderName(ClassLoader classLoader) { + return classLoader == null ? null : classLoader.toString(); + } + private Boolean updateLevel(Instrumentation inst, Class helperClass) throws Exception { ClassLoader classLoader = null; if (hashCode == null) { @@ -332,8 +268,8 @@ public class LoggerCommand extends AnnotatedCommand { } Class clazz = classLoader.loadClass(helperClassNameWithClassLoader(classLoader, helperClass)); - Method updateLevelMethod = clazz.getMethod("updateLevel", new Class[] { String.class, String.class }); - return (Boolean) updateLevelMethod.invoke(null, new Object[] { this.name, this.level }); + Method updateLevelMethod = clazz.getMethod("updateLevel", new Class[]{String.class, String.class}); + return (Boolean) updateLevelMethod.invoke(null, new Object[]{this.name, this.level}); } @@ -360,7 +296,7 @@ public class LoggerCommand extends AnnotatedCommand { private static byte[] loadClassBytes(Class clazz) { try { InputStream stream = LoggerCommand.class.getClassLoader() - .getResourceAsStream(clazz.getName().replace('.', '/') + ".class"); + .getResourceAsStream(clazz.getName().replace('.', '/') + ".class"); return IOUtils.getBytes(stream); } catch (IOException e) { diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/LoggerModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/LoggerModel.java new file mode 100644 index 000000000..05ee2e0ac --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/LoggerModel.java @@ -0,0 +1,34 @@ +package com.taobao.arthas.core.command.model; + +import java.util.Map; + +/** + * Model of logger command + * + * @author gongdewei 2020/4/22 + */ +public class LoggerModel extends ResultModel { + + private Map> loggerInfoMap; + + public LoggerModel() { + } + + public LoggerModel(Map> loggerInfoMap) { + this.loggerInfoMap = loggerInfoMap; + } + + public Map> getLoggerInfoMap() { + return loggerInfoMap; + } + + public void setLoggerInfoMap(Map> loggerInfoMap) { + this.loggerInfoMap = loggerInfoMap; + } + + @Override + public String getType() { + return "logger"; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/LoggerView.java b/core/src/main/java/com/taobao/arthas/core/command/view/LoggerView.java new file mode 100644 index 000000000..b6d925aa4 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/LoggerView.java @@ -0,0 +1,98 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.logger.LoggerHelper; +import com.taobao.arthas.core.command.model.LoggerModel; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.text.Decoration; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + +import java.util.List; +import java.util.Map; + +import static com.taobao.text.ui.Element.label; + +/** + * View of 'logger' command + * + * @author gongdewei 2020/4/22 + */ +public class LoggerView extends ResultView { + + @Override + public void draw(CommandProcess process, LoggerModel result) { + process.write(renderLoggerInfo(result.getLoggerInfoMap(), process.width())); + } + + private String renderLoggerInfo(Map> loggerInfos, int width) { + StringBuilder sb = new StringBuilder(8192); + + for (Map.Entry> entry : loggerInfos.entrySet()) { + Map info = entry.getValue(); + + TableElement table = new TableElement(2, 10).leftCellPadding(1).rightCellPadding(1); + TableElement appendersTable = new TableElement().rightCellPadding(1); + + Class clazz = (Class) info.get(LoggerHelper.clazz); + table.row(label(LoggerHelper.name).style(Decoration.bold.bold()), label("" + info.get(LoggerHelper.name))) + .row(label(LoggerHelper.clazz).style(Decoration.bold.bold()), label("" + clazz.getName())) + .row(label(LoggerHelper.classLoader).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.classLoader))) + .row(label(LoggerHelper.classLoaderHash).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.classLoaderHash))) + .row(label(LoggerHelper.level).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.level))); + if (info.get(LoggerHelper.effectiveLevel) != null) { + table.row(label(LoggerHelper.effectiveLevel).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.effectiveLevel))); + } + + if (info.get(LoggerHelper.config) != null) { + table.row(label(LoggerHelper.config).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.config))); + } + + table.row(label(LoggerHelper.additivity).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.additivity))) + .row(label(LoggerHelper.codeSource).style(Decoration.bold.bold()), + label("" + info.get(LoggerHelper.codeSource))); + + @SuppressWarnings("unchecked") + List> appenders = (List>) info.get(LoggerHelper.appenders); + if (appenders != null && !appenders.isEmpty()) { + + for (Map appenderInfo : appenders) { + Class appenderClass = (Class) appenderInfo.get(LoggerHelper.clazz); + + appendersTable.row(label(LoggerHelper.name).style(Decoration.bold.bold()), + label("" + appenderInfo.get(LoggerHelper.name))); + appendersTable.row(label(LoggerHelper.clazz), label("" + appenderClass.getName())); + appendersTable.row(label(LoggerHelper.classLoader), label("" + info.get(LoggerHelper.classLoader))); + appendersTable.row(label(LoggerHelper.classLoaderHash), + label("" + info.get(LoggerHelper.classLoaderHash))); + if (appenderInfo.get(LoggerHelper.file) != null) { + appendersTable.row(label(LoggerHelper.file), label("" + appenderInfo.get(LoggerHelper.file))); + } + if (appenderInfo.get(LoggerHelper.target) != null) { + appendersTable.row(label(LoggerHelper.target), + label("" + appenderInfo.get(LoggerHelper.target))); + } + if (appenderInfo.get(LoggerHelper.blocking) != null) { + appendersTable.row(label(LoggerHelper.blocking), + label("" + appenderInfo.get(LoggerHelper.blocking))); + } + if (appenderInfo.get(LoggerHelper.appenderRef) != null) { + appendersTable.row(label(LoggerHelper.appenderRef), + label("" + appenderInfo.get(LoggerHelper.appenderRef))); + } + } + + table.row(label("appenders").style(Decoration.bold.bold()), appendersTable); + } + + sb.append(RenderUtil.render(table, width)).append('\n'); + } + return sb.toString(); + } +} From 5b7603eebe0becc215a1e45c4125223eaefc2ff0 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Tue, 14 Jul 2020 20:15:03 +0800 Subject: [PATCH 2/5] transform command: dashboard, thread --- .../core/command/model/BlockingLockInfo.java | 45 +++ .../core/command/model/DashboardModel.java | 61 ++++ .../arthas/core/command/model/GcInfoVO.java | 41 +++ .../core/command/model/MemoryEntryVO.java | 70 ++++ .../core/command/model/RuntimeInfoVO.java | 75 ++++ .../core/command/model/ThreadCpuInfo.java | 36 ++ .../core/command/model/ThreadModel.java | 91 +++++ .../arthas/core/command/model/ThreadVO.java | 95 +++++ .../core/command/model/TomcatInfoVO.java | 129 +++++++ .../command/monitor200/DashboardCommand.java | 343 +++++++----------- .../command/monitor200/ThreadCommand.java | 131 +++---- .../command/monitor200/ThreadSampler.java | 111 ++++++ .../core/command/view/DashboardView.java | 225 ++++++++++++ .../arthas/core/command/view/ThreadView.java | 64 ++++ .../core/command/view/ViewRenderUtil.java | 62 ++++ .../taobao/arthas/core/util/ThreadUtil.java | 22 +- 16 files changed, 1300 insertions(+), 301 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/BlockingLockInfo.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/DashboardModel.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/GcInfoVO.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/MemoryEntryVO.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/RuntimeInfoVO.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/ThreadCpuInfo.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/ThreadModel.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/ThreadVO.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/TomcatInfoVO.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadSampler.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/DashboardView.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/ThreadView.java diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/BlockingLockInfo.java b/core/src/main/java/com/taobao/arthas/core/command/model/BlockingLockInfo.java new file mode 100644 index 000000000..6ff187779 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/BlockingLockInfo.java @@ -0,0 +1,45 @@ +package com.taobao.arthas.core.command.model; + +import java.lang.management.ThreadInfo; + +/** + * Thread blocking lock info, extract from ThreadUtil. + * + * @author gongdewei 2020/7/14 + */ +public class BlockingLockInfo { + + // the thread info that is holing this lock. + private ThreadInfo threadInfo = null; + // the associated LockInfo object + private int lockIdentityHashCode = 0; + // the number of thread that is blocked on this lock + private int blockingThreadCount = 0; + + public BlockingLockInfo() { + } + + public ThreadInfo getThreadInfo() { + return threadInfo; + } + + public void setThreadInfo(ThreadInfo threadInfo) { + this.threadInfo = threadInfo; + } + + public int getLockIdentityHashCode() { + return lockIdentityHashCode; + } + + public void setLockIdentityHashCode(int lockIdentityHashCode) { + this.lockIdentityHashCode = lockIdentityHashCode; + } + + public int getBlockingThreadCount() { + return blockingThreadCount; + } + + public void setBlockingThreadCount(int blockingThreadCount) { + this.blockingThreadCount = blockingThreadCount; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/DashboardModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/DashboardModel.java new file mode 100644 index 000000000..ab14ac070 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/DashboardModel.java @@ -0,0 +1,61 @@ +package com.taobao.arthas.core.command.model; + +import java.util.List; +import java.util.Map; + +/** + * Model of 'dashboard' command + * @author gongdewei 2020/4/22 + */ +public class DashboardModel extends ResultModel { + private List threads; + private Map> memoryInfo; + private List gcInfos; + private RuntimeInfoVO runtimeInfo; + private TomcatInfoVO tomcatInfo; + + @Override + public String getType() { + return "dashboard"; + } + + public List getThreads() { + return threads; + } + + public void setThreads(List threads) { + this.threads = threads; + } + + public Map> getMemoryInfo() { + return memoryInfo; + } + + public void setMemoryInfo(Map> memoryInfo) { + this.memoryInfo = memoryInfo; + } + + public List getGcInfos() { + return gcInfos; + } + + public void setGcInfos(List gcInfos) { + this.gcInfos = gcInfos; + } + + public RuntimeInfoVO getRuntimeInfo() { + return runtimeInfo; + } + + public void setRuntimeInfo(RuntimeInfoVO runtimeInfo) { + this.runtimeInfo = runtimeInfo; + } + + public TomcatInfoVO getTomcatInfo() { + return tomcatInfo; + } + + public void setTomcatInfo(TomcatInfoVO tomcatInfo) { + this.tomcatInfo = tomcatInfo; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/GcInfoVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/GcInfoVO.java new file mode 100644 index 000000000..75ab04e44 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/GcInfoVO.java @@ -0,0 +1,41 @@ +package com.taobao.arthas.core.command.model; + +/** + * GC info of dashboard + * @author gongdewei 2020/4/23 + */ +public class GcInfoVO { + private String name; + private long collectionCount; + private long collectionTime; + + public GcInfoVO(String name, long collectionCount, long collectionTime) { + this.name = name; + this.collectionCount = collectionCount; + this.collectionTime = collectionTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getCollectionCount() { + return collectionCount; + } + + public void setCollectionCount(long collectionCount) { + this.collectionCount = collectionCount; + } + + public long getCollectionTime() { + return collectionTime; + } + + public void setCollectionTime(long collectionTime) { + this.collectionTime = collectionTime; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/MemoryEntryVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/MemoryEntryVO.java new file mode 100644 index 000000000..31bdd001b --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/MemoryEntryVO.java @@ -0,0 +1,70 @@ +package com.taobao.arthas.core.command.model; + +/** + * Memory info of 'dashboard' command + * + * @author gongdewei 2020/4/22 + */ +public class MemoryEntryVO { + + public static final String TYPE_HEAP = "heap"; + public static final String TYPE_NON_HEAP = "nonheap"; + public static final String TYPE_BUFFER_POOL = "buffer_pool"; + + private String type; + private String name; + private long used; + private long total; + private long max; + + public MemoryEntryVO() { + } + + public MemoryEntryVO(String type, String name, long used, long total, long max) { + this.type = type; + this.name = name; + this.used = used; + this.total = total; + this.max = max; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getUsed() { + return used; + } + + public void setUsed(long used) { + this.used = used; + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + + public long getMax() { + return max; + } + + public void setMax(long max) { + this.max = max; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/RuntimeInfoVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/RuntimeInfoVO.java new file mode 100644 index 000000000..6b35f0f74 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/RuntimeInfoVO.java @@ -0,0 +1,75 @@ +package com.taobao.arthas.core.command.model; + +/** + * Dashboard - Runtime + * + * @author gongdewei 2020/4/22 + */ +public class RuntimeInfoVO { + private String osName; + private String osVersion; + private String javaVersion; + private String javaHome; + private double systemLoadAverage; + private int processors; + private long uptime; + + public RuntimeInfoVO() { + } + + public String getOsName() { + return osName; + } + + public void setOsName(String osName) { + this.osName = osName; + } + + public String getOsVersion() { + return osVersion; + } + + public void setOsVersion(String osVersion) { + this.osVersion = osVersion; + } + + public String getJavaVersion() { + return javaVersion; + } + + public void setJavaVersion(String javaVersion) { + this.javaVersion = javaVersion; + } + + public String getJavaHome() { + return javaHome; + } + + public void setJavaHome(String javaHome) { + this.javaHome = javaHome; + } + + public double getSystemLoadAverage() { + return systemLoadAverage; + } + + public void setSystemLoadAverage(double systemLoadAverage) { + this.systemLoadAverage = systemLoadAverage; + } + + public int getProcessors() { + return processors; + } + + public void setProcessors(int processors) { + this.processors = processors; + } + + public long getUptime() { + return uptime; + } + + public void setUptime(long uptime) { + this.uptime = uptime; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ThreadCpuInfo.java b/core/src/main/java/com/taobao/arthas/core/command/model/ThreadCpuInfo.java new file mode 100644 index 000000000..83c45c3f2 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/ThreadCpuInfo.java @@ -0,0 +1,36 @@ +package com.taobao.arthas.core.command.model; + +import java.lang.management.ThreadInfo; + +/** + * ThreadInfo with cpuUsage + * + * @author gongdewei 2020/4/26 + */ +public class ThreadCpuInfo { + + private ThreadInfo threadInfo; + + private long cpuUsage; + + public ThreadCpuInfo(ThreadInfo threadInfo, long cpuUsage) { + this.threadInfo = threadInfo; + this.cpuUsage = cpuUsage; + } + + public long getCpuUsage() { + return cpuUsage; + } + + public void setCpuUsage(long cpuUsage) { + this.cpuUsage = cpuUsage; + } + + public ThreadInfo threadInfo() { + return threadInfo; + } + + public void setThreadInfo(ThreadInfo threadInfo) { + this.threadInfo = threadInfo; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ThreadModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/ThreadModel.java new file mode 100644 index 000000000..18c59bf68 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/ThreadModel.java @@ -0,0 +1,91 @@ +package com.taobao.arthas.core.command.model; + +import java.lang.management.ThreadInfo; +import java.util.List; +import java.util.Map; + +/** + * Model of 'thread' command + * + * @author gongdewei 2020/4/26 + */ +public class ThreadModel extends ResultModel { + + //single thread: thread 12 + private ThreadInfo threadInfo; + + //thread -b + private BlockingLockInfo blockingLockInfo; + + //thread -n 5 + private ThreadCpuInfo[] busyThreads; + + //thread stats + private List threadStats; + private Map threadStateCount; + + public ThreadModel() { + } + + public ThreadModel(ThreadInfo threadInfo) { + this.threadInfo = threadInfo; + } + + public ThreadModel(BlockingLockInfo blockingLockInfo) { + this.blockingLockInfo = blockingLockInfo; + } + + public ThreadModel(ThreadCpuInfo[] busyThreads) { + this.busyThreads = busyThreads; + } + + public ThreadModel(List threadStats, Map threadStateCount) { + this.threadStats = threadStats; + this.threadStateCount = threadStateCount; + } + + @Override + public String getType() { + return "thread"; + } + + public ThreadInfo getThreadInfo() { + return threadInfo; + } + + public void setThreadInfo(ThreadInfo threadInfo) { + this.threadInfo = threadInfo; + } + + public BlockingLockInfo getBlockingLockInfo() { + return blockingLockInfo; + } + + public void setBlockingLockInfo(BlockingLockInfo blockingLockInfo) { + this.blockingLockInfo = blockingLockInfo; + } + + public ThreadCpuInfo[] getBusyThreads() { + return busyThreads; + } + + public void setBusyThreads(ThreadCpuInfo[] busyThreads) { + this.busyThreads = busyThreads; + } + + public List getThreadStats() { + return threadStats; + } + + public void setThreadStats(List threadStats) { + this.threadStats = threadStats; + } + + public Map getThreadStateCount() { + return threadStateCount; + } + + public void setThreadStateCount(Map threadStateCount) { + this.threadStateCount = threadStateCount; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/ThreadVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/ThreadVO.java new file mode 100644 index 000000000..42a7b8db4 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/ThreadVO.java @@ -0,0 +1,95 @@ +package com.taobao.arthas.core.command.model; + +import java.lang.Thread.State; + +/** + * Thread VO of 'dashboard' and 'thread' command + * + * @author gongdewei 2020/4/22 + */ +public class ThreadVO { + private long id; + private String name; + private String group; + private int priority; + private State state; + private long cpu; + private long time; + private boolean interrupted; + private boolean daemon; + + public ThreadVO() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public long getCpu() { + return cpu; + } + + public void setCpu(long cpu) { + this.cpu = cpu; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + public boolean isInterrupted() { + return interrupted; + } + + public void setInterrupted(boolean interrupted) { + this.interrupted = interrupted; + } + + public boolean isDaemon() { + return daemon; + } + + public void setDaemon(boolean daemon) { + this.daemon = daemon; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/TomcatInfoVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/TomcatInfoVO.java new file mode 100644 index 000000000..83943f168 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/TomcatInfoVO.java @@ -0,0 +1,129 @@ +package com.taobao.arthas.core.command.model; + +import java.util.List; + +/** + * Tomcat info of 'dashboard' command + * + * @author gongdewei 2020/4/23 + */ +public class TomcatInfoVO { + + private List connectorStats; + private List threadPools; + + public TomcatInfoVO() { + } + + public List getConnectorStats() { + return connectorStats; + } + + public void setConnectorStats(List connectorStats) { + this.connectorStats = connectorStats; + } + + public List getThreadPools() { + return threadPools; + } + + public void setThreadPools(List threadPools) { + this.threadPools = threadPools; + } + + public static class ConnectorStats { + private String name; + private double qps; + private double rt; + private double error; + private long received; + private long sent; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getQps() { + return qps; + } + + public void setQps(double qps) { + this.qps = qps; + } + + public double getRt() { + return rt; + } + + public void setRt(double rt) { + this.rt = rt; + } + + public double getError() { + return error; + } + + public void setError(double error) { + this.error = error; + } + + public long getReceived() { + return received; + } + + public void setReceived(long received) { + this.received = received; + } + + public long getSent() { + return sent; + } + + public void setSent(long sent) { + this.sent = sent; + } + } + + public static class ThreadPool { + private String name; + private long busy; + private long total; + + public ThreadPool() { + } + + public ThreadPool(String name, long busy, long total) { + this.name = name; + this.busy = busy; + this.total = total; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getBusy() { + return busy; + } + + public void setBusy(long busy) { + this.busy = busy; + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/DashboardCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/DashboardCommand.java index a6f331397..796d7bec2 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/DashboardCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/DashboardCommand.java @@ -5,6 +5,11 @@ import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.model.DashboardModel; +import com.taobao.arthas.core.command.model.GcInfoVO; +import com.taobao.arthas.core.command.model.MemoryEntryVO; +import com.taobao.arthas.core.command.model.RuntimeInfoVO; +import com.taobao.arthas.core.command.model.TomcatInfoVO; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.shell.handlers.Handler; @@ -18,13 +23,6 @@ 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.text.Color; -import com.taobao.text.Decoration; -import com.taobao.text.Style; -import com.taobao.text.renderers.ThreadRenderer; -import com.taobao.text.ui.RowElement; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; import java.lang.management.BufferPoolMXBean; import java.lang.management.GarbageCollectorMXBean; @@ -32,11 +30,17 @@ import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; +import static com.taobao.arthas.core.command.model.MemoryEntryVO.TYPE_BUFFER_POOL; +import static com.taobao.arthas.core.command.model.MemoryEntryVO.TYPE_HEAP; +import static com.taobao.arthas.core.command.model.MemoryEntryVO.TYPE_NON_HEAP; + /** * @author hengyunabc 2015年11月19日 上午11:57:21 */ @@ -140,8 +144,43 @@ public class DashboardCommand extends AnnotatedCommand { return name.replace(' ', '_').toLowerCase(); } - private static void addBufferPoolMemoryInfo(TableElement table) { + private static void addMemoryInfo(DashboardModel dashboardModel) { + List memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans(); + Map> memoryInfoMap = new LinkedHashMap>(); + dashboardModel.setMemoryInfo(memoryInfoMap); + + //heap + MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); + List heapMemEntries = new ArrayList(); + heapMemEntries.add(createMemoryEntryVO(TYPE_HEAP, TYPE_HEAP, heapMemoryUsage)); + for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) { + if (MemoryType.HEAP.equals(poolMXBean.getType())) { + MemoryUsage usage = poolMXBean.getUsage(); + String poolName = beautifyName(poolMXBean.getName()); + heapMemEntries.add(createMemoryEntryVO(TYPE_HEAP, poolName, usage)); + } + } + memoryInfoMap.put(TYPE_HEAP, heapMemEntries); + + //non-heap + MemoryUsage nonHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage(); + List nonheapMemEntries = new ArrayList(); + nonheapMemEntries.add(createMemoryEntryVO(TYPE_NON_HEAP, TYPE_NON_HEAP, nonHeapMemoryUsage)); + for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) { + if (MemoryType.NON_HEAP.equals(poolMXBean.getType())) { + MemoryUsage usage = poolMXBean.getUsage(); + String poolName = beautifyName(poolMXBean.getName()); + nonheapMemEntries.add(createMemoryEntryVO(TYPE_NON_HEAP, poolName, usage)); + } + } + memoryInfoMap.put(TYPE_NON_HEAP, nonheapMemEntries); + + addBufferPoolMemoryInfo(memoryInfoMap); + } + + private static void addBufferPoolMemoryInfo(Map> memoryInfoMap) { try { + List bufferPoolMemEntries = new ArrayList(); @SuppressWarnings("rawtypes") Class bufferPoolMXBeanClass = Class.forName("java.lang.management.BufferPoolMXBean"); @SuppressWarnings("unchecked") @@ -149,84 +188,57 @@ public class DashboardCommand extends AnnotatedCommand { for (BufferPoolMXBean mbean : bufferPoolMXBeans) { long used = mbean.getMemoryUsed(); long total = mbean.getTotalCapacity(); - new MemoryEntry(mbean.getName(), used, total, Long.MIN_VALUE).addTableRow(table); + bufferPoolMemEntries.add(new MemoryEntryVO(TYPE_BUFFER_POOL, mbean.getName(), used, total, Long.MIN_VALUE)); } + memoryInfoMap.put(TYPE_BUFFER_POOL, bufferPoolMemEntries); } catch (ClassNotFoundException e) { // ignore } } - private static void addRuntimeInfo(TableElement table) { - table.row("os.name", System.getProperty("os.name")); - table.row("os.version", System.getProperty("os.version")); - table.row("java.version", System.getProperty("java.version")); - table.row("java.home", System.getProperty("java.home")); - table.row("systemload.average", - String.format("%.2f", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage())); - table.row("processors", "" + Runtime.getRuntime().availableProcessors()); - table.row("uptime", "" + ManagementFactory.getRuntimeMXBean().getUptime() / 1000 + "s"); + private static void addRuntimeInfo(DashboardModel dashboardModel) { + RuntimeInfoVO runtimeInfo = new RuntimeInfoVO(); + runtimeInfo.setOsName(System.getProperty("os.name")); + runtimeInfo.setOsVersion(System.getProperty("os.version")); + runtimeInfo.setJavaVersion(System.getProperty("java.version")); + runtimeInfo.setJavaHome(System.getProperty("java.home")); + runtimeInfo.setSystemLoadAverage(ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage()); + runtimeInfo.setProcessors(Runtime.getRuntime().availableProcessors()); + runtimeInfo.setUptime(ManagementFactory.getRuntimeMXBean().getUptime() / 1000); + dashboardModel.setRuntimeInfo(runtimeInfo); } - private static void addMemoryInfo(TableElement table) { - MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); - MemoryUsage nonHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage(); - - List memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans(); - - new MemoryEntry("heap", heapMemoryUsage).addTableRow(table, Decoration.bold.bold()); - for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) { - if (MemoryType.HEAP.equals(poolMXBean.getType())) { - MemoryUsage usage = poolMXBean.getUsage(); - String poolName = beautifyName(poolMXBean.getName()); - new MemoryEntry(poolName, usage).addTableRow(table); - } - } - - new MemoryEntry("nonheap", nonHeapMemoryUsage).addTableRow(table, Decoration.bold.bold()); - for (MemoryPoolMXBean poolMXBean : memoryPoolMXBeans) { - if (MemoryType.NON_HEAP.equals(poolMXBean.getType())) { - MemoryUsage usage = poolMXBean.getUsage(); - String poolName = beautifyName(poolMXBean.getName()); - new MemoryEntry(poolName, usage).addTableRow(table); - } - } - - addBufferPoolMemoryInfo(table); + private static MemoryEntryVO createMemoryEntryVO(String type, String name, MemoryUsage memoryUsage) { + return new MemoryEntryVO(type, name, memoryUsage.getUsed(), memoryUsage.getCommitted(), memoryUsage.getMax()); } - private static void addGcInfo(TableElement table) { + private static void addGcInfo(DashboardModel dashboardModel) { + List gcInfos = new ArrayList(); + dashboardModel.setGcInfos(gcInfos); + List garbageCollectorMxBeans = ManagementFactory.getGarbageCollectorMXBeans(); - for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMxBeans) { - String name = garbageCollectorMXBean.getName(); - table.add(new RowElement().style(Decoration.bold.bold()).add("gc." + beautifyName(name) + ".count", - "" + garbageCollectorMXBean.getCollectionCount())); - table.row("gc." + beautifyName(name) + ".time(ms)", "" + garbageCollectorMXBean.getCollectionTime()); + for (GarbageCollectorMXBean gcMXBean : garbageCollectorMxBeans) { + String name = gcMXBean.getName(); + gcInfos.add(new GcInfoVO(beautifyName(name), gcMXBean.getCollectionCount(), gcMXBean.getCollectionTime())); } } - private static String formatBytes(long size) { - int unit = 1; - String unitStr = "B"; - if (size / 1024 > 0) { - unit = 1024; - unitStr = "K"; - } else if (size / 1024 / 1024 > 0) { - unit = 1024 * 1024; - unitStr = "M"; + private void addTomcatInfo(DashboardModel dashboardModel) { + // 如果请求tomcat信息失败,则不显示tomcat信息 + if (!NetUtils.request("http://localhost:8006").isSuccess()) { + return; } - return String.format("%d%s", size / unit, unitStr); - } - - private void addTomcatInfo(TableElement table) { - + TomcatInfoVO tomcatInfoVO = new TomcatInfoVO(); + dashboardModel.setTomcatInfo(tomcatInfoVO); String threadPoolPath = "http://localhost:8006/connector/threadpool"; String connectorStatPath = "http://localhost:8006/connector/stats"; Response connectorStatResponse = NetUtils.request(connectorStatPath); if (connectorStatResponse.isSuccess()) { - List connectorStats = JSON.parseArray(connectorStatResponse.getContent(), JSONObject.class); - for (JSONObject stat : connectorStats) { - String name = stat.getString("name").replace("\"", ""); + List connectorStats = new ArrayList(); + List tomcatConnectorStats = JSON.parseArray(connectorStatResponse.getContent(), JSONObject.class); + for (JSONObject stat : tomcatConnectorStats) { + String connectorName = stat.getString("name").replace("\"", ""); long bytesReceived = stat.getLongValue("bytesReceived"); long bytesSent = stat.getLongValue("bytesSent"); long processingTime = stat.getLongValue("processingTime"); @@ -238,173 +250,86 @@ public class DashboardCommand extends AnnotatedCommand { tomcatReceivedBytesCounter.update(bytesReceived); tomcatSentBytesCounter.update(bytesSent); - table.add(new RowElement().style(Decoration.bold.bold()).add("connector", name)); - table.row("QPS", String.format("%.2f", tomcatRequestCounter.rate())); - table.row("RT(ms)", String.format("%.2f", processingTime / (double) requestCount)); - table.row("error/s", String.format("%.2f", tomcatErrorCounter.rate())); - table.row("received/s", formatBytes((long) tomcatReceivedBytesCounter.rate())); - table.row("sent/s", formatBytes((long) tomcatSentBytesCounter.rate())); + double qps = tomcatRequestCounter.rate(); + double rt = processingTime / (double) requestCount; + double errorRate = tomcatErrorCounter.rate(); + long receivedBytesRate = new Double(tomcatReceivedBytesCounter.rate()).longValue(); + long sentBytesRate = new Double(tomcatSentBytesCounter.rate()).longValue(); + + TomcatInfoVO.ConnectorStats connectorStat = new TomcatInfoVO.ConnectorStats(); + connectorStat.setName(connectorName); + connectorStat.setQps(qps); + connectorStat.setRt(rt); + connectorStat.setError(errorRate); + connectorStat.setReceived(receivedBytesRate); + connectorStat.setSent(sentBytesRate); + connectorStats.add(connectorStat); } + tomcatInfoVO.setConnectorStats(connectorStats); } Response threadPoolResponse = NetUtils.request(threadPoolPath); if (threadPoolResponse.isSuccess()) { + List threadPools = new ArrayList(); List threadPoolInfos = JSON.parseArray(threadPoolResponse.getContent(), JSONObject.class); for (JSONObject info : threadPoolInfos) { String name = info.getString("name").replace("\"", ""); long busy = info.getLongValue("threadBusy"); long total = info.getLongValue("threadCount"); - table.add(new RowElement().style(Decoration.bold.bold()).add("threadpool", name)); - table.row("busy", "" + busy); - table.row("total", "" + total); + threadPools.add(new TomcatInfoVO.ThreadPool(name, busy, total)); } - } - } - - static String drawThreadInfo(int width, int height) { - Map threads = ThreadUtil.getThreads(); - return RenderUtil.render(threads.values().iterator(), new ThreadRenderer(), width, height); - } - - static String drawMemoryInfoAndGcInfo(int width, int height) { - TableElement table = new TableElement(1, 1); - - TableElement memoryInfoTable = new TableElement(3, 1, 1, 1, 1).rightCellPadding(1); - memoryInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Memory", - "used", "total", "max", "usage")); - - addMemoryInfo(memoryInfoTable); - - TableElement gcInfoTable = new TableElement(1, 1).rightCellPadding(1); - gcInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("GC", "")); - addGcInfo(gcInfoTable); - - table.row(memoryInfoTable, gcInfoTable); - return RenderUtil.render(table, width, height); - } - - String drawRuntimeInfoAndTomcatInfo(int width, int height) { - TableElement resultTable = new TableElement(1, 1); - - TableElement runtimeInfoTable = new TableElement(1, 1).rightCellPadding(1); - runtimeInfoTable - .add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Runtime", "")); - - addRuntimeInfo(runtimeInfoTable); - - TableElement tomcatInfoTable = null; - - try { - // 如果请求tomcat信息失败,则不显示tomcat信息 - if (NetUtils.request("http://localhost:8006").isSuccess()) { - tomcatInfoTable = new TableElement(1, 1).rightCellPadding(1); - tomcatInfoTable - .add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Tomcat", "")); - addTomcatInfo(tomcatInfoTable); - } - } catch (Throwable t) { - logger.error("get Tomcat Info error!", t); - } - - if (tomcatInfoTable != null) { - resultTable.row(runtimeInfoTable, tomcatInfoTable); - } else { - resultTable = runtimeInfoTable; - } - - return RenderUtil.render(resultTable, width, height); - } - - static class MemoryEntry { - String name; - long used; - long total; - long max; - - int unit; - String unitStr; - - public MemoryEntry(String name, long used, long total, long max) { - this.name = name; - this.used = used; - this.total = total; - this.max = max; - - unitStr = "K"; - unit = 1024; - if (used / 1024 / 1024 > 0) { - unitStr = "M"; - unit = 1024 * 1024; - } - } - - public MemoryEntry(String name, MemoryUsage usage) { - this(name, usage.getUsed(), usage.getCommitted(), usage.getMax()); - } - - private String format(long value) { - String valueStr = "-"; - if (value == -1) { - return "-1"; - } - if (value != Long.MIN_VALUE) { - valueStr = value / unit + unitStr; - } - return valueStr; - } - - public void addTableRow(TableElement table) { - double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100; - - table.row(name, format(used), format(total), format(max), String.format("%.2f%%", usage)); - } - - public void addTableRow(TableElement table, Style.Composite style) { - double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100; - - table.add(new RowElement().style(style).add(name, format(used), format(total), format(max), - String.format("%.2f%%", usage))); + tomcatInfoVO.setThreadPools(threadPools); } } private class DashboardTimerTask extends TimerTask { private CommandProcess process; + private ThreadSampler threadSampler; public DashboardTimerTask(CommandProcess process) { this.process = process; + this.threadSampler = new ThreadSampler(); } @Override public void run() { - if (count >= getNumOfExecutions()) { - // stop the timer - timer.cancel(); - timer.purge(); - process.write("Process ends after " + getNumOfExecutions() + " time(s).\n"); - process.end(); - return; + try { + if (count >= getNumOfExecutions()) { + // stop the timer + timer.cancel(); + timer.purge(); + process.end(0, "Process ends after " + getNumOfExecutions() + " time(s)."); + return; + } + + DashboardModel dashboardModel = new DashboardModel(); + + //thread sample + Map threads = ThreadUtil.getThreads(); + dashboardModel.setThreads(threadSampler.sample(threads.values())); + + //memory + addMemoryInfo(dashboardModel); + + //gc + addGcInfo(dashboardModel); + + //runtime + addRuntimeInfo(dashboardModel); + + //tomcat + addTomcatInfo(dashboardModel); + + process.appendResult(dashboardModel); + + count++; + process.times().incrementAndGet(); + } catch (Throwable e) { + String msg = "process dashboard failed: " + e.getMessage(); + logger.error(msg, e); + process.end(-1, msg); } - - int width = process.width(); - int height = process.height(); - - // 上半部分放thread top。下半部分再切分为田字格,其中上面两格放memory, gc的信息。下面两格放tomcat, - // runtime的信息 - int totalHeight = height - 1; - int threadTopHeight = totalHeight / 2; - int lowerHalf = totalHeight - threadTopHeight; - - int runtimeInfoHeight = lowerHalf / 2; - int heapInfoHeight = lowerHalf - runtimeInfoHeight; - - String threadInfo = drawThreadInfo(width, threadTopHeight); - String memoryAndGc = drawMemoryInfoAndGcInfo(width, runtimeInfoHeight); - String runTimeAndTomcat = drawRuntimeInfoAndTomcatInfo(width, heapInfoHeight); - - process.write(threadInfo + memoryAndGc + runTimeAndTomcat); - - count++; - process.times().incrementAndGet(); } } + } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadCommand.java index eb8a5f44b..2d02ac1b1 100755 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadCommand.java @@ -1,21 +1,22 @@ package com.taobao.arthas.core.command.monitor200; import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.model.BlockingLockInfo; +import com.taobao.arthas.core.command.model.ThreadCpuInfo; +import com.taobao.arthas.core.command.model.ThreadModel; +import com.taobao.arthas.core.command.model.ThreadVO; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.shell.command.ExitStatus; import com.taobao.arthas.core.util.ArrayUtils; +import com.taobao.arthas.core.util.CommandUtils; import com.taobao.arthas.core.util.StringUtils; import com.taobao.arthas.core.util.ThreadUtil; -import com.taobao.arthas.core.util.affect.Affect; -import com.taobao.arthas.core.util.affect.RowAffect; import com.taobao.middleware.cli.annotations.Argument; 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.text.renderers.ThreadRenderer; -import com.taobao.text.ui.LabelElement; -import com.taobao.text.util.RenderUtil; import java.lang.Thread.State; import java.lang.management.ManagementFactory; @@ -23,8 +24,9 @@ import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -106,31 +108,24 @@ public class ThreadCommand extends AnnotatedCommand { @Override public void process(CommandProcess process) { - Affect affect = new RowAffect(); - int status = 0; - try { - if (id > 0) { - status = processThread(process); - } else if (topNBusy != null) { - status = processTopBusyThreads(process); - } else if (findMostBlockingThread) { - status = processBlockingThread(process); - } else { - status = processAllThreads(process); - } - } finally { - process.write(affect + "\n"); - process.end(status); + ExitStatus exitStatus; + if (id > 0) { + exitStatus = processThread(process); + } else if (topNBusy != null) { + exitStatus = processTopBusyThreads(process); + } else if (findMostBlockingThread) { + exitStatus = processBlockingThread(process); + } else { + exitStatus = processAllThreads(process); } + CommandUtils.end(process, exitStatus); } - private int processAllThreads(CommandProcess process) { - int status = 0; + private ExitStatus processAllThreads(CommandProcess process) { Map threads = ThreadUtil.getThreads(); // 统计各种线程状态 - StringBuilder threadStat = new StringBuilder(); - Map stateCountMap = new HashMap(); + Map stateCountMap = new LinkedHashMap(); for (State s : State.values()) { stateCountMap.put(s, 0); } @@ -141,81 +136,65 @@ public class ThreadCommand extends AnnotatedCommand { stateCountMap.put(threadState, count + 1); } - threadStat.append("Threads Total: ").append(threads.values().size()); - - for (State s : State.values()) { - Integer count = stateCountMap.get(s); - threadStat.append(", ").append(s.name()).append(": ").append(count); - } - - String stat = RenderUtil.render(new LabelElement(threadStat), process.width()); - Collection resultThreads = new ArrayList(); - if (!StringUtils.isEmpty(this.state)){ + if (!StringUtils.isEmpty(this.state)) { this.state = this.state.toUpperCase(); - if(states.contains(this.state)) { + if (states.contains(this.state)) { for (Thread thread : threads.values()) { if (state.equals(thread.getState().name())) { resultThreads.add(thread); } } - }else{ - process.write("Illegal argument, state should be one of " + states + "\n"); - status = 1; - return status; + } else { + return ExitStatus.failure(1, "Illegal argument, state should be one of " + states); } } else { resultThreads = threads.values(); } - String content = RenderUtil.render(resultThreads.iterator(), - new ThreadRenderer(sampleInterval), process.width()); - process.write(stat + content); - return status; + + //thread stats + ThreadSampler threadSampler = new ThreadSampler(); + threadSampler.setSampleInterval(sampleInterval); + List threadStats = threadSampler.sample(resultThreads); + + process.appendResult(new ThreadModel(threadStats, stateCountMap)); + return ExitStatus.success(); } - private int processBlockingThread(CommandProcess process) { - int status = 0; - ThreadUtil.BlockingLockInfo blockingLockInfo = ThreadUtil.findMostBlockingLock(); - - if (blockingLockInfo.threadInfo == null) { - process.write("No most blocking thread found!\n"); - status = 1; - } else { - String stacktrace = ThreadUtil.getFullStacktrace(blockingLockInfo); - process.write(stacktrace); + private ExitStatus processBlockingThread(CommandProcess process) { + BlockingLockInfo blockingLockInfo = ThreadUtil.findMostBlockingLock(); + if (blockingLockInfo.getThreadInfo() == null) { + return ExitStatus.failure(1, "No most blocking thread found!"); } - return status; + process.appendResult(new ThreadModel(blockingLockInfo)); + return ExitStatus.success(); } - private int processTopBusyThreads(CommandProcess process) { - int status = 0; + private ExitStatus processTopBusyThreads(CommandProcess process) { Map topNThreads = ThreadUtil.getTopNThreads(sampleInterval, topNBusy); Long[] tids = topNThreads.keySet().toArray(new Long[0]); ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(ArrayUtils.toPrimitive(tids), lockedMonitors, lockedSynchronizers); if (threadInfos == null) { - process.write("thread do not exist! id: " + id + "\n"); - status = 1; - } else { - for (ThreadInfo info : threadInfos) { - String stacktrace = ThreadUtil.getFullStacktrace(info, topNThreads.get(info.getThreadId())); - process.write(stacktrace + "\n"); - } + return ExitStatus.failure(1, "get top busy threads failed"); } - return status; + + //threadInfo with cpuUsage + ThreadCpuInfo[] threadCpuInfos = new ThreadCpuInfo[threadInfos.length]; + for (int i = 0; i < threadInfos.length; i++) { + ThreadInfo threadInfo = threadInfos[i]; + threadCpuInfos[i] = new ThreadCpuInfo(threadInfo, topNThreads.get(threadInfo.getThreadId())); + } + process.appendResult(new ThreadModel(threadCpuInfos)); + return ExitStatus.success(); } - private int processThread(CommandProcess process) { - int status = 0; - String content; + private ExitStatus processThread(CommandProcess process) { ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(new long[]{id}, lockedMonitors, lockedSynchronizers); - if (threadInfos == null || threadInfos[0] == null) { - content = "thread do not exist! id: " + id + "\n"; - status = 1; - } else { - // no cpu usage info - content = ThreadUtil.getFullStacktrace(threadInfos[0], -1); + if (threadInfos == null || threadInfos.length < 1 || threadInfos[0] == null) { + return ExitStatus.failure(1, "thread do not exist! id: " + id); } - process.write(content); - return status; + + process.appendResult(new ThreadModel(threadInfos[0])); + return ExitStatus.success(); } } \ No newline at end of file diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadSampler.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadSampler.java new file mode 100644 index 000000000..3fadf6f75 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadSampler.java @@ -0,0 +1,111 @@ +package com.taobao.arthas.core.command.monitor200; + +import com.taobao.arthas.core.command.model.ThreadVO; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.util.*; + +/** + * Thread cpu sampler + * + * @author gongdewei 2020/4/23 + */ +public class ThreadSampler { + + private long sampleInterval = 100; + + public List sample(Collection originThreads) { + + List threads = new ArrayList(originThreads); + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + + // Sample CPU + Map times1 = new HashMap(); + for (Thread thread : threads) { + long cpu = threadMXBean.getThreadCpuTime(thread.getId()); + times1.put(thread.getId(), cpu); + } + + try { + Thread.sleep(sampleInterval); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + // Resample + Map times2 = new HashMap(threads.size()); + for (Thread thread : threads) { + long cpu = threadMXBean.getThreadCpuTime(thread.getId()); + times2.put(thread.getId(), cpu); + } + + // Compute delta map and total time + long total = 0; + Map deltas = new HashMap(threads.size()); + for (Long id : times2.keySet()) { + long time1 = times2.get(id); + long time2 = times1.get(id); + if (time1 == -1) { + time1 = time2; + } else if (time2 == -1) { + time2 = time1; + } + long delta = time2 - time1; + deltas.put(id, delta); + total += delta; + } + + // Compute cpu + final HashMap cpus = new HashMap(threads.size()); + for (Thread thread : threads) { + long cpu = total == 0 ? 0 : Math.round((deltas.get(thread.getId()) * 100) / total); + cpus.put(thread, cpu); + } + + // Sort by CPU time : should be a rendering hint... + Collections.sort(threads, new Comparator() { + public int compare(Thread o1, Thread o2) { + long l1 = cpus.get(o1); + long l2 = cpus.get(o2); + if (l1 < l2) { + return 1; + } else if (l1 > l2) { + return -1; + } else { + return 0; + } + } + }); + + List threadVOList = new ArrayList(threads.size()); + for (Thread thread : threads) { + ThreadGroup group = thread.getThreadGroup(); + long seconds = times2.get(thread.getId()) / 1000000000; + //long min = seconds / 60; + //String time = min + ":" + (seconds % 60); + long cpu = cpus.get(thread); + + ThreadVO threadVO = new ThreadVO(); + threadVO.setId(thread.getId()); + threadVO.setName(thread.getName()); + threadVO.setGroup(group == null ? "" : group.getName()); + threadVO.setPriority(thread.getPriority()); + threadVO.setState(thread.getState()); + threadVO.setCpu(cpu); + threadVO.setTime(seconds); + threadVO.setInterrupted(thread.isInterrupted()); + threadVO.setDaemon(thread.isDaemon()); + threadVOList.add(threadVO); + } + return threadVOList; + } + + public long getSampleInterval() { + return sampleInterval; + } + + public void setSampleInterval(long sampleInterval) { + this.sampleInterval = sampleInterval; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/DashboardView.java b/core/src/main/java/com/taobao/arthas/core/command/view/DashboardView.java new file mode 100644 index 000000000..684115cc6 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/DashboardView.java @@ -0,0 +1,225 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.*; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.text.Color; +import com.taobao.text.Decoration; +import com.taobao.text.Style; +import com.taobao.text.ui.RowElement; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + +import java.lang.management.MemoryUsage; +import java.util.List; +import java.util.Map; + +/** + * View of 'dashboard' command + * + * @author gongdewei 2020/4/22 + */ +public class DashboardView extends ResultView { + + @Override + public void draw(CommandProcess process, DashboardModel result) { + int width = process.width(); + int height = process.height(); + + // 上半部分放thread top。下半部分再切分为田字格,其中上面两格放memory, gc的信息。下面两格放tomcat, + // runtime的信息 + int totalHeight = height - 1; + int threadTopHeight = totalHeight / 2; + int lowerHalf = totalHeight - threadTopHeight; + + int runtimeInfoHeight = lowerHalf / 2; + int heapInfoHeight = lowerHalf - runtimeInfoHeight; + + String threadInfo = ViewRenderUtil.drawThreadInfo(result.getThreads(), width, threadTopHeight); + String memoryAndGc = drawMemoryInfoAndGcInfo(result.getMemoryInfo(), result.getGcInfos(), width, runtimeInfoHeight); + String runTimeAndTomcat = drawRuntimeInfoAndTomcatInfo(result.getRuntimeInfo(), result.getTomcatInfo(), width, heapInfoHeight); + + process.write(threadInfo + memoryAndGc + runTimeAndTomcat); + } + + static String drawMemoryInfoAndGcInfo(Map> memoryInfo, List gcInfos, int width, int height) { + TableElement table = new TableElement(1, 1); + + TableElement memoryInfoTable = new TableElement(3, 1, 1, 1, 1).rightCellPadding(1); + memoryInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Memory", + "used", "total", "max", "usage")); + + drawMemoryInfo(memoryInfoTable, memoryInfo); + + TableElement gcInfoTable = new TableElement(1, 1).rightCellPadding(1); + gcInfoTable.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("GC", "")); + drawGcInfo(gcInfoTable, gcInfos); + + table.row(memoryInfoTable, gcInfoTable); + return RenderUtil.render(table, width, height); + } + + private static void drawMemoryInfo(TableElement table, Map> memoryInfo) { + List heapMemoryEntries = memoryInfo.get(MemoryEntryVO.TYPE_HEAP); + //heap memory + for (MemoryEntryVO memoryEntryVO : heapMemoryEntries) { + if (MemoryEntryVO.TYPE_HEAP.equals(memoryEntryVO.getName())) { + new MemoryEntry(memoryEntryVO).addTableRow(table, Decoration.bold.bold()); + } else { + new MemoryEntry(memoryEntryVO).addTableRow(table); + } + } + + //non-heap memory + List nonheapMemoryEntries = memoryInfo.get(MemoryEntryVO.TYPE_NON_HEAP); + for (MemoryEntryVO memoryEntryVO : nonheapMemoryEntries) { + if (MemoryEntryVO.TYPE_NON_HEAP.equals(memoryEntryVO.getName())) { + new MemoryEntry(memoryEntryVO).addTableRow(table, Decoration.bold.bold()); + } else { + new MemoryEntry(memoryEntryVO).addTableRow(table); + } + } + + //buffer-pool + List bufferPoolMemoryEntries = memoryInfo.get(MemoryEntryVO.TYPE_BUFFER_POOL); + for (MemoryEntryVO memoryEntryVO : bufferPoolMemoryEntries) { + new MemoryEntry(memoryEntryVO).addTableRow(table); + } + } + + private static void drawGcInfo(TableElement table, List gcInfos) { + for (GcInfoVO gcInfo : gcInfos) { + table.add(new RowElement().style(Decoration.bold.bold()).add("gc." + gcInfo.getName() + ".count", + "" + gcInfo.getCollectionCount())); + table.row("gc." + gcInfo.getName() + ".time(ms)", "" + gcInfo.getCollectionTime()); + } + } + + String drawRuntimeInfoAndTomcatInfo(RuntimeInfoVO runtimeInfo, TomcatInfoVO tomcatInfo, int width, int height) { + TableElement resultTable = new TableElement(1, 1); + //runtime + TableElement runtimeInfoTable = drawRuntimeInfo(runtimeInfo); + //tomcat + TableElement tomcatInfoTable = drawTomcatInfo(tomcatInfo); + + if (tomcatInfoTable != null) { + resultTable.row(runtimeInfoTable, tomcatInfoTable); + } else { + resultTable = runtimeInfoTable; + } + return RenderUtil.render(resultTable, width, height); + } + + private static TableElement drawRuntimeInfo(RuntimeInfoVO runtimeInfo) { + TableElement table = new TableElement(1, 1).rightCellPadding(1); + table.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Runtime", "")); + + table.row("os.name", runtimeInfo.getOsName()); + table.row("os.version", runtimeInfo.getOsVersion()); + table.row("java.version", runtimeInfo.getJavaVersion()); + table.row("java.home", runtimeInfo.getJavaHome()); + table.row("systemload.average", String.format("%.2f", runtimeInfo.getSystemLoadAverage())); + table.row("processors", "" + runtimeInfo.getProcessors()); + table.row("uptime", "" + runtimeInfo.getUptime() + "s"); + return table; + } + + private static String formatBytes(long size) { + int unit = 1; + String unitStr = "B"; + if (size / 1024 > 0) { + unit = 1024; + unitStr = "K"; + } else if (size / 1024 / 1024 > 0) { + unit = 1024 * 1024; + unitStr = "M"; + } + + return String.format("%d%s", size / unit, unitStr); + } + + private TableElement drawTomcatInfo(TomcatInfoVO tomcatInfo) { + if (tomcatInfo == null) { + return null; + } + + //header + TableElement table = new TableElement(1, 1).rightCellPadding(1); + table.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Tomcat", "")); + + if (tomcatInfo.getConnectorStats() != null) { + for (TomcatInfoVO.ConnectorStats connectorStat : tomcatInfo.getConnectorStats()) { + table.add(new RowElement().style(Decoration.bold.bold()).add("connector", connectorStat.getName())); + table.row("QPS", String.format("%.2f", connectorStat.getQps())); + table.row("RT(ms)", String.format("%.2f", connectorStat.getRt())); + table.row("error/s", String.format("%.2f", connectorStat.getError())); + table.row("received/s", formatBytes(connectorStat.getReceived())); + table.row("sent/s", formatBytes(connectorStat.getSent())); + } + } + + if (tomcatInfo.getThreadPools() != null) { + for (TomcatInfoVO.ThreadPool threadPool : tomcatInfo.getThreadPools()) { + table.add(new RowElement().style(Decoration.bold.bold()).add("threadpool", threadPool.getName())); + table.row("busy", "" + threadPool.getBusy()); + table.row("total", "" + threadPool.getTotal()); + } + } + return table; + } + + static class MemoryEntry { + String name; + long used; + long total; + long max; + + int unit; + String unitStr; + + public MemoryEntry(String name, long used, long total, long max) { + this.name = name; + this.used = used; + this.total = total; + this.max = max; + + unitStr = "K"; + unit = 1024; + if (used / 1024 / 1024 > 0) { + unitStr = "M"; + unit = 1024 * 1024; + } + } + + public MemoryEntry(String name, MemoryUsage usage) { + this(name, usage.getUsed(), usage.getCommitted(), usage.getMax()); + } + + public MemoryEntry(MemoryEntryVO memoryEntryVO) { + this(memoryEntryVO.getName(), memoryEntryVO.getUsed(), memoryEntryVO.getTotal(), memoryEntryVO.getMax()); + } + + private String format(long value) { + String valueStr = "-"; + if (value == -1) { + return "-1"; + } + if (value != Long.MIN_VALUE) { + valueStr = value / unit + unitStr; + } + return valueStr; + } + + public void addTableRow(TableElement table) { + double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100; + + table.row(name, format(used), format(total), format(max), String.format("%.2f%%", usage)); + } + + public void addTableRow(TableElement table, Style.Composite style) { + double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100; + + table.add(new RowElement().style(style).add(name, format(used), format(total), format(max), + String.format("%.2f%%", usage))); + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ThreadView.java b/core/src/main/java/com/taobao/arthas/core/command/view/ThreadView.java new file mode 100644 index 000000000..ca18e8322 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ThreadView.java @@ -0,0 +1,64 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.ThreadCpuInfo; +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 { + + @Override + public void draw(CommandProcess process, ThreadModel result) { + if (result.getThreadInfo() != null) { + // no cpu usage info + String content = ThreadUtil.getFullStacktrace(result.getThreadInfo(), -1); + process.write(content); + } else if (result.getBusyThreads() != null) { + ThreadCpuInfo[] threadInfos = result.getBusyThreads(); + for (ThreadCpuInfo info : threadInfos) { + String stacktrace = ThreadUtil.getFullStacktrace(info.threadInfo(), info.getCpuUsage()); + 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 threadStateCount = result.getThreadStateCount(); + List threadStats = result.getThreadStats(); + + //sum total thread count + int total = 0; + for (Integer value : threadStateCount.values()) { + total += value; + } + 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); + } + String stat = RenderUtil.render(new LabelElement(threadStat), process.width()); + + //thread stats + int 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); + } + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ViewRenderUtil.java b/core/src/main/java/com/taobao/arthas/core/command/view/ViewRenderUtil.java index 4676d50c2..4e7201576 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/view/ViewRenderUtil.java +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ViewRenderUtil.java @@ -2,11 +2,18 @@ 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; @@ -19,6 +26,17 @@ import static java.lang.String.format; */ public class ViewRenderUtil { + /** Thread State Colors */ + public static final EnumMap colorMapping = new EnumMap(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 @@ -85,4 +103,48 @@ public class ViewRenderUtil { return infoSB.toString(); } + + public static String drawThreadInfo(List threads, int width, int height) { + TableElement table = new TableElement(1, 3, 2, 1, 1, 1, 1, 1, 1).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", + "TIME", + "INTERRUPTED", + "DAEMON" + ) + ); + + for (ThreadVO thread : threads) { + Color color = colorMapping.get(thread.getState()); + long seconds = thread.getTime(); + long min = seconds / 60; + String time = min + ":" + (seconds % 60); + long cpu = thread.getCpu(); + + LabelElement daemonLabel = new LabelElement(thread.isDaemon()); + if (!thread.isDaemon()) { + daemonLabel.setStyle(Style.style(Color.magenta)); + } + table.row( + new LabelElement(thread.getId()), + new LabelElement(thread.getName()), + new LabelElement(thread.getGroup()), + new LabelElement(thread.getPriority()), + new LabelElement(thread.getState()).style(color.fg()), + new LabelElement(cpu), + new LabelElement(time), + new LabelElement(thread.isInterrupted()), + daemonLabel + ); + } + return RenderUtil.render(table, width, height); + } } 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 370e93ec4..0831ada24 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,5 +1,6 @@ package com.taobao.arthas.core.util; +import com.taobao.arthas.core.command.model.BlockingLockInfo; import com.taobao.arthas.core.view.Ansi; import java.arthas.SpyAPI; @@ -221,9 +222,9 @@ abstract public class ThreadUtil { } BlockingLockInfo blockingLockInfo = new BlockingLockInfo(); - blockingLockInfo.threadInfo = ownerThreadPerLock.get(mostBlockingLock); - blockingLockInfo.lockIdentityHashCode = mostBlockingLock; - blockingLockInfo.blockingThreadCount = blockCountPerLock.get(mostBlockingLock); + blockingLockInfo.setThreadInfo(ownerThreadPerLock.get(mostBlockingLock)); + blockingLockInfo.setLockIdentityHashCode(mostBlockingLock); + blockingLockInfo.setBlockingThreadCount(blockCountPerLock.get(mostBlockingLock)); return blockingLockInfo; } @@ -234,8 +235,8 @@ abstract public class ThreadUtil { public static String getFullStacktrace(BlockingLockInfo blockingLockInfo) { - return getFullStacktrace(blockingLockInfo.threadInfo, -1, blockingLockInfo.lockIdentityHashCode, - blockingLockInfo.blockingThreadCount); + return getFullStacktrace(blockingLockInfo.getThreadInfo(), -1, blockingLockInfo.getLockIdentityHashCode(), + blockingLockInfo.getBlockingThreadCount()); } @@ -329,17 +330,6 @@ abstract public class ThreadUtil { return sb.toString().replace("\t", " "); } - public static class BlockingLockInfo { - - // the thread info that is holing this lock. - public ThreadInfo threadInfo = null; - // the associated LockInfo object - public int lockIdentityHashCode = 0; - // the number of thread that is blocked on this lock - public int blockingThreadCount = 0; - - } - /** * * java.lang.Thread.getStackTrace(Thread.java:1559), From 25a8ba9f4f12a168f44dfed08602ed348f52add1 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Tue, 14 Jul 2020 20:17:14 +0800 Subject: [PATCH 3/5] transform commands: heapdump, perfcounter --- .../core/command/model/HeapDumpModel.java | 42 +++++++++++++ .../core/command/model/PerfCounterModel.java | 42 +++++++++++++ .../core/command/model/PerfCounterVO.java | 62 +++++++++++++++++++ .../command/monitor200/HeapDumpCommand.java | 18 +++--- .../monitor200/PerfCounterCommand.java | 55 +++++++--------- .../core/command/view/PerfCounterView.java | 46 ++++++++++++++ 6 files changed, 223 insertions(+), 42 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/HeapDumpModel.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/PerfCounterModel.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/PerfCounterVO.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/PerfCounterView.java diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/HeapDumpModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/HeapDumpModel.java new file mode 100644 index 000000000..6c2a987ce --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/HeapDumpModel.java @@ -0,0 +1,42 @@ +package com.taobao.arthas.core.command.model; + +/** + * Model of `heapdump` command + * @author gongdewei 2020/4/24 + */ +public class HeapDumpModel extends ResultModel { + + private String dumpFile; + + private boolean live; + + public HeapDumpModel() { + } + + public HeapDumpModel(String dumpFile, boolean live) { + this.dumpFile = dumpFile; + this.live = live; + } + + public String getDumpFile() { + return dumpFile; + } + + public void setDumpFile(String dumpFile) { + this.dumpFile = dumpFile; + } + + public boolean isLive() { + return live; + } + + public void setLive(boolean live) { + this.live = live; + } + + @Override + public String getType() { + return "heapdump"; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/PerfCounterModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/PerfCounterModel.java new file mode 100644 index 000000000..217f40719 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/PerfCounterModel.java @@ -0,0 +1,42 @@ +package com.taobao.arthas.core.command.model; + +import java.util.List; + +/** + * Model of 'perfcounter' + * + * @author gongdewei 2020/4/27 + */ +public class PerfCounterModel extends ResultModel { + private List perfCounters; + private boolean details; + + public PerfCounterModel() { + } + + public PerfCounterModel(List perfCounters, boolean details) { + this.perfCounters = perfCounters; + this.details = details; + } + + @Override + public String getType() { + return "perfcounter"; + } + + public List getPerfCounters() { + return perfCounters; + } + + public void setPerfCounters(List perfCounters) { + this.perfCounters = perfCounters; + } + + public boolean isDetails() { + return details; + } + + public void setDetails(boolean details) { + this.details = details; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/PerfCounterVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/PerfCounterVO.java new file mode 100644 index 000000000..27dd0030e --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/PerfCounterVO.java @@ -0,0 +1,62 @@ +package com.taobao.arthas.core.command.model; + + +/** + * VO for PerfCounterCommand + * + * @author gongdewei 2020/4/27 + */ +public class PerfCounterVO { + + private String name; + private String units; + private String variability; + private Object value; + + public PerfCounterVO() { + } + + public PerfCounterVO(String name, Object value) { + this.name = name; + this.value = value; + } + + public PerfCounterVO(String name, String units, String variability, Object value) { + this.name = name; + this.units = units; + this.variability = variability; + this.value = value; + } + + public void setName(String name) { + this.name = name; + } + + public void setUnits(String units) { + this.units = units; + } + + public void setVariability(String variability) { + this.variability = variability; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getName() { + return name; + } + + public String getUnits() { + return units; + } + + public String getVariability() { + return variability; + } + + public Object getValue() { + return value; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/HeapDumpCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/HeapDumpCommand.java index d1acf4a41..e1e031e4c 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/HeapDumpCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/HeapDumpCommand.java @@ -10,6 +10,8 @@ import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.sun.management.HotSpotDiagnosticMXBean; import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.model.HeapDumpModel; +import com.taobao.arthas.core.command.model.MessageModel; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.middleware.cli.annotations.Argument; @@ -48,7 +50,6 @@ public class HeapDumpCommand extends AnnotatedCommand { @Override public void process(CommandProcess process) { - int status = 0; try { String dumpFile = file; if (dumpFile == null || dumpFile.isEmpty()) { @@ -58,18 +59,17 @@ public class HeapDumpCommand extends AnnotatedCommand { file.delete(); } - process.write("Dumping heap to " + dumpFile + "...\n"); + process.appendResult(new MessageModel("Dumping heap to " + dumpFile + " ...")); run(process, dumpFile, live); - process.write("Heap dump file created\n"); - + process.appendResult(new MessageModel("Heap dump file created")); + process.appendResult(new HeapDumpModel(dumpFile, live)); + process.end(); } catch (Throwable t) { - logger.error("heap dump error", t); - process.write("Heap dump error: " + t.getMessage() + '\n'); - status = 1; - } finally { - process.end(status); + String errorMsg = "heap dump error: " + t.getMessage(); + logger.error(errorMsg, t); + process.end(-1, errorMsg); } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/PerfCounterCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/PerfCounterCommand.java index a388a7dba..6980c4cd3 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/PerfCounterCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/PerfCounterCommand.java @@ -1,9 +1,9 @@ package com.taobao.arthas.core.command.monitor200; -import static com.taobao.text.ui.Element.label; import java.lang.reflect.Method; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -12,15 +12,14 @@ import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.common.JavaVersionUtils; import com.taobao.arthas.common.PidUtils; import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.model.PerfCounterModel; +import com.taobao.arthas.core.command.model.PerfCounterVO; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; 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.text.Decoration; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; import sun.management.counter.Counter; import sun.management.counter.perf.PerfInstrumentation; @@ -51,36 +50,26 @@ public class PerfCounterCommand extends AnnotatedCommand { @Override public void process(CommandProcess process) { - try { - TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); - if (this.details) { - table = new TableElement(3, 1, 1, 10).leftCellPadding(1).rightCellPadding(1); - table.row(true, label("Name").style(Decoration.bold.bold()), - label("Variability").style(Decoration.bold.bold()), - label("Units").style(Decoration.bold.bold()), label("Value").style(Decoration.bold.bold())); - } - - List perfCounters = getPerfCounters(); - if (perfCounters.isEmpty()) { - process.write( - "please check arthas log. if java version >=9 , try to add jvm options when start your process: " - + "--add-opens java.base/jdk.internal.perf=ALL-UNNAMED " - + "--add-exports java.base/jdk.internal.perf=ALL-UNNAMED\n"); - } else { - for (Counter counter : perfCounters) { - if (details) { - table.row(counter.getName(), String.valueOf(counter.getVariability()), - String.valueOf(counter.getUnits()), String.valueOf(counter.getValue())); - } else { - table.row(counter.getName(), String.valueOf(counter.getValue())); - } - } - } - - process.write(RenderUtil.render(table, process.width())); - } finally { - process.end(); + List perfCounters = getPerfCounters(); + if (perfCounters.isEmpty()) { + process.end(1, + "please check arthas log. if java version >=9 , try to add jvm options when start your process: " + + "--add-opens java.base/jdk.internal.perf=ALL-UNNAMED " + + "--add-exports java.base/jdk.internal.perf=ALL-UNNAMED"); + return; } + + List perfCounterVOs = new ArrayList(); + for (Counter counter : perfCounters) { + PerfCounterVO perfCounterVO = new PerfCounterVO(counter.getName(), counter.getValue()); + if (details) { + perfCounterVO.setUnits(counter.getUnits().toString()); + perfCounterVO.setVariability(counter.getVariability().toString()); + } + perfCounterVOs.add(perfCounterVO); + } + process.appendResult(new PerfCounterModel(perfCounterVOs, details)); + process.end(); } private static List getPerfCounters() { diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/PerfCounterView.java b/core/src/main/java/com/taobao/arthas/core/command/view/PerfCounterView.java new file mode 100644 index 000000000..50dc07943 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/PerfCounterView.java @@ -0,0 +1,46 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.PerfCounterModel; +import com.taobao.arthas.core.command.model.PerfCounterVO; +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; + +/** + * View of 'perfcounter' command + * + * @author gongdewei 2020/4/27 + */ +public class PerfCounterView extends ResultView { + @Override + public void draw(CommandProcess process, PerfCounterModel result) { + List perfCounters = result.getPerfCounters(); + boolean details = result.isDetails(); + TableElement table; + if (details) { + table = new TableElement(3, 1, 1, 10).leftCellPadding(1).rightCellPadding(1); + table.row(true, label("Name").style(Decoration.bold.bold()), + label("Variability").style(Decoration.bold.bold()), + label("Units").style(Decoration.bold.bold()), label("Value").style(Decoration.bold.bold())); + } else { + table = new TableElement(4, 6).leftCellPadding(1).rightCellPadding(1); + table.row(true, label("Name").style(Decoration.bold.bold()), + label("Value").style(Decoration.bold.bold())); + } + + for (PerfCounterVO counter : perfCounters) { + if (details) { + table.row(counter.getName(), counter.getVariability(), + counter.getUnits(), String.valueOf(counter.getValue())); + } else { + table.row(counter.getName(), String.valueOf(counter.getValue())); + } + } + process.write(RenderUtil.render(table, process.width())); + } +} From e629ce72c2021ff262dde18e363e3a9859ffb5d3 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Tue, 14 Jul 2020 20:26:15 +0800 Subject: [PATCH 4/5] transform command: jvm, improve MEMORY section --- .../arthas/core/command/model/JvmItemVO.java | 46 ++++ .../arthas/core/command/model/JvmModel.java | 48 ++++ .../core/command/monitor200/JvmCommand.java | 235 ++++++++---------- .../arthas/core/command/view/JvmView.java | 120 +++++++++ 4 files changed, 312 insertions(+), 137 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/JvmItemVO.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/JvmModel.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/JvmView.java diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/JvmItemVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/JvmItemVO.java new file mode 100644 index 000000000..4c9b89006 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/JvmItemVO.java @@ -0,0 +1,46 @@ +package com.taobao.arthas.core.command.model; + +/** + * Key/value/desc + * @author gongdewei 2020/4/24 + */ +public class JvmItemVO { + private String name; + private Object value; + private String desc; + + public JvmItemVO(String name, Object value, String desc) { + this.name = name; + this.value = value; + this.desc = desc; + } + + public JvmItemVO(String name, Object value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/JvmModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/JvmModel.java new file mode 100644 index 000000000..dffc53c3f --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/JvmModel.java @@ -0,0 +1,48 @@ +package com.taobao.arthas.core.command.model; + +import java.util.*; + +/** + * Model of 'jvm' command + * + * @author gongdewei 2020/4/24 + */ +public class JvmModel extends ResultModel { + + private Map> jvmInfo; + + public JvmModel() { + jvmInfo = Collections.synchronizedMap(new LinkedHashMap>()); + } + + @Override + public String getType() { + return "jvm"; + } + + public JvmModel addItem(String group, String name, Object value) { + this.addItem(group, name, value, null); + return this; + } + + public JvmModel addItem(String group, String name, Object value, String desc) { + this.group(group).add(new JvmItemVO(name, value, desc)); + return this; + } + + public List group(String group) { + synchronized (this) { + List list = jvmInfo.get(group); + if (list == null) { + list = new ArrayList(); + jvmInfo.put(group, list); + } + return list; + } + } + + public Map> getJvmInfo() { + return jvmInfo; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/JvmCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/JvmCommand.java index 7f3f295c2..7ff325693 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/JvmCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/JvmCommand.java @@ -1,25 +1,21 @@ package com.taobao.arthas.core.command.monitor200; import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.model.GcInfoVO; +import com.taobao.arthas.core.command.model.JvmModel; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; -import com.taobao.arthas.core.util.StringUtils; -import com.taobao.arthas.core.util.affect.RowAffect; import com.taobao.middleware.cli.annotations.Description; import com.taobao.middleware.cli.annotations.Name; import com.taobao.middleware.cli.annotations.Summary; -import com.taobao.text.Decoration; -import com.taobao.text.ui.Element; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; import java.lang.management.*; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; - -import static com.taobao.text.ui.Element.label; +import java.util.LinkedHashMap; +import java.util.Map; /** * JVM info command @@ -43,50 +39,41 @@ public class JvmCommand extends AnnotatedCommand { @Override public void process(CommandProcess process) { - RowAffect affect = new RowAffect(); - TableElement table = new TableElement(2, 5).leftCellPadding(1).rightCellPadding(1); - table.row(true, label("RUNTIME").style(Decoration.bold.bold())); - drawRuntimeTable(table); - table.row("", ""); - table.row(true, label("CLASS-LOADING").style(Decoration.bold.bold())); - drawClassLoadingTable(table); - table.row("", ""); - table.row(true, label("COMPILATION").style(Decoration.bold.bold())); - drawCompilationTable(table); + + JvmModel jvmModel = new JvmModel(); + + addRuntimeInfo(jvmModel); + + addClassLoading(jvmModel); + + addCompilation(jvmModel); if (!garbageCollectorMXBeans.isEmpty()) { - table.row("", ""); - table.row(true, label("GARBAGE-COLLECTORS").style(Decoration.bold.bold())); - drawGarbageCollectorsTable(table); + addGarbageCollectors(jvmModel); } if (!memoryManagerMXBeans.isEmpty()) { - table.row("", ""); - table.row(true, label("MEMORY-MANAGERS").style(Decoration.bold.bold())); - drawMemoryManagersTable(table); + addMemoryManagers(jvmModel); } - table.row("", ""); - table.row(true, label("MEMORY").style(Decoration.bold.bold())); - drawMemoryTable(table); - table.row("", ""); - table.row(true, label("OPERATING-SYSTEM").style(Decoration.bold.bold())); - drawOperatingSystemMXBeanTable(table); - table.row("", ""); - table.row(true, label("THREAD").style(Decoration.bold.bold())); - drawThreadTable(table); - table.row("", ""); - table.row(true, label("FILE-DESCRIPTOR").style(Decoration.bold.bold())); - drawFileDescriptorTable(table); - process.write(RenderUtil.render(table, process.width())); - process.write(affect.toString()).write("\n"); + addMemory(jvmModel); + + addOperatingSystem(jvmModel); + + addThread(jvmModel); + + addFileDescriptor(jvmModel); + + process.appendResult(jvmModel); process.end(); } - private void drawFileDescriptorTable(TableElement table) { - table.row("MAX-FILE-DESCRIPTOR-COUNT", "" + invokeFileDescriptor(operatingSystemMXBean, "getMaxFileDescriptorCount")) - .row("OPEN-FILE-DESCRIPTOR-COUNT", "" + invokeFileDescriptor(operatingSystemMXBean, "getOpenFileDescriptorCount")); + private void addFileDescriptor(JvmModel jvmModel) { + String group = "FILE-DESCRIPTOR"; + jvmModel.addItem(group,"MAX-FILE-DESCRIPTOR-COUNT", invokeFileDescriptor(operatingSystemMXBean, "getMaxFileDescriptorCount")) + .addItem(group,"OPEN-FILE-DESCRIPTOR-COUNT", invokeFileDescriptor(operatingSystemMXBean, "getOpenFileDescriptorCount")); } + private long invokeFileDescriptor(OperatingSystemMXBean os, String name) { try { final Method method = os.getClass().getDeclaredMethod(name); @@ -96,137 +83,111 @@ public class JvmCommand extends AnnotatedCommand { return -1; } } - private String toCol(Collection strings) { - final StringBuilder colSB = new StringBuilder(); - if (strings.isEmpty()) { - colSB.append("[]"); - } else { - for (String str : strings) { - colSB.append(str).append("\n"); - } - } - return colSB.toString(); - } - private String toCol(String... stringArray) { - final StringBuilder colSB = new StringBuilder(); - if (null == stringArray - || stringArray.length == 0) { - colSB.append("[]"); - } else { - for (String str : stringArray) { - colSB.append(str).append("\n"); - } - } - return colSB.toString(); - } - - private Element drawRuntimeTable(TableElement table) { + private void addRuntimeInfo(JvmModel jvmModel) { String bootClassPath = ""; try { bootClassPath = runtimeMXBean.getBootClassPath(); } catch (Exception e) { // under jdk9 will throw UnsupportedOperationException, ignore } - table.row("MACHINE-NAME", runtimeMXBean.getName()) - .row("JVM-START-TIME", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(runtimeMXBean.getStartTime()))) - .row("MANAGEMENT-SPEC-VERSION", runtimeMXBean.getManagementSpecVersion()) - .row("SPEC-NAME", runtimeMXBean.getSpecName()) - .row("SPEC-VENDOR", runtimeMXBean.getSpecVendor()) - .row("SPEC-VERSION", runtimeMXBean.getSpecVersion()) - .row("VM-NAME", runtimeMXBean.getVmName()) - .row("VM-VENDOR", runtimeMXBean.getVmVendor()) - .row("VM-VERSION", runtimeMXBean.getVmVersion()) - .row("INPUT-ARGUMENTS", toCol(runtimeMXBean.getInputArguments())) - .row("CLASS-PATH", runtimeMXBean.getClassPath()) - .row("BOOT-CLASS-PATH", bootClassPath) - .row("LIBRARY-PATH", runtimeMXBean.getLibraryPath()); - - return table; + String group = "RUNTIME"; + jvmModel.addItem(group,"MACHINE-NAME", runtimeMXBean.getName()); + jvmModel.addItem(group, "JVM-START-TIME", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(runtimeMXBean.getStartTime()))); + jvmModel.addItem(group, "MANAGEMENT-SPEC-VERSION", runtimeMXBean.getManagementSpecVersion()); + jvmModel.addItem(group, "SPEC-NAME", runtimeMXBean.getSpecName()); + jvmModel.addItem(group, "SPEC-VENDOR", runtimeMXBean.getSpecVendor()); + jvmModel.addItem(group, "SPEC-VERSION", runtimeMXBean.getSpecVersion()); + jvmModel.addItem(group, "VM-NAME", runtimeMXBean.getVmName()); + jvmModel.addItem(group, "VM-VENDOR", runtimeMXBean.getVmVendor()); + jvmModel.addItem(group, "VM-VERSION", runtimeMXBean.getVmVersion()); + jvmModel.addItem(group, "INPUT-ARGUMENTS", runtimeMXBean.getInputArguments()); + jvmModel.addItem(group, "CLASS-PATH", runtimeMXBean.getClassPath()); + jvmModel.addItem(group, "BOOT-CLASS-PATH", bootClassPath); + jvmModel.addItem(group, "LIBRARY-PATH", runtimeMXBean.getLibraryPath()); } - private Element drawClassLoadingTable(TableElement table) { - table.row("LOADED-CLASS-COUNT", "" + classLoadingMXBean.getLoadedClassCount()) - .row("TOTAL-LOADED-CLASS-COUNT", "" + classLoadingMXBean.getTotalLoadedClassCount()) - .row("UNLOADED-CLASS-COUNT", "" + classLoadingMXBean.getUnloadedClassCount()) - .row("IS-VERBOSE", "" + classLoadingMXBean.isVerbose()); - - return table; + private void addClassLoading(JvmModel jvmModel) { + String group = "CLASS-LOADING"; + jvmModel.addItem(group, "LOADED-CLASS-COUNT", classLoadingMXBean.getLoadedClassCount()); + jvmModel.addItem(group, "TOTAL-LOADED-CLASS-COUNT", classLoadingMXBean.getTotalLoadedClassCount()); + jvmModel.addItem(group, "UNLOADED-CLASS-COUNT", classLoadingMXBean.getUnloadedClassCount()); + jvmModel.addItem(group, "IS-VERBOSE", classLoadingMXBean.isVerbose()); } - private Element drawCompilationTable(TableElement table) { - table.row("NAME", compilationMXBean.getName()); - + private void addCompilation(JvmModel jvmModel) { + String group = "COMPILATION"; + jvmModel.addItem(group, "NAME", compilationMXBean.getName()); if (compilationMXBean.isCompilationTimeMonitoringSupported()) { - table.row("TOTAL-COMPILE-TIME", compilationMXBean.getTotalCompilationTime() + "(ms)"); - + jvmModel.addItem(group, "TOTAL-COMPILE-TIME", compilationMXBean.getTotalCompilationTime(), "time (ms)"); } - return table; } - private Element drawGarbageCollectorsTable(TableElement table) { - for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMXBeans) { - table.row(garbageCollectorMXBean.getName() + "\n[count/time]", - garbageCollectorMXBean.getCollectionCount() + "/" + garbageCollectorMXBean.getCollectionTime() + "(ms)"); - } + private void addGarbageCollectors(JvmModel jvmModel) { + String group = "GARBAGE-COLLECTORS"; + for (GarbageCollectorMXBean gcMXBean : garbageCollectorMXBeans) { + Map gcInfo = new LinkedHashMap(); + gcInfo.put("name", gcMXBean.getName()); + gcInfo.put("collectionCount", gcMXBean.getCollectionCount()); + gcInfo.put("collectionTime", gcMXBean.getCollectionTime()); - return table; + jvmModel.addItem(group, gcMXBean.getName(), gcInfo, "count/time (ms)"); + } } - private Element drawMemoryManagersTable(TableElement table) { + private void addMemoryManagers(JvmModel jvmModel) { + String group = "MEMORY-MANAGERS"; for (final MemoryManagerMXBean memoryManagerMXBean : memoryManagerMXBeans) { if (memoryManagerMXBean.isValid()) { final String name = memoryManagerMXBean.isValid() ? memoryManagerMXBean.getName() : memoryManagerMXBean.getName() + "(Invalid)"; - - - table.row(name, toCol(memoryManagerMXBean.getMemoryPoolNames())); + jvmModel.addItem(group, name, memoryManagerMXBean.getMemoryPoolNames()); } } - - return table; } - private Element drawMemoryTable(TableElement table) { + private void addMemory(JvmModel jvmModel) { + String group = "MEMORY"; MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); - table.row("HEAP-MEMORY-USAGE\n[committed/init/max/used]", - formatMemoryByte(heapMemoryUsage.getCommitted()) - + "/" + formatMemoryByte(heapMemoryUsage.getInit()) - + "/" + formatMemoryByte(heapMemoryUsage.getMax()) - + "/" + formatMemoryByte(heapMemoryUsage.getUsed()) - ); + Map heapMemoryInfo = getMemoryUsageInfo("heap", heapMemoryUsage); + jvmModel.addItem(group, "HEAP-MEMORY-USAGE", heapMemoryInfo, "memory in bytes"); + MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage(); - table.row("NO-HEAP-MEMORY-USAGE\n[committed/init/max/used]", - formatMemoryByte(nonHeapMemoryUsage.getCommitted()) - + "/" + formatMemoryByte(nonHeapMemoryUsage.getInit()) - + "/" + formatMemoryByte(nonHeapMemoryUsage.getMax()) - + "/" + formatMemoryByte(nonHeapMemoryUsage.getUsed()) - ); + Map nonheapMemoryInfo = getMemoryUsageInfo("nonheap", nonHeapMemoryUsage); + jvmModel.addItem(group,"NO-HEAP-MEMORY-USAGE", nonheapMemoryInfo, "memory in bytes"); - table.row("PENDING-FINALIZE-COUNT", "" + memoryMXBean.getObjectPendingFinalizationCount()); - return table; - } - private String formatMemoryByte(long bytes){ - return String.format("%s(%s)",bytes, StringUtils.humanReadableByteCount(bytes)); + jvmModel.addItem(group,"PENDING-FINALIZE-COUNT", memoryMXBean.getObjectPendingFinalizationCount()); } - private Element drawOperatingSystemMXBeanTable(TableElement table) { - table.row("OS", operatingSystemMXBean.getName()).row("ARCH", operatingSystemMXBean.getArch()) - .row("PROCESSORS-COUNT", "" + operatingSystemMXBean.getAvailableProcessors()) - .row("LOAD-AVERAGE", "" + operatingSystemMXBean.getSystemLoadAverage()) - .row("VERSION", operatingSystemMXBean.getVersion()); - return table; + private Map getMemoryUsageInfo(String name, MemoryUsage heapMemoryUsage) { + Map memoryInfo = new LinkedHashMap(); + memoryInfo.put("name", name); + memoryInfo.put("init", heapMemoryUsage.getInit()); + memoryInfo.put("used", heapMemoryUsage.getUsed()); + memoryInfo.put("committed", heapMemoryUsage.getCommitted()); + memoryInfo.put("max", heapMemoryUsage.getMax()); + return memoryInfo; } - private Element drawThreadTable(TableElement table) { - table.row("COUNT", "" + threadMXBean.getThreadCount()) - .row("DAEMON-COUNT", "" + threadMXBean.getDaemonThreadCount()) - .row("PEAK-COUNT", "" + threadMXBean.getPeakThreadCount()) - .row("STARTED-COUNT", "" + threadMXBean.getTotalStartedThreadCount()) - .row("DEADLOCK-COUNT","" + getDeadlockedThreadsCount(threadMXBean)); - return table; + private void addOperatingSystem(JvmModel jvmModel) { + String group = "OPERATING-SYSTEM"; + jvmModel.addItem(group,"OS", operatingSystemMXBean.getName()) + .addItem(group,"ARCH", operatingSystemMXBean.getArch()) + .addItem(group,"PROCESSORS-COUNT", operatingSystemMXBean.getAvailableProcessors()) + .addItem(group,"LOAD-AVERAGE", operatingSystemMXBean.getSystemLoadAverage()) + .addItem(group,"VERSION", operatingSystemMXBean.getVersion()); } + + private void addThread(JvmModel jvmModel) { + String group = "THREAD"; + jvmModel.addItem(group, "COUNT", threadMXBean.getThreadCount()) + .addItem(group, "DAEMON-COUNT", threadMXBean.getDaemonThreadCount()) + .addItem(group, "PEAK-COUNT", threadMXBean.getPeakThreadCount()) + .addItem(group, "STARTED-COUNT", threadMXBean.getTotalStartedThreadCount()) + .addItem(group, "DEADLOCK-COUNT",getDeadlockedThreadsCount(threadMXBean)); + } + private int getDeadlockedThreadsCount(ThreadMXBean threads) { final long[] ids = threads.findDeadlockedThreads(); if (ids == null) { diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/JvmView.java b/core/src/main/java/com/taobao/arthas/core/command/view/JvmView.java new file mode 100644 index 000000000..34a2952d3 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/JvmView.java @@ -0,0 +1,120 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.JvmModel; +import com.taobao.arthas.core.command.model.JvmItemVO; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.text.Decoration; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static com.taobao.text.ui.Element.label; + +/** + * View of 'jvm' command + * + * @author gongdewei 2020/4/24 + */ +public class JvmView extends ResultView { + + @Override + public void draw(CommandProcess process, JvmModel result) { + TableElement table = new TableElement(2, 5).leftCellPadding(1).rightCellPadding(1); + + for (Map.Entry> entry : result.getJvmInfo().entrySet()) { + String group = entry.getKey(); + List items = entry.getValue(); + + table.row(true, label(group).style(Decoration.bold.bold())); + for (JvmItemVO item : items) { + String valueStr; + if (item.getValue() instanceof Map && item.getName().endsWith("MEMORY-USAGE")) { + valueStr = renderMemoryUsage((Map) item.getValue()); + } else { + valueStr = renderItemValue(item.getValue()); + } + if (item.getDesc() != null) { + table.row(item.getName() + "\n[" + item.getDesc() + "]", valueStr); + } else { + table.row(item.getName(), valueStr); + } + } + table.row("", ""); + } + + process.write(RenderUtil.render(table, process.width())); + } + + private String renderCountTime(long[] value) { + //count/time + return value[0] + "/" + value[1]; + } + + private String renderItemValue(Object value) { + if (value == null) { + return "null"; + } + if (value instanceof Collection) { + return renderCollectionValue((Collection) value); + } else if (value instanceof String[]) { + return renderArrayValue((String[]) value); + } else if (value instanceof Map) { + return renderMapValue((Map) value); + } + return String.valueOf(value); + } + + private String renderCollectionValue(Collection strings) { + final StringBuilder colSB = new StringBuilder(); + if (strings.isEmpty()) { + colSB.append("[]"); + } else { + for (String str : strings) { + colSB.append(str).append("\n"); + } + } + return colSB.toString(); + } + + private String renderArrayValue(String... stringArray) { + final StringBuilder colSB = new StringBuilder(); + if (null == stringArray + || stringArray.length == 0) { + colSB.append("[]"); + } else { + for (String str : stringArray) { + colSB.append(str).append("\n"); + } + } + return colSB.toString(); + } + + private String renderMapValue(Map valueMap) { + final StringBuilder colSB = new StringBuilder(); + if (valueMap != null) { + for (Map.Entry entry : valueMap.entrySet()) { + colSB.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n"); + } + } + return colSB.toString(); + } + + private String renderMemoryUsage(Map valueMap) { + final StringBuilder colSB = new StringBuilder(); + String[] keys = new String[]{"init", "used", "committed", "max"}; + for (String key : keys) { + Object value = valueMap.get(key); + String valueStr = value != null ? formatMemoryByte((Long) value) : ""; + colSB.append(key).append(" : ").append(valueStr).append("\n"); + } + return colSB.toString(); + } + + private String formatMemoryByte(long bytes) { + return String.format("%s(%s)", bytes, StringUtils.humanReadableByteCount(bytes)); + } +} From 5c564fbd0fc3305a14ac5cf46030589ed227a12d Mon Sep 17 00:00:00 2001 From: gongdewei Date: Tue, 14 Jul 2020 20:27:31 +0800 Subject: [PATCH 5/5] transform command: mbean --- .../core/command/model/MBeanAttributeVO.java | 47 +++ .../arthas/core/command/model/MBeanModel.java | 55 ++++ .../core/command/monitor200/JvmCommand.java | 1 - .../core/command/monitor200/MBeanCommand.java | 268 ++++++++---------- .../arthas/core/command/view/MBeanView.java | 212 ++++++++++++++ .../core/command/view/ResultViewResolver.java | 6 + 6 files changed, 443 insertions(+), 146 deletions(-) create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/MBeanAttributeVO.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/model/MBeanModel.java create mode 100644 core/src/main/java/com/taobao/arthas/core/command/view/MBeanView.java diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/MBeanAttributeVO.java b/core/src/main/java/com/taobao/arthas/core/command/model/MBeanAttributeVO.java new file mode 100644 index 000000000..cae43de59 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/MBeanAttributeVO.java @@ -0,0 +1,47 @@ +package com.taobao.arthas.core.command.model; + +/** + * MBean attribute + * + * @author gongdewei 2020/4/26 + */ +public class MBeanAttributeVO { + private String name; + private Object value; + private String error; + + public MBeanAttributeVO(String name, Object value) { + this.name = name; + this.value = value; + } + + public MBeanAttributeVO(String name, Object value, String error) { + this.name = name; + this.value = value; + this.error = error; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/MBeanModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/MBeanModel.java new file mode 100644 index 000000000..105da1518 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/MBeanModel.java @@ -0,0 +1,55 @@ +package com.taobao.arthas.core.command.model; + +import javax.management.MBeanInfo; +import java.util.List; +import java.util.Map; + +/** + * Model of 'mbean' + * + * @author gongdewei 2020/4/26 + */ +public class MBeanModel extends ResultModel { + + private List mbeanNames; + + private Map mbeanMetadata; + + private Map> mbeanAttribute; + + public MBeanModel() { + } + + public MBeanModel(List mbeanNames) { + this.mbeanNames = mbeanNames; + } + + @Override + public String getType() { + return "mbean"; + } + + public List getMbeanNames() { + return mbeanNames; + } + + public void setMbeanNames(List mbeanNames) { + this.mbeanNames = mbeanNames; + } + + public Map getMbeanMetadata() { + return mbeanMetadata; + } + + public void setMbeanMetadata(Map mbeanMetadata) { + this.mbeanMetadata = mbeanMetadata; + } + + public Map> getMbeanAttribute() { + return mbeanAttribute; + } + + public void setMbeanAttribute(Map> mbeanAttribute) { + this.mbeanAttribute = mbeanAttribute; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/JvmCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/JvmCommand.java index 7ff325693..44eaeafd6 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/JvmCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/JvmCommand.java @@ -1,7 +1,6 @@ package com.taobao.arthas.core.command.monitor200; import com.taobao.arthas.core.command.Constants; -import com.taobao.arthas.core.command.model.GcInfoVO; import com.taobao.arthas.core.command.model.JvmModel; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/MBeanCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/MBeanCommand.java index 12f7d1575..563281337 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/MBeanCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/MBeanCommand.java @@ -3,6 +3,8 @@ package com.taobao.arthas.core.command.monitor200; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.command.model.MBeanAttributeVO; +import com.taobao.arthas.core.command.model.MBeanModel; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.cli.Completion; import com.taobao.arthas.core.shell.cli.CompletionUtils; @@ -22,38 +24,25 @@ 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.text.Color; -import com.taobao.text.Decoration; -import com.taobao.text.ui.LabelElement; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; import java.lang.management.ManagementFactory; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; -import javax.management.Descriptor; -import javax.management.DescriptorRead; import javax.management.MBeanAttributeInfo; -import javax.management.MBeanConstructorInfo; import javax.management.MBeanInfo; -import javax.management.MBeanNotificationInfo; -import javax.management.MBeanOperationInfo; -import javax.management.MBeanParameterInfo; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; - -import static com.taobao.text.ui.Element.label; -import static javax.management.MBeanOperationInfo.ACTION; -import static javax.management.MBeanOperationInfo.ACTION_INFO; -import static javax.management.MBeanOperationInfo.INFO; -import static javax.management.MBeanOperationInfo.UNKNOWN; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; /** * Date: 2019/4/18 @@ -63,13 +52,16 @@ import static javax.management.MBeanOperationInfo.UNKNOWN; @Name("mbean") @Summary("Display the mbean information") @Description("\nExamples:\n" + - " mbean\n" + - " mbean -m java.lang:type=Threading\n" + - " mbean java.lang:type=Threading\n" + - " mbean java.lang:type=Threading *Count\n" + - " mbean -E java.lang:type=Threading PeakThreadCount|ThreadCount|DaemonThreadCount\n" + - " mbean -i 1000 java.lang:type=Threading *Count\n" + - Constants.WIKI + Constants.WIKI_HOME + "mbean") + " mbean\n" + + " mbean -m java.lang:type=Threading\n" + + " mbean java.lang:type=Threading\n" + + " mbean java.lang:type=Threading *Count\n" + + " mbean java.lang:type=MemoryPool,name=PS\\ Old\\ Gen\n" + + " mbean java.lang:type=MemoryPool,name=*\n" + + " mbean java.lang:type=MemoryPool,name=* Usage\n" + + " mbean -E java.lang:type=Threading PeakThreadCount|ThreadCount|DaemonThreadCount\n" + + " mbean -i 1000 java.lang:type=Threading *Count\n" + + Constants.WIKI + Constants.WIKI_HOME + "mbean") public class MBeanCommand extends AnnotatedCommand { private static final Logger logger = LoggerFactory.getLogger(MBeanCommand.class); @@ -84,7 +76,12 @@ public class MBeanCommand extends AnnotatedCommand { private long count = 0; @Argument(argName = "name-pattern", index = 0, required = false) - @Description("ObjectName pattern, see javax.management.ObjectName for more detail.") + @Description("ObjectName pattern, see javax.management.ObjectName for more detail. \n" + + "It looks like this: \n" + + " domain: key-property-list\n" + + "For example: \n" + + " java.lang:name=G1 Old Gen,type=MemoryPool\n" + + " java.lang:name=*,type=MemoryPool") public void setNamePattern(String name) { this.name = name; } @@ -107,7 +104,7 @@ public class MBeanCommand extends AnnotatedCommand { isRegEx = regEx; } - @Option(shortName = "m", longName = "metadata", flag = false) + @Option(shortName = "m", longName = "metadata", flag = true) @Description("Show metadata of mbean.") public void setMetaData(boolean metaData) { this.metaData = metaData; @@ -141,6 +138,7 @@ public class MBeanCommand extends AnnotatedCommand { @Override public void process(CommandProcess process) { + //每个分支调用process.end()结束执行 if (StringUtils.isEmpty(getName())) { listMBean(process); } else if (isMetaData()) { @@ -152,10 +150,11 @@ public class MBeanCommand extends AnnotatedCommand { private void listMBean(CommandProcess process) { Set objectNames = queryObjectNames(); + List mbeanNames = new ArrayList(objectNames.size()); for (ObjectName objectName : objectNames) { - process.write(objectName.toString()); - process.write("\n"); + mbeanNames.add(objectName.toString()); } + process.appendResult(new MBeanModel(mbeanNames)); process.end(); } @@ -192,6 +191,8 @@ public class MBeanCommand extends AnnotatedCommand { } else { timer.schedule(new MBeanTimerTask(process), 0); } + + //异步执行,这里不能调用process.end(),在timer task中结束命令执行 } public synchronized void stop() { @@ -214,19 +215,18 @@ public class MBeanCommand extends AnnotatedCommand { Set objectNames = queryObjectNames(); MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); try { - TableElement table = createTable(); + MBeanModel mbeanModel = new MBeanModel(); + Map mbeanMetaData = new LinkedHashMap(); + mbeanModel.setMbeanMetadata(mbeanMetaData); for (ObjectName objectName : objectNames) { MBeanInfo mBeanInfo = mBeanServer.getMBeanInfo(objectName); - drawMetaInfo(mBeanInfo, objectName, table); - drawAttributeInfo(mBeanInfo.getAttributes(), table); - drawOperationInfo(mBeanInfo.getOperations(), table); - drawNotificationInfo(mBeanInfo.getNotifications(), table); + mbeanMetaData.put(objectName.toString(), mBeanInfo); } - process.write(RenderUtil.render(table, process.width())); + process.appendResult(mbeanModel); + process.end(); } catch (Throwable e) { logger.warn("listMetaData error", e); - } finally { - process.end(); + process.end(1, "list mbean metadata error"); } } @@ -348,102 +348,6 @@ public class MBeanCommand extends AnnotatedCommand { return isRegEx ? new RegexMatcher(attribute) : new WildcardMatcher(attribute); } - private void drawMetaInfo(MBeanInfo mBeanInfo, ObjectName objectName, TableElement table) { - table.row(new LabelElement("MBeanInfo").style(Decoration.bold.fg(Color.red))); - table.row(new LabelElement("Info:").style(Decoration.bold.fg(Color.yellow))); - table.row("ObjectName", objectName.toString()); - table.row("ClassName", mBeanInfo.getClassName()); - table.row("Description", mBeanInfo.getDescription()); - drawDescriptorInfo("Info Descriptor:", mBeanInfo, table); - MBeanConstructorInfo[] constructors = mBeanInfo.getConstructors(); - if (constructors.length > 0) { - for (int i = 0; i < constructors.length; i++) { - table.row(new LabelElement("Constructor-" + i).style(Decoration.bold.fg(Color.yellow))); - table.row("Name", constructors[i].getName()); - table.row("Description", constructors[i].getDescription()); - } - } - } - - private void drawAttributeInfo(MBeanAttributeInfo[] attributes, TableElement table) { - for (MBeanAttributeInfo attribute : attributes) { - table.row(new LabelElement("MBeanAttributeInfo").style(Decoration.bold.fg(Color.red))); - table.row(new LabelElement("Attribute:").style(Decoration.bold.fg(Color.yellow))); - table.row("Name", attribute.getName()); - table.row("Description", attribute.getDescription()); - table.row("Readable", String.valueOf(attribute.isReadable())); - table.row("Writable", String.valueOf(attribute.isWritable())); - table.row("Is", String.valueOf(attribute.isIs())); - table.row("Type", attribute.getType()); - drawDescriptorInfo("Attribute Descriptor:", attribute, table); - } - } - - private void drawOperationInfo(MBeanOperationInfo[] operations, TableElement table) { - for (MBeanOperationInfo operation : operations) { - table.row(new LabelElement("MBeanOperationInfo").style(Decoration.bold.fg(Color.red))); - table.row(new LabelElement("Operation:").style(Decoration.bold.fg(Color.yellow))); - table.row("Name", operation.getName()); - table.row("Description", operation.getDescription()); - String impact = ""; - switch (operation.getImpact()) { - case ACTION: - impact = "action"; - break; - case ACTION_INFO: - impact = "action/info"; - break; - case INFO: - impact = "info"; - break; - case UNKNOWN: - impact = "unknown"; - break; - } - table.row("Impact", impact); - table.row("ReturnType", operation.getReturnType()); - MBeanParameterInfo[] signature = operation.getSignature(); - if (signature.length > 0) { - for (int i = 0; i < signature.length; i++) { - table.row(new LabelElement("Parameter-" + i).style(Decoration.bold.fg(Color.yellow))); - table.row("Name", signature[i].getName()); - table.row("Type", signature[i].getType()); - table.row("Description", signature[i].getDescription()); - } - } - drawDescriptorInfo("Operation Descriptor:", operation, table); - } - } - - private void drawNotificationInfo(MBeanNotificationInfo[] notificationInfos, TableElement table) { - for (MBeanNotificationInfo notificationInfo : notificationInfos) { - table.row(new LabelElement("MBeanNotificationInfo").style(Decoration.bold.fg(Color.red))); - table.row(new LabelElement("Notification:").style(Decoration.bold.fg(Color.yellow))); - table.row("Name", notificationInfo.getName()); - table.row("Description", notificationInfo.getDescription()); - table.row("NotifTypes", Arrays.toString(notificationInfo.getNotifTypes())); - drawDescriptorInfo("Notification Descriptor:", notificationInfo, table); - } - } - - private void drawDescriptorInfo(String title, DescriptorRead descriptorRead, TableElement table) { - Descriptor descriptor = descriptorRead.getDescriptor(); - String[] fieldNames = descriptor.getFieldNames(); - if (fieldNames.length > 0) { - table.row(new LabelElement(title).style(Decoration.bold.fg(Color.yellow))); - for (String fieldName : fieldNames) { - Object fieldValue = descriptor.getFieldValue(fieldName); - table.row(fieldName, fieldValue.toString()); - } - } - } - - private static TableElement createTable() { - TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); - table.row(true, label("NAME").style(Decoration.bold.bold()), - label("VALUE").style(Decoration.bold.bold())); - return table; - } public class MBeanInterruptHandler extends CommandInterruptHandler { @@ -475,16 +379,20 @@ public class MBeanCommand extends AnnotatedCommand { // stop the timer timer.cancel(); timer.purge(); - process.write("Process ends after " + getNumOfExecutions() + " time(s).\n"); - process.end(); + process.end(-1, "Process ends after " + getNumOfExecutions() + " time(s)."); return; } try { + //result model + MBeanModel mBeanModel = new MBeanModel(); + Map> mbeanAttributeMap = new LinkedHashMap>(); + mBeanModel.setMbeanAttribute(mbeanAttributeMap); + MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer(); Set objectNames = queryObjectNames(); - TableElement table = createTable(); for (ObjectName objectName : objectNames) { + List attributeVOs = null; MBeanInfo mBeanInfo = platformMBeanServer.getMBeanInfo(objectName); MBeanAttributeInfo[] attributes = mBeanInfo.getAttributes(); for (MBeanAttributeInfo attribute : attributes) { @@ -492,29 +400,99 @@ public class MBeanCommand extends AnnotatedCommand { if (!getAttributeMatcher().matching(attributeName)) { continue; } - String value; + + //create attributeVO list + if (attributeVOs == null) { + attributeVOs = new ArrayList(); + mbeanAttributeMap.put(objectName.toString(), attributeVOs); + } + if (!attribute.isReadable()) { - value = RenderUtil.render(new LabelElement("Unavailable").style(Decoration.bold_off.fg(Color.red))); + attributeVOs.add(new MBeanAttributeVO(attributeName, null, "Unavailable")); } else { - Object attributeObj = platformMBeanServer.getAttribute(objectName, attributeName); - value = String.valueOf(attributeObj); + try { + Object attributeObj = platformMBeanServer.getAttribute(objectName, attributeName); + attributeVOs.add(createMBeanAttributeVO(attributeName, attributeObj)); + } catch (Throwable e) { + logger.error("read mbean attribute failed: objectName={}, attributeName={}", objectName, attributeName, e); + String errorStr; + Throwable cause = e.getCause(); + if (cause != null && cause instanceof UnsupportedOperationException) { + errorStr = "Unsupported"; + } else { + errorStr = "Failure"; + } + attributeVOs.add(new MBeanAttributeVO(attributeName, null, errorStr)); + } } - table.row(attributeName, value); } - process.write(RenderUtil.render(table, process.width())); - process.write("\n"); } + process.appendResult(mBeanModel); } catch (Throwable e) { - logger.warn("mbean error", e); + logger.warn("read mbean error", e); + stop(); + process.end(1, "read mbean error."); + return; } count++; process.times().incrementAndGet(); - if (getInterval() <= 0) { stop(); process.end(); } } } + + private MBeanAttributeVO createMBeanAttributeVO(String attributeName, Object originAttrValue) { + Object attrValue = convertAttrValue(attributeName, originAttrValue); + + return new MBeanAttributeVO(attributeName, attrValue); + } + + private Object convertAttrValue(String attributeName, Object originAttrValue) { + Object attrValue = originAttrValue; + + try { + if (originAttrValue instanceof ObjectName) { + attrValue = String.valueOf(originAttrValue); + } else if (attrValue instanceof CompositeData) { + //mbean java.lang:type=MemoryPool,name=* + CompositeData compositeData = (CompositeData) attrValue; + attrValue = convertCompositeData(attributeName, compositeData); + } else if (attrValue instanceof CompositeData[]) { + //mbean com.sun.management:type=HotSpotDiagnostic + CompositeData[] compositeDataArray = (CompositeData[]) attrValue; + List> dataList = new ArrayList>(compositeDataArray.length); + for (CompositeData compositeData : compositeDataArray) { + dataList.add(convertCompositeData(attributeName, compositeData)); + } + attrValue = dataList; + } else if (attrValue instanceof TabularData) { + //mbean java.lang:type=GarbageCollector,name=* + TabularData tabularData = (TabularData) attrValue; + Collection compositeDataList = (Collection) tabularData.values(); + List> dataList = new ArrayList>(compositeDataList.size()); + for (CompositeData compositeData : compositeDataList) { + dataList.add(convertCompositeData(attributeName, compositeData)); + } + attrValue = dataList; + } + } catch (Throwable e) { + logger.error("convert mbean attribute error, attribute: {}={}", attributeName, originAttrValue, e); + attrValue = String.valueOf(originAttrValue); + } + return attrValue; + } + + private Map convertCompositeData(String attributeName, CompositeData compositeData) { + Set keySet = compositeData.getCompositeType().keySet(); + String[] keys = keySet.toArray(new String[0]); + Object[] values = compositeData.getAll(keys); + Map data = new LinkedHashMap(); + for (int i = 0; i < keys.length; i++) { + data.put(keys[i], convertAttrValue(attributeName + "." + keys[i], values[i])); + } + return data; + } } \ No newline at end of file diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/MBeanView.java b/core/src/main/java/com/taobao/arthas/core/command/view/MBeanView.java new file mode 100644 index 000000000..87349036a --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/view/MBeanView.java @@ -0,0 +1,212 @@ +package com.taobao.arthas.core.command.view; + +import com.taobao.arthas.core.command.model.MBeanAttributeVO; +import com.taobao.arthas.core.command.model.MBeanModel; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.text.Color; +import com.taobao.text.Decoration; +import com.taobao.text.ui.LabelElement; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + +import javax.management.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static com.taobao.text.ui.Element.label; +import static javax.management.MBeanOperationInfo.*; +import static javax.management.MBeanOperationInfo.UNKNOWN; + +/** + * View of 'mbean' command + * + * @author gongdewei 2020/4/26 + */ +public class MBeanView extends ResultView { + @Override + public void draw(CommandProcess process, MBeanModel result) { + if (result.getMbeanNames() != null) { + drawMBeanNames(process, result.getMbeanNames()); + } else if (result.getMbeanMetadata() != null) { + drawMBeanMetadata(process, result.getMbeanMetadata()); + } else if (result.getMbeanAttribute() != null) { + drawMBeanAttributes(process, result.getMbeanAttribute()); + } + } + + private void drawMBeanAttributes(CommandProcess process, Map> mbeanAttributeMap) { + for (Map.Entry> entry : mbeanAttributeMap.entrySet()) { + String objectName = entry.getKey(); + List attributeVOList = entry.getValue(); + + TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); + table.row(true, "OBJECT_NAME", objectName); + table.row(true, label("NAME").style(Decoration.bold.bold()), + label("VALUE").style(Decoration.bold.bold())); + + for (MBeanAttributeVO attributeVO : attributeVOList) { + String attributeName = attributeVO.getName(); + String valueStr; + if (attributeVO.getError() != null) { + valueStr = RenderUtil.render(new LabelElement(attributeVO.getError()).style(Decoration.bold_off.fg(Color.red))); + } else { + //convert array to list + // TODO support all array type + Object value = attributeVO.getValue(); + if (value instanceof String[]) { + value = Arrays.asList((String[]) value); + } else if (value instanceof Integer[]) { + value = Arrays.asList((Integer[]) value); + } else if (value instanceof Long[]) { + value = Arrays.asList((Long[]) value); + } else if (value instanceof int[]) { + value = convertArrayToList((int[]) value); + } else if (value instanceof long[]) { + value = convertArrayToList((long[]) value); + } + //to string + valueStr = String.valueOf(value); + } + table.row(attributeName, valueStr); + } + process.write(RenderUtil.render(table, process.width())); + process.write("\n"); + } + } + + private List convertArrayToList(long[] longs) { + List list = new ArrayList(); + for (long aLong : longs) { + list.add(aLong); + } + return list; + } + + private List convertArrayToList(int[] ints) { + List list = new ArrayList(); + for (int anInt : ints) { + list.add(anInt); + } + return list; + } + + private void drawMBeanMetadata(CommandProcess process, Map mbeanMetadata) { + TableElement table = createTable(); + for (Map.Entry entry : mbeanMetadata.entrySet()) { + String objectName = entry.getKey(); + MBeanInfo mBeanInfo = entry.getValue(); + drawMetaInfo(mBeanInfo, objectName, table); + drawAttributeInfo(mBeanInfo.getAttributes(), table); + drawOperationInfo(mBeanInfo.getOperations(), table); + drawNotificationInfo(mBeanInfo.getNotifications(), table); + } + process.write(RenderUtil.render(table, process.width())); + + } + + private void drawMBeanNames(CommandProcess process, List mbeanNames) { + for (String mbeanName : mbeanNames) { + process.write(mbeanName).write("\n"); + } + } + + private static TableElement createTable() { + TableElement table = new TableElement().leftCellPadding(1).rightCellPadding(1); + table.row(true, label("NAME").style(Decoration.bold.bold()), + label("VALUE").style(Decoration.bold.bold())); + return table; + } + + private void drawMetaInfo(MBeanInfo mBeanInfo, String objectName, TableElement table) { + table.row(new LabelElement("MBeanInfo").style(Decoration.bold.fg(Color.red))); + table.row(new LabelElement("Info:").style(Decoration.bold.fg(Color.yellow))); + table.row("ObjectName", objectName); + table.row("ClassName", mBeanInfo.getClassName()); + table.row("Description", mBeanInfo.getDescription()); + drawDescriptorInfo("Info Descriptor:", mBeanInfo, table); + MBeanConstructorInfo[] constructors = mBeanInfo.getConstructors(); + if (constructors.length > 0) { + for (int i = 0; i < constructors.length; i++) { + table.row(new LabelElement("Constructor-" + i).style(Decoration.bold.fg(Color.yellow))); + table.row("Name", constructors[i].getName()); + table.row("Description", constructors[i].getDescription()); + } + } + } + + private void drawAttributeInfo(MBeanAttributeInfo[] attributes, TableElement table) { + for (MBeanAttributeInfo attribute : attributes) { + table.row(new LabelElement("MBeanAttributeInfo").style(Decoration.bold.fg(Color.red))); + table.row(new LabelElement("Attribute:").style(Decoration.bold.fg(Color.yellow))); + table.row("Name", attribute.getName()); + table.row("Description", attribute.getDescription()); + table.row("Readable", String.valueOf(attribute.isReadable())); + table.row("Writable", String.valueOf(attribute.isWritable())); + table.row("Is", String.valueOf(attribute.isIs())); + table.row("Type", attribute.getType()); + drawDescriptorInfo("Attribute Descriptor:", attribute, table); + } + } + + private void drawOperationInfo(MBeanOperationInfo[] operations, TableElement table) { + for (MBeanOperationInfo operation : operations) { + table.row(new LabelElement("MBeanOperationInfo").style(Decoration.bold.fg(Color.red))); + table.row(new LabelElement("Operation:").style(Decoration.bold.fg(Color.yellow))); + table.row("Name", operation.getName()); + table.row("Description", operation.getDescription()); + String impact = ""; + switch (operation.getImpact()) { + case ACTION: + impact = "action"; + break; + case ACTION_INFO: + impact = "action/info"; + break; + case INFO: + impact = "info"; + break; + case UNKNOWN: + impact = "unknown"; + break; + } + table.row("Impact", impact); + table.row("ReturnType", operation.getReturnType()); + MBeanParameterInfo[] signature = operation.getSignature(); + if (signature.length > 0) { + for (int i = 0; i < signature.length; i++) { + table.row(new LabelElement("Parameter-" + i).style(Decoration.bold.fg(Color.yellow))); + table.row("Name", signature[i].getName()); + table.row("Type", signature[i].getType()); + table.row("Description", signature[i].getDescription()); + } + } + drawDescriptorInfo("Operation Descriptor:", operation, table); + } + } + + private void drawNotificationInfo(MBeanNotificationInfo[] notificationInfos, TableElement table) { + for (MBeanNotificationInfo notificationInfo : notificationInfos) { + table.row(new LabelElement("MBeanNotificationInfo").style(Decoration.bold.fg(Color.red))); + table.row(new LabelElement("Notification:").style(Decoration.bold.fg(Color.yellow))); + table.row("Name", notificationInfo.getName()); + table.row("Description", notificationInfo.getDescription()); + table.row("NotifTypes", Arrays.toString(notificationInfo.getNotifTypes())); + drawDescriptorInfo("Notification Descriptor:", notificationInfo, table); + } + } + + private void drawDescriptorInfo(String title, DescriptorRead descriptorRead, TableElement table) { + Descriptor descriptor = descriptorRead.getDescriptor(); + String[] fieldNames = descriptor.getFieldNames(); + if (fieldNames.length > 0) { + table.row(new LabelElement(title).style(Decoration.bold.fg(Color.yellow))); + for (String fieldName : fieldNames) { + Object fieldValue = descriptor.getFieldValue(fieldName); + table.row(fieldName, fieldValue.toString()); + } + } + } + +} 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 9891883c2..4df0afbe4 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 @@ -60,8 +60,14 @@ public class ResultViewResolver { registerView(SearchMethodView.class); //logger + registerView(LoggerView.class); //monitor2000 + registerView(DashboardView.class); + registerView(JvmView.class); + registerView(MBeanView.class); + registerView(PerfCounterView.class); + registerView(ThreadView.class); } catch (Throwable e) { logger.error("register result view failed", e);