/** * 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.io.IOException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.management.JMException; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; /** * GC monitor. * * @author Alexey Ragozin (alexey.ragozin@gmail.com) */ public class MBeanGCMonitor { MBeanServerConnection connection; private Map<String, CollectorTracker> trackers = new LinkedHashMap<String, CollectorTracker>(); public MBeanGCMonitor(MBeanServerConnection connection) { this.connection = connection; initTrackers(); } private void initTrackers() { try { for(ObjectName name: connection.queryNames(null, null)) { if (name.getDomain().equals("java.lang") && "GarbageCollector".equals(name.getKeyProperty("type"))) { CollectorTracker tracker = new CollectorTracker(connection, name); trackers.put(tracker.name, tracker); } } } catch (Exception e) { throw new RuntimeException(e); } } public String calculateStats() { StringBuilder sb = new StringBuilder(); for(CollectorTracker ct: trackers.values()) { if (sb.length() > 0) { sb.append('\n'); } sb.append(ct.calculateStats()); } return sb.toString(); } public String reportCollection () { StringBuilder sb = new StringBuilder(); for(CollectorTracker ct: trackers.values()) { String report = ct.reportCollection(); if (report.length() == 0) { continue; } if (sb.length() > 0) { sb.append('\n'); } sb.append(report); } return sb.toString(); } private static class CollectorTracker { private MBeanServerConnection mserv; private ObjectName mbean; private String name; private long initialCount; private long initialTime; private long prevTimestamp = 0; private long lastCount; public CollectorTracker(MBeanServerConnection mserv, ObjectName gcbean) { try { this.mserv = mserv; this.mbean = gcbean; this.name = getName(); this.initialCount = getCollectionCount(); this.initialTime = getCollectionTime(); while(getCollectionCount() != initialCount) { initialCount = getCollectionCount(); initialTime = getCollectionTime(); } lastCount = initialCount; } catch (Exception e) { throw new RuntimeException(e); } } private String getName() throws JMException, IOException { return (String) mserv.getAttribute(mbean, "Name"); } private long getCollectionCount() throws JMException, IOException { return (Long)mserv.getAttribute(mbean, "CollectionCount"); } private long getCollectionTime() throws JMException, IOException { return (Long)mserv.getAttribute(mbean, "CollectionTime"); } private CompositeData getLastGcInfo() throws JMException, IOException { return (CompositeData)mserv.getAttribute(mbean, "LastGcInfo"); } @SuppressWarnings("unchecked") public String reportCollection() { try { long count = getCollectionCount(); CompositeData lastGC = getLastGcInfo(); while(getCollectionCount() != count) { count = getCollectionCount(); lastGC = getLastGcInfo(); } if (count == lastCount) { return ""; } else { lastCount = count; StringBuilder builder = new StringBuilder(); int id = ((Number) lastGC.get("id")).intValue(); long dur = (Long) lastGC.get("duration"); long startTs = (Long) lastGC.get("startTime"); builder.append("[GC: ").append(name).append("#").append(id).append(" time: "); builder.append(dur).append("ms"); long inter = -1; if (prevTimestamp != 0) { inter = startTs - prevTimestamp; builder.append(" interval: ").append(inter).append("ms"); } builder.append(" mem:"); prevTimestamp = startTs; Map<List<?>, CompositeData> beforeGC = (Map<List<?>, CompositeData>) lastGC.get("memoryUsageBeforeGc"); Map<List<?>, CompositeData> afterGC = (Map<List<?>, CompositeData>) lastGC.get("memoryUsageAfterGc"); for(List<?> memPool: afterGC.keySet()) { String poolName = (String) memPool.get(0); if (poolName.contains("Perm") || poolName.contains("Cache")) { // ignore continue; } Object[] poolKey = new Object[]{poolName}; CompositeData mbefore = (CompositeData) beforeGC.get(poolKey).get("value"); CompositeData mafter = (CompositeData) afterGC.get(poolKey).get("value"); long before = (Long) mbefore.get("used"); long after = (Long) mafter.get("used"); long max = (Long) mbefore.get("max"); String mb,ma,mm,md,mt; if (max > 1024 << 20) { ma = (after >> 20) + "m"; mb = (before >> 20) + "m"; mm = (max >> 20) + "m"; md = (after - before) / (1 << 20) + "m"; } else { ma = (after >> 10) + "k"; mb = (before >> 10) + "k"; mm = (max >> 10) + "k"; md = (after - before) / (1 << 10) + "k"; } if (!md.startsWith("-")) { md = "+" + md; } if (inter > 0) { double mspeed = 1000d * (after - before) / inter; if (mspeed > 16 << 20) { mt = String.format("%.2fMb/s", mspeed / (1 << 20)); } else { mt = String.format("%.2fkb/s", mspeed / (1 << 10)); } } else { mt = ""; } String ext = (max > 0) ? "max:" + mm : ""; if (mt.length() > 0) { if (ext.length() > 0) { ext += ","; } ext += "rate:" + mt; } if (ext.length() > 0) { ext = "[" + ext + "]"; } builder.append(" ").append(poolName).append(": ").append(mb).append(md).append("->").append(ma).append(ext); } builder.append("]"); return builder.toString(); } } catch (Exception e) { throw new RuntimeException(e); } } public String calculateStats() { try { long count = getCollectionCount(); long time = getCollectionTime(); while(getCollectionCount() != count) { count = getCollectionCount(); time = getCollectionTime(); } double avg = ((double)(time - initialTime)) / ((double)(count - initialCount)); StringBuilder builder = new StringBuilder(); builder.append(String.format("%s[ collections: %d | avg: %.4f secs | total: %.1f secs ]", name, count - initialCount, avg / 1000d, (time - initialTime) / 1000d)); return builder.toString(); } catch (Exception e) { throw new RuntimeException(e); } } } }