improve dashboard/thread cpu time sample (#1501)

* dashboard command support native thread, delta time
* thread command support --all option
This commit is contained in:
gongdewei
2020-09-22 22:18:51 +08:00
committed by GitHub
parent 4d06126b76
commit 503e0de6b4
24 changed files with 1193 additions and 609 deletions
@@ -0,0 +1,163 @@
package com.taobao.arthas.core.command.model;
import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
/**
* Busy thread info, include ThreadInfo fields
*
* @author gongdewei 2020/4/26
*/
public class BusyThreadInfo extends ThreadVO {
private long blockedTime;
private long blockedCount;
private long waitedTime;
private long waitedCount;
private LockInfo lockInfo;
private String lockName;
private long lockOwnerId;
private String lockOwnerName;
private boolean inNative;
private boolean suspended;
private StackTraceElement[] stackTrace;
private MonitorInfo[] lockedMonitors;
private LockInfo[] lockedSynchronizers;
public BusyThreadInfo(ThreadVO thread, ThreadInfo threadInfo) {
this.setId(thread.getId());
this.setName(thread.getName());
this.setDaemon(thread.isDaemon());
this.setInterrupted(thread.isInterrupted());
this.setPriority(thread.getPriority());
this.setGroup(thread.getGroup());
this.setState(thread.getState());
this.setCpu(thread.getCpu());
this.setDeltaTime(thread.getDeltaTime());
this.setTime(thread.getTime());
//thread info
if (threadInfo != null) {
this.setLockInfo(threadInfo.getLockInfo());
this.setLockedMonitors(threadInfo.getLockedMonitors());
this.setLockedSynchronizers(threadInfo.getLockedSynchronizers());
this.setLockName(threadInfo.getLockName());
this.setLockOwnerId(threadInfo.getLockOwnerId());
this.setLockOwnerName(threadInfo.getLockOwnerName());
this.setStackTrace(threadInfo.getStackTrace());
this.setBlockedCount(threadInfo.getBlockedCount());
this.setBlockedTime(threadInfo.getBlockedTime());
this.setInNative(threadInfo.isInNative());
this.setSuspended(threadInfo.isSuspended());
this.setWaitedCount(threadInfo.getWaitedCount());
this.setWaitedTime(threadInfo.getWaitedTime());
}
}
public long getBlockedTime() {
return blockedTime;
}
public void setBlockedTime(long blockedTime) {
this.blockedTime = blockedTime;
}
public long getBlockedCount() {
return blockedCount;
}
public void setBlockedCount(long blockedCount) {
this.blockedCount = blockedCount;
}
public long getWaitedTime() {
return waitedTime;
}
public void setWaitedTime(long waitedTime) {
this.waitedTime = waitedTime;
}
public long getWaitedCount() {
return waitedCount;
}
public void setWaitedCount(long waitedCount) {
this.waitedCount = waitedCount;
}
public LockInfo getLockInfo() {
return lockInfo;
}
public void setLockInfo(LockInfo lockInfo) {
this.lockInfo = lockInfo;
}
public String getLockName() {
return lockName;
}
public void setLockName(String lockName) {
this.lockName = lockName;
}
public long getLockOwnerId() {
return lockOwnerId;
}
public void setLockOwnerId(long lockOwnerId) {
this.lockOwnerId = lockOwnerId;
}
public String getLockOwnerName() {
return lockOwnerName;
}
public void setLockOwnerName(String lockOwnerName) {
this.lockOwnerName = lockOwnerName;
}
public boolean isInNative() {
return inNative;
}
public void setInNative(boolean inNative) {
this.inNative = inNative;
}
public boolean isSuspended() {
return suspended;
}
public void setSuspended(boolean suspended) {
this.suspended = suspended;
}
public StackTraceElement[] getStackTrace() {
return stackTrace;
}
public void setStackTrace(StackTraceElement[] stackTrace) {
this.stackTrace = stackTrace;
}
public MonitorInfo[] getLockedMonitors() {
return lockedMonitors;
}
public void setLockedMonitors(MonitorInfo[] lockedMonitors) {
this.lockedMonitors = lockedMonitors;
}
public LockInfo[] getLockedSynchronizers() {
return lockedSynchronizers;
}
public void setLockedSynchronizers(LockInfo[] lockedSynchronizers) {
this.lockedSynchronizers = lockedSynchronizers;
}
}
@@ -1,36 +0,0 @@
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;
}
}
@@ -18,11 +18,12 @@ public class ThreadModel extends ResultModel {
private BlockingLockInfo blockingLockInfo;
//thread -n 5
private ThreadCpuInfo[] busyThreads;
private List<BusyThreadInfo> busyThreads;
//thread stats
private List<ThreadVO> threadStats;
private Map<Thread.State, Integer> threadStateCount;
private boolean all;
public ThreadModel() {
}
@@ -35,13 +36,14 @@ public class ThreadModel extends ResultModel {
this.blockingLockInfo = blockingLockInfo;
}
public ThreadModel(ThreadCpuInfo[] busyThreads) {
public ThreadModel(List<BusyThreadInfo> busyThreads) {
this.busyThreads = busyThreads;
}
public ThreadModel(List<ThreadVO> threadStats, Map<Thread.State, Integer> threadStateCount) {
public ThreadModel(List<ThreadVO> threadStats, Map<Thread.State, Integer> threadStateCount, boolean all) {
this.threadStats = threadStats;
this.threadStateCount = threadStateCount;
this.all = all;
}
@Override
@@ -65,11 +67,11 @@ public class ThreadModel extends ResultModel {
this.blockingLockInfo = blockingLockInfo;
}
public ThreadCpuInfo[] getBusyThreads() {
public List<BusyThreadInfo> getBusyThreads() {
return busyThreads;
}
public void setBusyThreads(ThreadCpuInfo[] busyThreads) {
public void setBusyThreads(List<BusyThreadInfo> busyThreads) {
this.busyThreads = busyThreads;
}
@@ -88,4 +90,12 @@ public class ThreadModel extends ResultModel {
public void setThreadStateCount(Map<Thread.State, Integer> threadStateCount) {
this.threadStateCount = threadStateCount;
}
public boolean isAll() {
return all;
}
public void setAll(boolean all) {
this.all = all;
}
}
@@ -13,7 +13,8 @@ public class ThreadVO {
private String group;
private int priority;
private State state;
private long cpu;
private double cpu;
private long deltaTime;
private long time;
private boolean interrupted;
private boolean daemon;
@@ -61,14 +62,22 @@ public class ThreadVO {
this.state = state;
}
public long getCpu() {
public double getCpu() {
return cpu;
}
public void setCpu(long cpu) {
public void setCpu(double cpu) {
this.cpu = cpu;
}
public long getDeltaTime() {
return deltaTime;
}
public void setDeltaTime(long deltaTime) {
this.deltaTime = deltaTime;
}
public long getTime() {
return time;
}
@@ -92,4 +101,22 @@ public class ThreadVO {
public void setDaemon(boolean daemon) {
this.daemon = daemon;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ThreadVO threadVO = (ThreadVO) o;
if (id != threadVO.id) return false;
return name != null ? name.equals(threadVO.name) : threadVO.name == null;
}
@Override
public int hashCode() {
int result = (int) (id ^ (id >>> 32));
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
@@ -9,6 +9,7 @@ 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.ThreadVO;
import com.taobao.arthas.core.command.model.TomcatInfoVO;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
@@ -305,7 +306,7 @@ public class DashboardCommand extends AnnotatedCommand {
DashboardModel dashboardModel = new DashboardModel();
//thread sample
Map<String, Thread> threads = ThreadUtil.getThreads();
Map<String, ThreadVO> threads = ThreadUtil.getThreads();
dashboardModel.setThreads(threadSampler.sample(threads.values()));
//memory
@@ -2,7 +2,7 @@ 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.BusyThreadInfo;
import com.taobao.arthas.core.command.model.ThreadModel;
import com.taobao.arthas.core.command.model.ThreadVO;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
@@ -51,11 +51,12 @@ public class ThreadCommand extends AnnotatedCommand {
private long id = -1;
private Integer topNBusy = null;
private boolean findMostBlockingThread = false;
private int sampleInterval = 100;
private int sampleInterval = 200;
private String state;
private boolean lockedMonitors = false;
private boolean lockedSynchronizers = false;
private boolean all = false;
static {
states = new HashSet<String>(State.values().length);
@@ -70,6 +71,12 @@ public class ThreadCommand extends AnnotatedCommand {
this.id = id;
}
@Option(longName = "all", flag = true)
@Description("Display all thread results instead of the first page")
public void setAll(boolean all) {
this.all = all;
}
@Option(shortName = "n", longName = "top-n-threads")
@Description("The number of thread(s) to show, ordered by cpu utilization, -1 to show all.")
public void setTopNBusy(Integer topNBusy) {
@@ -122,7 +129,7 @@ public class ThreadCommand extends AnnotatedCommand {
}
private ExitStatus processAllThreads(CommandProcess process) {
Map<String, Thread> threads = ThreadUtil.getThreads();
Map<String, ThreadVO> threads = ThreadUtil.getThreads();
// 统计各种线程状态
Map<State, Integer> stateCountMap = new LinkedHashMap<State, Integer>();
@@ -130,18 +137,20 @@ public class ThreadCommand extends AnnotatedCommand {
stateCountMap.put(s, 0);
}
for (Thread thread : threads.values()) {
for (ThreadVO thread : threads.values()) {
State threadState = thread.getState();
Integer count = stateCountMap.get(threadState);
stateCountMap.put(threadState, count + 1);
}
Collection<Thread> resultThreads = new ArrayList<Thread>();
boolean includeInternalThreads = true;
Collection<ThreadVO> resultThreads = new ArrayList<ThreadVO>();
if (!StringUtils.isEmpty(this.state)) {
this.state = this.state.toUpperCase();
if (states.contains(this.state)) {
for (Thread thread : threads.values()) {
if (state.equals(thread.getState().name())) {
includeInternalThreads = false;
for (ThreadVO thread : threads.values()) {
if (thread.getState() != null && state.equals(thread.getState().name())) {
resultThreads.add(thread);
}
}
@@ -154,10 +163,12 @@ public class ThreadCommand extends AnnotatedCommand {
//thread stats
ThreadSampler threadSampler = new ThreadSampler();
threadSampler.setSampleInterval(sampleInterval);
threadSampler.setIncludeInternalThreads(includeInternalThreads);
threadSampler.sample(resultThreads);
threadSampler.pause(sampleInterval);
List<ThreadVO> threadStats = threadSampler.sample(resultThreads);
process.appendResult(new ThreadModel(threadStats, stateCountMap));
process.appendResult(new ThreadModel(threadStats, stateCountMap, all));
return ExitStatus.success();
}
@@ -171,21 +182,44 @@ public class ThreadCommand extends AnnotatedCommand {
}
private ExitStatus processTopBusyThreads(CommandProcess process) {
Map<Long, Long> 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) {
ThreadSampler threadSampler = new ThreadSampler();
threadSampler.sample(ThreadUtil.getThreads().values());
threadSampler.pause(sampleInterval);
List<ThreadVO> threadStats = threadSampler.sample(ThreadUtil.getThreads().values());
int limit = Math.min(threadStats.size(), topNBusy);
List<ThreadVO> topNThreads = threadStats.subList(0, limit);
List<Long> tids = new ArrayList<Long>(topNThreads.size());
for (ThreadVO thread : topNThreads) {
if (thread.getId() > 0) {
tids.add(thread.getId());
}
}
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(ArrayUtils.toPrimitive(tids.toArray(new Long[0])), lockedMonitors, lockedSynchronizers);
if (tids.size()> 0 && threadInfos == null) {
return ExitStatus.failure(1, "get top busy threads failed");
}
//threadInfo with cpuUsage
ThreadCpuInfo[] threadCpuInfos = new ThreadCpuInfo[threadInfos.length];
List<BusyThreadInfo> busyThreadInfos = new ArrayList<BusyThreadInfo>(topNThreads.size());
for (ThreadVO thread : topNThreads) {
ThreadInfo threadInfo = findThreadInfoById(threadInfos, thread.getId());
BusyThreadInfo busyThread = new BusyThreadInfo(thread, threadInfo);
busyThreadInfos.add(busyThread);
}
process.appendResult(new ThreadModel(busyThreadInfos));
return ExitStatus.success();
}
private ThreadInfo findThreadInfoById(ThreadInfo[] threadInfos, long id) {
for (int i = 0; i < threadInfos.length; i++) {
ThreadInfo threadInfo = threadInfos[i];
threadCpuInfos[i] = new ThreadCpuInfo(threadInfo, topNThreads.get(threadInfo.getThreadId()));
if ( threadInfo.getThreadId() == id) {
return threadInfo;
}
}
process.appendResult(new ThreadModel(threadCpuInfos));
return ExitStatus.success();
return null;
}
private ExitStatus processThread(CommandProcess process) {
@@ -1,10 +1,18 @@
package com.taobao.arthas.core.command.monitor200;
import com.taobao.arthas.core.command.model.ThreadVO;
import sun.management.HotspotThreadMBean;
import sun.management.ManagementFactoryHelper;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Thread cpu sampler
@@ -13,61 +21,112 @@ import java.util.*;
*/
public class ThreadSampler {
private long sampleInterval = 100;
private static ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
private static HotspotThreadMBean hotspotThreadMBean;
private static boolean hotspotThreadMBeanEnable = true;
public List<ThreadVO> sample(Collection<Thread> originThreads) {
private Map<ThreadVO, Long> lastCpuTimes = new HashMap<ThreadVO, Long>();
List<Thread> threads = new ArrayList<Thread>(originThreads);
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
private long lastSampleTimeNanos;
private boolean includeInternalThreads = true;
public List<ThreadVO> sample(Collection<ThreadVO> originThreads) {
List<ThreadVO> threads = new ArrayList<ThreadVO>(originThreads);
// Sample CPU
Map<Long, Long> times1 = new HashMap<Long, Long>();
for (Thread thread : threads) {
long cpu = threadMXBean.getThreadCpuTime(thread.getId());
times1.put(thread.getId(), cpu);
}
if (lastCpuTimes.isEmpty()) {
lastSampleTimeNanos = System.nanoTime();
for (ThreadVO thread : threads) {
if (thread.getId() > 0) {
long cpu = threadMXBean.getThreadCpuTime(thread.getId());
lastCpuTimes.put(thread, cpu);
thread.setTime(cpu / 1000000);
}
}
try {
Thread.sleep(sampleInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// add internal threads
Map<String, Long> internalThreadCpuTimes = getInternalThreadCpuTimes();
if (internalThreadCpuTimes != null) {
for (Map.Entry<String, Long> entry : internalThreadCpuTimes.entrySet()) {
String key = entry.getKey();
ThreadVO thread = createThreadVO(key);
thread.setTime(entry.getValue() / 1000000);
threads.add(thread);
lastCpuTimes.put(thread, entry.getValue());
}
}
//sort by time
Collections.sort(threads, new Comparator<ThreadVO>() {
@Override
public int compare(ThreadVO o1, ThreadVO o2) {
long l1 = o1.getTime();
long l2 = o2.getTime();
if (l1 < l2) {
return 1;
} else if (l1 > l2) {
return -1;
} else {
return 0;
}
}
});
return threads;
}
// Resample
Map<Long, Long> times2 = new HashMap<Long, Long>(threads.size());
for (Thread thread : threads) {
long cpu = threadMXBean.getThreadCpuTime(thread.getId());
times2.put(thread.getId(), cpu);
long newSampleTimeNanos = System.nanoTime();
Map<ThreadVO, Long> newCpuTimes = new HashMap<ThreadVO, Long>(threads.size());
for (ThreadVO thread : threads) {
if (thread.getId() > 0) {
long cpu = threadMXBean.getThreadCpuTime(thread.getId());
newCpuTimes.put(thread, cpu);
}
}
// internal threads
Map<String, Long> newInternalThreadCpuTimes = getInternalThreadCpuTimes();
if (newInternalThreadCpuTimes != null) {
for (Map.Entry<String, Long> entry : newInternalThreadCpuTimes.entrySet()) {
ThreadVO threadVO = createThreadVO(entry.getKey());
threads.add(threadVO);
newCpuTimes.put(threadVO, entry.getValue());
}
}
// Compute delta map and total time
long total = 0;
Map<Long, Long> deltas = new HashMap<Long, Long>(threads.size());
for (Long id : times2.keySet()) {
long time1 = times2.get(id);
long time2 = times1.get(id);
// Compute delta time
final Map<ThreadVO, Long> deltas = new HashMap<ThreadVO, Long>(threads.size());
for (ThreadVO thread : newCpuTimes.keySet()) {
Long t = lastCpuTimes.get(thread);
if (t == null) {
t = 0L;
}
long time1 = t;
long time2 = newCpuTimes.get(thread);
if (time1 == -1) {
time1 = time2;
} else if (time2 == -1) {
time2 = time1;
}
long delta = time2 - time1;
deltas.put(id, delta);
total += delta;
deltas.put(thread, delta);
}
// Compute cpu
final HashMap<Thread, Long> cpus = new HashMap<Thread, Long>(threads.size());
for (Thread thread : threads) {
long cpu = total == 0 ? 0 : Math.round((deltas.get(thread.getId()) * 100) / total);
cpus.put(thread, cpu);
long sampleIntervalNanos = newSampleTimeNanos - lastSampleTimeNanos;
// Compute cpu usage
final HashMap<ThreadVO, Double> cpuUsages = new HashMap<ThreadVO, Double>(threads.size());
for (ThreadVO thread : threads) {
double cpu = sampleIntervalNanos == 0 ? 0 : (deltas.get(thread) * 10000 / sampleIntervalNanos / 100.0);
cpuUsages.put(thread, cpu);
}
// Sort by CPU time : should be a rendering hint...
Collections.sort(threads, new Comparator<Thread>() {
public int compare(Thread o1, Thread o2) {
long l1 = cpus.get(o1);
long l2 = cpus.get(o2);
Collections.sort(threads, new Comparator<ThreadVO>() {
public int compare(ThreadVO o1, ThreadVO o2) {
long l1 = deltas.get(o1);
long l2 = deltas.get(o2);
if (l1 < l2) {
return 1;
} else if (l1 > l2) {
@@ -78,34 +137,61 @@ public class ThreadSampler {
}
});
List<ThreadVO> threadVOList = new ArrayList<ThreadVO>(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);
for (ThreadVO thread : threads) {
//nanos to mills
long timeMills = newCpuTimes.get(thread) / 1000000;
long deltaTime = deltas.get(thread) / 1000000;
double cpu = cpuUsages.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);
thread.setCpu(cpu);
thread.setTime(timeMills);
thread.setDeltaTime(deltaTime);
}
return threadVOList;
lastCpuTimes = newCpuTimes;
lastSampleTimeNanos = newSampleTimeNanos;
return threads;
}
public long getSampleInterval() {
return sampleInterval;
private Map<String, Long> getInternalThreadCpuTimes() {
if (hotspotThreadMBeanEnable && includeInternalThreads) {
try {
if (hotspotThreadMBean == null) {
hotspotThreadMBean = ManagementFactoryHelper.getHotspotThreadMBean();
}
return hotspotThreadMBean.getInternalThreadCpuTimes();
} catch (Throwable e) {
//ignore ex
hotspotThreadMBeanEnable = false;
}
}
return null;
}
public void setSampleInterval(long sampleInterval) {
this.sampleInterval = sampleInterval;
private ThreadVO createThreadVO(String name) {
ThreadVO threadVO = new ThreadVO();
threadVO.setId(-1);
threadVO.setName(name);
threadVO.setPriority(-1);
threadVO.setDaemon(true);
threadVO.setInterrupted(false);
return threadVO;
}
public void pause(long mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
// ignore
}
}
public boolean isIncludeInternalThreads() {
return includeInternalThreads;
}
public void setIncludeInternalThreads(boolean includeInternalThreads) {
this.includeInternalThreads = includeInternalThreads;
}
}
@@ -28,37 +28,61 @@ public class DashboardView extends ResultView<DashboardModel> {
// 上半部分放thread top。下半部分再切分为田字格,其中上面两格放memory, gc的信息。下面两格放tomcat,
// runtime的信息
int totalHeight = height - 1;
int threadTopHeight = totalHeight / 2;
int threadTopHeight;
if (totalHeight <= 24) {
//总高度较小时取1/2
threadTopHeight = totalHeight / 2;
} else {
//总高度较大时取1/3,但不少于上面的值(24/2=12)
threadTopHeight = totalHeight / 3;
if (threadTopHeight < 12) {
threadTopHeight = 12;
}
}
int lowerHalf = totalHeight - threadTopHeight;
int runtimeInfoHeight = lowerHalf / 2;
int heapInfoHeight = lowerHalf - runtimeInfoHeight;
//Memory至少保留8行, 显示metaspace信息
int memoryInfoHeight = lowerHalf / 2;
if (memoryInfoHeight < 8) {
memoryInfoHeight = Math.min(8, lowerHalf);
}
//runtime
TableElement runtimeInfoTable = drawRuntimeInfo(result.getRuntimeInfo());
//tomcat
TableElement tomcatInfoTable = drawTomcatInfo(result.getTomcatInfo());
int runtimeInfoHeight = Math.max(runtimeInfoTable.getRows().size(), tomcatInfoTable == null ? 0 : tomcatInfoTable.getRows().size());
if (runtimeInfoHeight < lowerHalf - memoryInfoHeight) {
//如果runtimeInfo高度有剩余,则增大MemoryInfo的高度
memoryInfoHeight = lowerHalf - runtimeInfoHeight;
} else {
runtimeInfoHeight = lowerHalf - memoryInfoHeight;
}
//如果MemoryInfo高度有剩余,则增大ThreadHeight
int maxMemoryInfoHeight = getMemoryInfoHeight(result.getMemoryInfo());
memoryInfoHeight = Math.min(memoryInfoHeight, maxMemoryInfoHeight);
threadTopHeight = totalHeight - memoryInfoHeight - 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);
String memoryAndGc = drawMemoryInfoAndGcInfo(result.getMemoryInfo(), result.getGcInfos(), width, memoryInfoHeight);
String runTimeAndTomcat = drawRuntimeInfoAndTomcatInfo(runtimeInfoTable, tomcatInfoTable, width, runtimeInfoHeight);
process.write(threadInfo + memoryAndGc + runTimeAndTomcat);
}
static String drawMemoryInfoAndGcInfo(Map<String, List<MemoryEntryVO>> memoryInfo, List<GcInfoVO> 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);
TableElement memoryInfoTable = drawMemoryInfo(memoryInfo);
TableElement gcInfoTable = drawGcInfo(gcInfos);
table.row(memoryInfoTable, gcInfoTable);
return RenderUtil.render(table, width, height);
}
private static void drawMemoryInfo(TableElement table, Map<String, List<MemoryEntryVO>> memoryInfo) {
private static TableElement drawMemoryInfo(Map<String, List<MemoryEntryVO>> memoryInfo) {
TableElement table = new TableElement(3, 1, 1, 1, 1).rightCellPadding(1);
table.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("Memory",
"used", "total", "max", "usage"));
List<MemoryEntryVO> heapMemoryEntries = memoryInfo.get(MemoryEntryVO.TYPE_HEAP);
//heap memory
for (MemoryEntryVO memoryEntryVO : heapMemoryEntries) {
@@ -81,26 +105,38 @@ public class DashboardView extends ResultView<DashboardModel> {
//buffer-pool
List<MemoryEntryVO> bufferPoolMemoryEntries = memoryInfo.get(MemoryEntryVO.TYPE_BUFFER_POOL);
for (MemoryEntryVO memoryEntryVO : bufferPoolMemoryEntries) {
new MemoryEntry(memoryEntryVO).addTableRow(table);
if (bufferPoolMemoryEntries != null) {
for (MemoryEntryVO memoryEntryVO : bufferPoolMemoryEntries) {
new MemoryEntry(memoryEntryVO).addTableRow(table);
}
}
return table;
}
private static void drawGcInfo(TableElement table, List<GcInfoVO> gcInfos) {
private static int getMemoryInfoHeight(Map<String, List<MemoryEntryVO>> memoryInfo) {
int height = 1;
for (List<MemoryEntryVO> memoryEntryVOS : memoryInfo.values()) {
height += memoryEntryVOS.size();
}
return height;
}
private static TableElement drawGcInfo(List<GcInfoVO> gcInfos) {
TableElement table = new TableElement(1, 1).rightCellPadding(1);
table.add(new RowElement().style(Decoration.bold.fg(Color.black).bg(Color.white)).add("GC", ""));
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());
}
return table;
}
String drawRuntimeInfoAndTomcatInfo(RuntimeInfoVO runtimeInfo, TomcatInfoVO tomcatInfo, int width, int height) {
String drawRuntimeInfoAndTomcatInfo(TableElement runtimeInfoTable, TableElement tomcatInfoTable, int width, int height) {
if (height <= 0) {
return "";
}
TableElement resultTable = new TableElement(1, 1);
//runtime
TableElement runtimeInfoTable = drawRuntimeInfo(runtimeInfo);
//tomcat
TableElement tomcatInfoTable = drawTomcatInfo(tomcatInfo);
if (tomcatInfoTable != null) {
resultTable.row(runtimeInfoTable, tomcatInfoTable);
} else {
@@ -211,13 +247,17 @@ public class DashboardView extends ResultView<DashboardModel> {
public void addTableRow(TableElement table) {
double usage = used / (double) (max == -1 || max == Long.MIN_VALUE ? total : max) * 100;
if (Double.isNaN(usage) || Double.isInfinite(usage)) {
usage = 0;
}
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;
if (Double.isNaN(usage) || Double.isInfinite(usage)) {
usage = 0;
}
table.add(new RowElement().style(style).add(name, format(used), format(total), format(max),
String.format("%.2f%%", usage)));
}
@@ -1,6 +1,6 @@
package com.taobao.arthas.core.command.view;
import com.taobao.arthas.core.command.model.ThreadCpuInfo;
import com.taobao.arthas.core.command.model.BusyThreadInfo;
import com.taobao.arthas.core.command.model.ThreadModel;
import com.taobao.arthas.core.command.model.ThreadVO;
import com.taobao.arthas.core.shell.command.CommandProcess;
@@ -23,12 +23,12 @@ public class ThreadView extends ResultView<ThreadModel> {
public void draw(CommandProcess process, ThreadModel result) {
if (result.getThreadInfo() != null) {
// no cpu usage info
String content = ThreadUtil.getFullStacktrace(result.getThreadInfo(), -1);
String content = ThreadUtil.getFullStacktrace(result.getThreadInfo());
process.write(content);
} else if (result.getBusyThreads() != null) {
ThreadCpuInfo[] threadInfos = result.getBusyThreads();
for (ThreadCpuInfo info : threadInfos) {
String stacktrace = ThreadUtil.getFullStacktrace(info.threadInfo(), info.getCpuUsage());
List<BusyThreadInfo> threadInfos = result.getBusyThreads();
for (BusyThreadInfo info : threadInfos) {
String stacktrace = ThreadUtil.getFullStacktrace(info, -1, -1);
process.write(stacktrace).write("\n");
}
} else if (result.getBlockingLockInfo() != null) {
@@ -44,6 +44,15 @@ public class ThreadView extends ResultView<ThreadModel> {
for (Integer value : threadStateCount.values()) {
total += value;
}
int internalThreadCount = 0;
for (ThreadVO thread : threadStats) {
if (thread.getId() <= 0) {
internalThreadCount += 1;
}
}
total += internalThreadCount;
StringBuilder threadStat = new StringBuilder();
threadStat.append("Threads Total: ").append(total);
@@ -51,12 +60,20 @@ public class ThreadView extends ResultView<ThreadModel> {
Integer count = threadStateCount.get(s);
threadStat.append(", ").append(s.name()).append(": ").append(count);
}
if (internalThreadCount > 0) {
threadStat.append(", Internal threads: ").append(internalThreadCount);
}
String stat = RenderUtil.render(new LabelElement(threadStat), process.width());
//thread stats
int height = Math.max(5, process.height() - 2);
//remove blank lines
height = Math.min(height, threadStats.size() + 2);
int height;
if (result.isAll()) {
height = threadStats.size() + 1;
} else {
height = Math.max(5, process.height() - 2);
//remove blank lines
height = Math.min(height, threadStats.size() + 2);
}
String content = ViewRenderUtil.drawThreadInfo(threadStats, process.width(), height);
process.write(stat + content);
}
@@ -105,7 +105,7 @@ public class ViewRenderUtil {
}
public static String drawThreadInfo(List<ThreadVO> threads, int width, int height) {
TableElement table = new TableElement(1, 3, 2, 1, 1, 1, 1, 1, 1).overflow(Overflow.HIDDEN).rightCellPadding(1);
TableElement table = new TableElement(1, 6, 3, 2, 2, 2, 2, 2, 2, 2).overflow(Overflow.HIDDEN).rightCellPadding(1);
// Header
table.add(
@@ -116,6 +116,7 @@ public class ViewRenderUtil {
"PRIORITY",
"STATE",
"%CPU",
"DELTA_TIME",
"TIME",
"INTERRUPTED",
"DAEMON"
@@ -124,22 +125,28 @@ public class ViewRenderUtil {
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();
String time = formatTimeMills(thread.getTime());
String deltaTime = formatTimeMillsToSeconds(thread.getDeltaTime());
double cpu = thread.getCpu();
LabelElement daemonLabel = new LabelElement(thread.isDaemon());
if (!thread.isDaemon()) {
daemonLabel.setStyle(Style.style(Color.magenta));
}
LabelElement stateElement;
if (thread.getState() != null) {
stateElement = new LabelElement(thread.getState()).style(color.fg());
} else {
stateElement = new LabelElement("-");
}
table.row(
new LabelElement(thread.getId()),
new LabelElement(thread.getName()),
new LabelElement(thread.getGroup()),
new LabelElement(thread.getGroup() != null ? thread.getGroup() : "-"),
new LabelElement(thread.getPriority()),
new LabelElement(thread.getState()).style(color.fg()),
stateElement,
new LabelElement(cpu),
new LabelElement(deltaTime),
new LabelElement(time),
new LabelElement(thread.isInterrupted()),
daemonLabel
@@ -147,4 +154,18 @@ public class ViewRenderUtil {
}
return RenderUtil.render(table, width, height);
}
private static String formatTimeMills(long timeMills) {
long seconds = timeMills / 1000;
long mills = timeMills % 1000;
long min = seconds / 60;
//return min + ":" + (seconds % 60);
return String.format("%d:%d.%03d", min, seconds, mills);
}
private static String formatTimeMillsToSeconds(long timeMills) {
long seconds = timeMills / 1000;
long mills = timeMills % 1000;
return String.format("%d.%03d", seconds, mills);
}
}
@@ -1,8 +1,10 @@
package com.taobao.arthas.core.util;
import com.taobao.arthas.core.command.model.BlockingLockInfo;
import com.taobao.arthas.core.command.model.BusyThreadInfo;
import com.taobao.arthas.core.command.model.StackModel;
import com.taobao.arthas.core.command.model.ThreadNode;
import com.taobao.arthas.core.command.model.ThreadVO;
import com.taobao.arthas.core.view.Ansi;
import java.arthas.SpyAPI;
@@ -38,13 +40,13 @@ abstract public class ThreadUtil {
*
* @return
*/
public static Map<String, Thread> getThreads() {
public static Map<String, ThreadVO> getThreads() {
ThreadGroup root = getRoot();
Thread[] threads = new Thread[root.activeCount()];
while (root.enumerate(threads, true) == threads.length) {
threads = new Thread[threads.length * 2];
}
SortedMap<String, Thread> map = new TreeMap<String, Thread>(new Comparator<String>() {
SortedMap<String, ThreadVO> map = new TreeMap<String, ThreadVO>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
@@ -52,12 +54,26 @@ abstract public class ThreadUtil {
});
for (Thread thread : threads) {
if (thread != null) {
map.put(thread.getName() + "-" + thread.getId(), thread);
ThreadVO threadVO = createThreadVO(thread);
map.put(thread.getName() + "-" + thread.getId(), threadVO);
}
}
return map;
}
private static ThreadVO createThreadVO(Thread thread) {
ThreadGroup group = thread.getThreadGroup();
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.setInterrupted(thread.isInterrupted());
threadVO.setDaemon(thread.isDaemon());
return threadVO;
}
/**
* 获取所有线程List
*
@@ -78,89 +94,6 @@ abstract public class ThreadUtil {
return result;
}
/**
* get the top N busy thread
* @param sampleInterval the interval between two samples
* @param topN the number of thread
* @return a Map representing <ThreadID, cpuUsage>
*/
public static Map<Long, Long> getTopNThreads(int sampleInterval, int topN) {
List<Thread> threads = getThreadList();
// Sample CPU
Map<Long, Long> times1 = new HashMap<Long, Long>();
for (Thread thread : threads) {
long cpu = threadMXBean.getThreadCpuTime(thread.getId());
times1.put(thread.getId(), cpu);
}
try {
// Sleep for some time
Thread.sleep(sampleInterval);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// Resample
Map<Long, Long> times2 = new HashMap<Long, Long>(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<Long, Long> deltas = new HashMap<Long, Long>(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<Thread, Long> cpus = new HashMap<Thread, Long>(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<Thread>() {
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;
}
}
});
// use LinkedHashMap to preserve insert order
Map<Long, Long> topNThreads = new LinkedHashMap<Long, Long>();
List<Thread> topThreads = topN > 0 && topN <= threads.size()
? threads.subList(0, topN) : threads;
for (Thread thread: topThreads) {
// Compute cpu usage
topNThreads.put(thread.getId(), cpus.get(thread));
}
return topNThreads;
}
/**
* Find the thread and lock that is blocking the most other threads.
@@ -233,13 +166,12 @@ abstract public class ThreadUtil {
}
public static String getFullStacktrace(ThreadInfo threadInfo, long cpuUsage) {
return getFullStacktrace(threadInfo, cpuUsage, 0, 0);
public static String getFullStacktrace(ThreadInfo threadInfo) {
return getFullStacktrace(threadInfo, -1, -1, -1, 0, 0);
}
public static String getFullStacktrace(BlockingLockInfo blockingLockInfo) {
return getFullStacktrace(blockingLockInfo.getThreadInfo(), -1, blockingLockInfo.getLockIdentityHashCode(),
return getFullStacktrace(blockingLockInfo.getThreadInfo(), -1, -1, -1, blockingLockInfo.getLockIdentityHashCode(),
blockingLockInfo.getBlockingThreadCount());
}
@@ -252,7 +184,7 @@ abstract public class ThreadUtil {
* @param blockingThreadCount 阻塞了其他线程的数量
* @return the string representation of the thread stack
*/
public static String getFullStacktrace(ThreadInfo threadInfo, long cpuUsage, int lockIdentityHashCode,
public static String getFullStacktrace(ThreadInfo threadInfo, double cpuUsage, long deltaTime, long time, int lockIdentityHashCode,
int blockingThreadCount) {
StringBuilder sb = new StringBuilder("\"" + threadInfo.getThreadName() + "\"" + " Id="
+ threadInfo.getThreadId());
@@ -260,6 +192,12 @@ abstract public class ThreadUtil {
if (cpuUsage >= 0 && cpuUsage <= 100) {
sb.append(" cpuUsage=").append(cpuUsage).append("%");
}
if (deltaTime >= 0 ) {
sb.append(" deltaTime=").append(deltaTime).append("ms");
}
if (time >= 0 ) {
sb.append(" time=").append(time).append("ms");
}
sb.append(" ").append(threadInfo.getThreadState());
@@ -334,6 +272,102 @@ abstract public class ThreadUtil {
return sb.toString().replace("\t", " ");
}
public static String getFullStacktrace(BusyThreadInfo threadInfo, int lockIdentityHashCode, int blockingThreadCount) {
StringBuilder sb = new StringBuilder("\"" + threadInfo.getName() + "\"");
if (threadInfo.getId() > 0) {
sb.append(" Id=").append(threadInfo.getId());
} else {
sb.append(" [Internal]");
}
double cpuUsage = threadInfo.getCpu();
if (cpuUsage >= 0 && cpuUsage <= 100) {
sb.append(" cpuUsage=").append(cpuUsage).append("%");
}
if (threadInfo.getDeltaTime() >= 0 ) {
sb.append(" deltaTime=").append(threadInfo.getDeltaTime()).append("ms");
}
if (threadInfo.getTime() >= 0 ) {
sb.append(" time=").append(threadInfo.getTime()).append("ms");
}
if (threadInfo.getState() == null) {
sb.append("\n\n");
return sb.toString();
}
sb.append(" ").append(threadInfo.getState());
if (threadInfo.getLockName() != null) {
sb.append(" on ").append(threadInfo.getLockName());
}
if (threadInfo.getLockOwnerName() != null) {
sb.append(" owned by \"").append(threadInfo.getLockOwnerName()).append("\" Id=").append(threadInfo.getLockOwnerId());
}
if (threadInfo.isSuspended()) {
sb.append(" (suspended)");
}
if (threadInfo.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
int i = 0;
for (; i < threadInfo.getStackTrace().length; i++) {
StackTraceElement ste = threadInfo.getStackTrace()[i];
sb.append("\tat ").append(ste.toString());
sb.append('\n');
if (i == 0 && threadInfo.getLockInfo() != null) {
Thread.State ts = threadInfo.getState();
switch (ts) {
case BLOCKED:
sb.append("\t- blocked on ").append(threadInfo.getLockInfo());
sb.append('\n');
break;
case WAITING:
sb.append("\t- waiting on ").append(threadInfo.getLockInfo());
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("\t- waiting on ").append(threadInfo.getLockInfo());
sb.append('\n');
break;
default:
}
}
for (MonitorInfo mi : threadInfo.getLockedMonitors()) {
if (mi.getLockedStackDepth() == i) {
sb.append("\t- locked ").append(mi);
if (mi.getIdentityHashCode() == lockIdentityHashCode) {
Ansi highlighted = Ansi.ansi().fg(Ansi.Color.RED);
highlighted.a(" <---- but blocks ").a(blockingThreadCount).a(" other threads!");
sb.append(highlighted.reset().toString());
}
sb.append('\n');
}
}
}
if (i < threadInfo.getStackTrace().length) {
sb.append("\t...");
sb.append('\n');
}
LockInfo[] locks = threadInfo.getLockedSynchronizers();
if (locks.length > 0) {
sb.append("\n\tNumber of locked synchronizers = ").append(locks.length);
sb.append('\n');
for (LockInfo li : locks) {
sb.append("\t- ").append(li);
if (li.getIdentityHashCode() == lockIdentityHashCode) {
sb.append(" <---- but blocks ").append(blockingThreadCount);
sb.append(" other threads!");
}
sb.append('\n');
}
}
sb.append('\n');
return sb.toString().replace("\t", " ");
}
/**
* </pre>
* java.lang.Thread.getStackTrace(Thread.java:1559),