From 5b7603eebe0becc215a1e45c4125223eaefc2ff0 Mon Sep 17 00:00:00 2001 From: gongdewei Date: Tue, 14 Jul 2020 20:15:03 +0800 Subject: [PATCH] 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),