mirror of
https://github.com/alibaba/arthas.git
synced 2024-04-21 10:21:39 +00:00
improve dashboard/thread cpu time sample (#1501)
* dashboard command support native thread, delta time * thread command support --all option
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user