开篇
在elasticsearch的源码当中存在HotThreads.java这个类,里面有一些采集热点线程的方法,刚好我们组的大神本着拿来主义精神把该部分代码应用到了我们线上程序,正好拿来做个简单分享。
整个代码核心思路非常简单,通过JMX接口获取线程bean,针对所有线程通过连续采集两次数据做差值算出cpu占用耗时,最后按照cpu占用时间排序取top耗时线程。
源码分析
- 1、第一次通过threadBean.getThreadCpuTime(threadId)获取cpu耗时
- 2、 休眠一定时间Thread.sleep(interval.millis());
- 3、第二次通过threadBean.getThreadCpuTime(threadId)获取cpu耗时
- 4、计算两次采集差值获取cpu占用耗时的Top N个线程进行返回
public class HotThreads {
private static final Object mutex = new Object();
private static final FormatDateTimeFormatter DATE_TIME_FORMATTER = Joda.forPattern("dateOptionalTime");
private int busiestThreads = 3;
private TimeValue interval = new TimeValue(500, TimeUnit.MILLISECONDS);
private TimeValue threadElementsSnapshotDelay = new TimeValue(10);
private int threadElementsSnapshotCount = 10;
private String type = "cpu";
private boolean ignoreIdleThreads = true;
private String innerDetect() throws Exception {
StringBuilder sb = new StringBuilder();
// 省略一些代码
// 获取JMX提供的接口ThreadMXBean
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
boolean enabledCpu = false;
try {
if (threadBean.isThreadCpuTimeSupported()) {
if (!threadBean.isThreadCpuTimeEnabled()) {
enabledCpu = true;
threadBean.setThreadCpuTimeEnabled(true);
}
} else {
throw new IllegalStateException("MBean doesn't support thread CPU Time");
}
// 通过threadBean.getAllThreadIds()获取所有活动线程ids
Map<Long, MyThreadInfo> threadInfos = new HashMap<>();
for (long threadId : threadBean.getAllThreadIds()) {
// 排除当前线程
if (Thread.currentThread().getId() == threadId) {
continue;
}
// 获取当前线程的cpu时间占用
long cpu = threadBean.getThreadCpuTime(threadId);
if (cpu == -1) {
continue;
}
// 获取线程的线程信息
ThreadInfo info = threadBean.getThreadInfo(threadId, 0);
if (info == null) {
continue;
}
// 记录第一次采集线程的CPU耗时
threadInfos.put(threadId, new MyThreadInfo(cpu, info));
}
// 休眠一段时间500ms后再次采集
Thread.sleep(interval.millis());
// 再次遍历当前所有线程并进行采集
for (long threadId : threadBean.getAllThreadIds()) {
// ignore our own thread...
if (Thread.currentThread().getId() == threadId) {
continue;
}
// 获取当前线程的cpu时间占用
long cpu = threadBean.getThreadCpuTime(threadId);
if (cpu == -1) {
threadInfos.remove(threadId);
continue;
}
// 获取线程的线程信息
ThreadInfo info = threadBean.getThreadInfo(threadId, 0);
if (info == null) {
threadInfos.remove(threadId);
continue;
}
MyThreadInfo data = threadInfos.get(threadId);
if (data != null) {
// 计算cpu耗时的差值
data.setDelta(cpu, info);
} else {
threadInfos.remove(threadId);
}
}
// 根据cpu耗时差值进行排序,按照耗时倒序进行排列取Top N个线程
List<MyThreadInfo> hotties = new ArrayList<>(threadInfos.values());
final int busiestThreads = Math.min(this.busiestThreads, hotties.size());
CollectionUtil.introSort(hotties, new Comparator<MyThreadInfo>() {
public int compare(MyThreadInfo o1, MyThreadInfo o2) {
if ("cpu".equals(type)) {
return (int) (o2.cpuTime - o1.cpuTime);
} else if ("wait".equals(type)) {
return (int) (o2.waitedTime - o1.waitedTime);
} else if ("block".equals(type)) {
return (int) (o2.blockedTime - o1.blockedTime);
}
throw new IllegalArgumentException();
}
});
// 针对Top N线程进行快照采集,固定间隔进行进行快照采集
long[] ids = new long[busiestThreads];
for (int i = 0; i < busiestThreads; i++) {
MyThreadInfo info = hotties.get(i);
ids[i] = info.info.getThreadId();
}
ThreadInfo[][] allInfos = new ThreadInfo[threadElementsSnapshotCount][];
for (int j = 0; j < threadElementsSnapshotCount; j++) {
allInfos[j] = threadBean.getThreadInfo(ids, Integer.MAX_VALUE);
Thread.sleep(threadElementsSnapshotDelay.millis());
}
// 省略一些核心代码
return sb.toString();
} finally {
if (enabledCpu) {
threadBean.setThreadCpuTimeEnabled(false);
}
}
}
}