/**
* Copyright 2013 Alexey Ragozin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gridkit.jvmtool;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.gridkit.jvmtool.stacktrace.ThreadMXBeanEx;
import org.gridkit.util.formating.Formats;
/**
* Thread CPU tracker.
*
* @author Alexey Ragozin (alexey.ragozin@gmail.com)
*/
public class MBeanCpuUsageReporter {
private static final ObjectName THREADING_MBEAN = name("java.lang:type=Threading");
private static ObjectName name(String name) {
try {
return new ObjectName(name);
} catch (MalformedObjectNameException e) {
throw new RuntimeException(e);
}
}
private MBeanServerConnection mserver;
private ThreadMXBean mbean;
private long lastTimestamp;
private long lastProcessCpuTime;
private long lastProcessOSCpuTime;
private long lastProcessOSSysTime;
private long lastYougGcTime;
private long lastOldGcTime;
private long lastSafePointCount;
private long lastSafePointTime;
private long lastSafePointSyncTime;
private BigInteger lastCummulativeCpuTime = BigInteger.valueOf(0);
private BigInteger lastCummulativeUserTime = BigInteger.valueOf(0);
private BigInteger lastCummulativeAllocatedAmount = BigInteger.valueOf(0);
private Map<Long, ThreadTrac> threadDump = new HashMap<Long, ThreadTrac>();
private Map<Long, ThreadNote> notes = new HashMap<Long, ThreadNote>();
private List<Comparator<ThreadLine>> comparators = new ArrayList<Comparator<ThreadLine>>();
private int topLimit = Integer.MAX_VALUE;
private Pattern filter;
private boolean bulkCpuEnabled;
private boolean threadAllocatedMemoryEnabled;
private GcCpuUsageMonitor gcMon;
private SafePointMonitor spMon;
private NativeThreadMonitor ntMon;
public MBeanCpuUsageReporter(MBeanServerConnection mserver) {
this.mserver = mserver;
this.mbean = ThreadMXBeanEx.BeanHelper.connectThreadMXBean(mserver);
threadAllocatedMemoryEnabled = getThreadingMBeanCapability("ThreadAllocatedMemoryEnabled");
bulkCpuEnabled = verifyBulkCpu();
lastTimestamp = System.nanoTime();
lastProcessCpuTime = getProcessCpuTime();
}
public void setGcCpuUsageMonitor(GcCpuUsageMonitor gcMon) {
this.gcMon = gcMon;
}
public void setSafePointMonitor(SafePointMonitor spMon) {
this.spMon = spMon;
}
public void setNativeThreadMonitor(NativeThreadMonitor ntMon) {
this.ntMon = ntMon;
}
private boolean getThreadingMBeanCapability(String attrName) {
try {
Object val = mserver.getAttribute(THREADING_MBEAN, attrName);
return Boolean.TRUE.equals(val);
}
catch(Exception e) {
return false;
}
}
private boolean verifyBulkCpu() {
try {
long[] ids = mbean.getAllThreadIds();
((ThreadMXBeanEx)mbean).getThreadCpuTime(ids);
((ThreadMXBeanEx)mbean).getThreadUserTime(ids);
return true;
} catch (Exception e) {
return false;
}
}
public void sortByThreadName() {
comparators.add(0, new ThreadNameComparator());
}
public void sortByUserCpu() {
comparators.add(0, new UserTimeComparator());
}
public void sortBySysCpu() {
comparators.add(0, new SysTimeComparator());
}
public void sortByTotalCpu() {
comparators.add(0, new CpuTimeComparator());
}
public void sortByAllocRate() {
comparators.add(0, new AllocRateComparator());
}
public void setTopLimit(int n) {
topLimit = n;
}
public void setThreadFilter(Pattern regEx) {
filter = regEx;
}
public void probe() {
try {
long[] ids = mbean.getAllThreadIds();
ThreadInfo[] ti = mbean.getThreadInfo(ids);
Map<Long, ThreadInfo> buf = new HashMap<Long, ThreadInfo>();
for(ThreadInfo t: ti) {
if (t != null) {
buf.put(t.getThreadId(), t);
}
}
for(Long key: threadDump.keySet()) {
ThreadTrac tt = threadDump.get(key);
ThreadInfo t = buf.remove(key);
if (t != null) {
tt.name = t.getThreadName();
tt.lastThreadInfo = t;
}
else {
tt.dead = true;
}
}
for(ThreadInfo t: buf.values()) {
ThreadTrac tt = new ThreadTrac();
tt.name = t.getThreadName();
tt.lastThreadInfo = t;
threadDump.put(t.getThreadId(), tt);
}
if (threadAllocatedMemoryEnabled) {
long[] alloc = ((ThreadMXBeanEx)mbean).getThreadAllocatedBytes(ids);
for (int i = 0 ; i != ids.length; ++i) {
if (threadDump.get(ids[i]) == null) {
continue;
}
threadDump.get(ids[i]).lastAllocatedBytes = alloc[i];
}
}
if (bulkCpuEnabled) {
long[] cpu = ((ThreadMXBeanEx)mbean).getThreadCpuTime(ids);
long[] usr = ((ThreadMXBeanEx)mbean).getThreadUserTime(ids);
for (int i = 0 ; i != ids.length; ++i) {
if (threadDump.get(ids[i]) == null) {
continue;
}
threadDump.get(ids[i]).lastCpuTime = cpu[i];
threadDump.get(ids[i]).lastUserTime = usr[i];
}
}
else {
for(long id: ids) {
if (threadDump.get(id) == null) {
continue;
}
ThreadTrac tt = threadDump.get(id);
tt.lastCpuTime = mbean.getThreadCpuTime(id);
tt.lastUserTime = mbean.getThreadCpuTime(id);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void cleanDead() {
Iterator<ThreadTrac> it = threadDump.values().iterator();
while(it.hasNext()) {
ThreadTrac tt = it.next();
if (tt.dead) {
it.remove();
}
}
}
public String report() {
probe();
StringBuilder sb = new StringBuilder();
long currentTime = System.nanoTime();
long timeSplit = currentTime - lastTimestamp;
long currentCpuTime = getProcessCpuTime();
long currentOsSysTime = ntMon == null ? 0 : ntMon.getProcessSysCPU();
long currentOsCpuTime = ntMon == null ? 0 : ntMon.getProcessCPU();
long currentYoungGcTime = gcMon == null ? 0 : gcMon.getYoungGcCpu();
long currentOldGcTime = gcMon == null ? 0 : gcMon.getOldGcCpu();
long currentSafePointCount = spMon == null ? 0 : spMon.getSafePointCount();
long currentSafePointTime = spMon == null ? 0 : spMon.getSafePointTime();
long currentSafePointSyncTime = spMon == null ? 0 : spMon.getSafePointSyncTime();
Map<Long,ThreadNote> newNotes = new HashMap<Long, ThreadNote>();
BigInteger deltaCpu = BigInteger.valueOf(0);
BigInteger deltaUser = BigInteger.valueOf(0);
BigInteger deltaAlloc = BigInteger.valueOf(0);
List<ThreadLine> table = new ArrayList<ThreadLine>();
for(long tid: getAllThreadIds()) {
String threadName = getThreadName(tid);
ThreadNote lastNote = notes.get(tid);
ThreadNote newNote = new ThreadNote();
newNote.lastCpuTime = getThreadCpuTime(tid);
newNote.lastUserTime = getThreadUserTime(tid);
newNote.lastAllocatedBytes = getThreadAllocatedBytes(tid);
newNotes.put(tid, newNote);
long lastCpu = lastNote == null ? 0 : lastNote.lastCpuTime;
long lastUser = lastNote == null ? 0 : lastNote.lastUserTime;
long lastAlloc = lastNote == null ? 0 : lastNote.lastAllocatedBytes;
deltaCpu = deltaCpu.add(BigInteger.valueOf(newNote.lastCpuTime - lastCpu));
deltaUser = deltaUser.add(BigInteger.valueOf(newNote.lastUserTime - lastUser));
deltaAlloc = deltaAlloc.add(BigInteger.valueOf(newNote.lastAllocatedBytes - lastAlloc));
if (lastNote != null) {
if (filter != null && !filter.matcher(threadName).matches()) {
continue;
}
double cpuT = ((double)(newNote.lastCpuTime - lastNote.lastCpuTime)) / timeSplit;
double userT = ((double)(newNote.lastUserTime - lastNote.lastUserTime)) / timeSplit;
double allocRate = ((double)(newNote.lastAllocatedBytes - lastNote.lastAllocatedBytes)) * TimeUnit.SECONDS.toNanos(1) / timeSplit;
table.add(new ThreadLine(tid, 100 * userT, 100 * (cpuT - userT), allocRate, getThreadName(tid)));
}
}
int threadCount = table.size();
if (table.size() >0) {
for(Comparator<ThreadLine> cmp: comparators) {
Collections.sort(table, cmp);
}
if (table.size() > topLimit) {
table = table.subList(0, topLimit);
}
double processT = ((double)(currentCpuTime - lastProcessCpuTime)) / timeSplit;
double cpuT = ((double)(deltaCpu.longValue())) / timeSplit;
double userT = ((double)(deltaUser.longValue())) / timeSplit;
double allocRate = ((double)(deltaAlloc.longValue())) * TimeUnit.SECONDS.toNanos(1) / timeSplit;
double youngGcT = ((double)currentYoungGcTime - lastYougGcTime) / timeSplit;
double oldGcT = ((double)currentOldGcTime - lastOldGcTime) / timeSplit;
String osproccpu = "";
if (currentOsCpuTime > 0) {
double processCpuT = ((double)(currentOsCpuTime - lastProcessOSCpuTime)) / TimeUnit.NANOSECONDS.toMicros(timeSplit);
double processSysT = ((double)(currentOsSysTime - lastProcessOSSysTime)) / TimeUnit.NANOSECONDS.toMicros(timeSplit);
osproccpu = String.format(" (OS usr+sys: %.2f%% sys: %.2f%%)", processCpuT, processSysT);
}
sb.append(Formats.toDatestamp(System.currentTimeMillis()));
sb.append(String.format(" Process summary \n process cpu=%.2f%%%s\n application cpu=%.2f%% (user=%.2f%% sys=%.2f%%)\n other: cpu=%.2f%% \n", 100 * processT, osproccpu, 100 * cpuT, 100 * userT, 100 * (cpuT - userT), 100 * (processT - cpuT), threadCount));
int osthreadcount = ntMon == null ? 0 : ntMon.getThreadsForProcess().length;
if (osthreadcount > threadCount) {
sb.append(String.format(" thread count: %d (OS threads: %d)\n", threadCount, osthreadcount));
}
else {
sb.append(String.format(" thread count: %d\n", threadCount));
}
if (currentYoungGcTime > 0) {
sb.append(String.format(" GC time=%.2f%% (young=%.2f%%, old=%.2f%%)\n", 100 * (youngGcT + oldGcT), 100 * youngGcT, 100 * oldGcT));
}
if (threadAllocatedMemoryEnabled) {
sb.append(String.format(" heap allocation rate %sb/s\n", Formats.toMemorySize((long) allocRate)));
}
if (currentSafePointCount > 0) {
if (currentSafePointCount == lastSafePointCount) {
sb.append(String.format(" no safe points\n"));
}
else {
double spRate = (TimeUnit.SECONDS.toNanos(1) * (double)(currentSafePointCount - lastSafePointCount)) / timeSplit;
double spCpuUsage = ((double)(currentSafePointTime - lastSafePointTime)) / timeSplit;
double spSyncCpuUsage = ((double)(currentSafePointSyncTime - lastSafePointSyncTime)) / timeSplit;
double spAvg = ((double)(currentSafePointTime + currentSafePointSyncTime - lastSafePointTime - lastSafePointSyncTime)) / (currentSafePointCount - lastSafePointCount) / TimeUnit.MILLISECONDS.toNanos(1);
sb.append(String.format(" safe point rate: %.1f (events/s) avg. safe point pause: %.2fms\n", spRate, spAvg));
sb.append(String.format(" safe point sync time: %.2f%% processing time: %.2f%% (wallclock time)\n", 100 * spSyncCpuUsage, 100 * spCpuUsage));
}
}
for(ThreadLine line: table) {
sb.append(format(line)).append('\n');
}
sb.append("\n");
}
lastTimestamp = currentTime;
notes = newNotes;
lastCummulativeCpuTime = lastCummulativeCpuTime.add(deltaCpu);
lastCummulativeUserTime = lastCummulativeUserTime.add(deltaUser);
lastCummulativeAllocatedAmount = lastCummulativeAllocatedAmount.add(deltaAlloc);
lastProcessCpuTime = currentCpuTime;
lastProcessOSCpuTime = currentOsCpuTime;
lastProcessOSSysTime = currentOsSysTime;
lastYougGcTime = currentYoungGcTime;
lastOldGcTime = currentOldGcTime;
lastSafePointCount = currentSafePointCount;
lastSafePointTime = currentSafePointTime;
lastSafePointSyncTime = currentSafePointSyncTime;
cleanDead();
return sb.toString();
}
private Object format(ThreadLine line) {
if (threadAllocatedMemoryEnabled) {
return String.format("[%06d] user=%5.2f%% sys=%5.2f%% alloc=%6sb/s - %s", line.id, line.userT, (line.sysT), Formats.toMemorySize((long)line.allocRate), line.name);
}
else {
return String.format("[%06d] user=%5.2f%% sys=%5.2f%% - %s", line.id, line.userT, (line.sysT), line.name);
}
}
private String getThreadName(long tid) {
return threadDump.get(tid).name;
}
private Collection<Long> getAllThreadIds() {
return new TreeSet<Long>(threadDump.keySet());
}
private long getThreadCpuTime(long tid) {
return threadDump.get(tid).lastCpuTime;
}
private long getThreadUserTime(long tid) {
return threadDump.get(tid).lastUserTime;
}
private long getThreadAllocatedBytes(long tid) {
return threadDump.get(tid).lastAllocatedBytes;
}
private long getProcessCpuTime() {
try {
ObjectName bean = new ObjectName("java.lang:type=OperatingSystem");
return (Long) mserver.getAttribute(bean, "ProcessCpuTime");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static class ThreadTrac {
private String name;
private long lastCpuTime;
private long lastUserTime;
private long lastAllocatedBytes;
@SuppressWarnings("unused")
private ThreadInfo lastThreadInfo;
private boolean dead;
}
private static class ThreadNote {
private long lastCpuTime;
private long lastUserTime;
private long lastAllocatedBytes;
}
private static class ThreadLine {
long id;
double userT;
double sysT;
double allocRate;
String name;
public ThreadLine(long id, double userT, double sysT, double allocRate, String name) {
this.id = id;
this.userT = userT;
this.sysT = sysT;
this.allocRate = allocRate;
this.name = name;
}
public String toString() {
return String.format("[%06d] user=%5.2f%% sys=%5.2f%% - %s", id, userT, (sysT), name);
}
}
private static class UserTimeComparator implements Comparator<ThreadLine> {
@Override
public int compare(ThreadLine o1, ThreadLine o2) {
return Double.compare(o2.userT, o1.userT);
}
}
private static class SysTimeComparator implements Comparator<ThreadLine> {
@Override
public int compare(ThreadLine o1, ThreadLine o2) {
return Double.compare(o2.sysT, o1.sysT);
}
}
private static class CpuTimeComparator implements Comparator<ThreadLine> {
@Override
public int compare(ThreadLine o1, ThreadLine o2) {
return Double.compare(o2.userT + o2.sysT, o1.userT + o1.sysT);
}
}
private static class ThreadNameComparator implements Comparator<ThreadLine> {
@Override
public int compare(ThreadLine o1, ThreadLine o2) {
return o1.name.compareTo(o2.name);
}
}
private static class AllocRateComparator implements Comparator<ThreadLine> {
@Override
public int compare(ThreadLine o1, ThreadLine o2) {
return Double.compare(o2.allocRate, o1.allocRate);
}
}
}