package org.gridkit.jvmtool.gcmon;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.lang.management.RuntimeMXBean;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
public class GcEventPoller implements Runnable {
private static final ObjectName RUNTIME_MXBEAN = mname(ManagementFactory.RUNTIME_MXBEAN_NAME);
private static ObjectName mname(String name) {
try {
return new ObjectName(name);
} catch (MalformedObjectNameException e) {
throw new RuntimeException(e);
}
}
protected final MBeanServerConnection mserver;
protected final GarbageCollectionEventConsumer eventSink;
protected long jvmStartTime;
protected List<GcTracker> trackers = new ArrayList<GcTracker>();
public GcEventPoller(MBeanServerConnection mserver, GarbageCollectionEventConsumer eventSink) {
this.mserver = mserver;
this.eventSink = eventSink;
RuntimeMXBean runtime = JMX.newMXBeanProxy(mserver, RUNTIME_MXBEAN, RuntimeMXBean.class);
jvmStartTime = runtime.getStartTime();
initTrackers();
}
private void initTrackers() {
try {
for(ObjectName name: mserver.queryNames(null, null)) {
if (name.getDomain().equals("java.lang") && "GarbageCollector".equals(name.getKeyProperty("type"))) {
GcTracker tracker = new GcTracker(name, name.getKeyProperty("name"));
initTracker(tracker);
trackers.add(tracker);
tracker.capture();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void initTracker(GcTracker tracker) throws InstanceNotFoundException, IOException {
}
@Override
public void run() {
for(GcTracker tracker: trackers) {
try {
tracker.capture();
} catch (JMException e) {
// ignore
} catch (IOException e) {
// ignore
}
}
}
public ExecutorService schedule(long period) {
ScheduledThreadPoolExecutor sp = new ScheduledThreadPoolExecutor(1);
sp.scheduleAtFixedRate(this, period, period, TimeUnit.MILLISECONDS);
return sp;
}
class GcTracker {
ObjectName name;
String collectorName;
long lastReportedEvent = -1;
public GcTracker(ObjectName name, String collectorName) {
this.name = name;
this.collectorName = collectorName;
}
public synchronized void capture() throws JMException, IOException {
CompositeData lastGc = getLastGcInfo();
if (lastGc != null) {
long id = along(lastGc.get("id"));
long ts = along(lastGc.get("startTime"));
// ignoring initial (t==0) pseudo event
if (ts > 0 && id != lastReportedEvent) {
processGcEvent(lastGc);
}
}
}
protected synchronized void processGcEvent(CompositeData lastGc) throws JMException, IOException {
long id = along(lastGc.get("id"));
if (lastReportedEvent == id) {
return;
}
lastReportedEvent = id;
long dur = along(lastGc.get("duration"));
long startTs = along(lastGc.get("startTime")) + jvmStartTime;
@SuppressWarnings("unchecked")
Map<List<?>, CompositeData> beforeGC = (Map<List<?>, CompositeData>) lastGc.get("memoryUsageBeforeGc");
@SuppressWarnings("unchecked")
Map<List<?>, CompositeData> afterGC = (Map<List<?>, CompositeData>) lastGc.get("memoryUsageAfterGc");
GcSummary summary = new GcSummary();
summary.name = collectorName;
summary.timestamp = startTs;
summary.durationMs = dur;
summary.collectionCount = id;
summary.collectionTotalTime = totalTime(id);
for(Entry<List<?>, CompositeData> e: beforeGC.entrySet()) {
String pool = (String) e.getKey().get(0);
String spaceId = toSpaceId(pool);
MemoryUsage membefore = MemoryUsage.from((CompositeData) e.getValue().get("value"));
MemUsage mu = new MemUsage(pool, membefore);
summary.before.put(spaceId, mu);
}
for(Entry<List<?>, CompositeData> e: afterGC.entrySet()) {
String pool = (String) e.getKey().get(0);
String spaceId = toSpaceId(pool);
MemUsage memafter = new MemUsage(pool, MemoryUsage.from((CompositeData) e.getValue().get("value")));
MemUsage mu = memafter;
summary.after.put(spaceId, mu);
}
eventSink.consume(summary);
}
private String toSpaceId(String key) {
return key.toLowerCase().replace(' ', '-');
}
private long along(Object v) {
return ((Number)v).longValue();
}
private long totalTime(long id) throws JMException, IOException {
AttributeList list = mserver.getAttributes(name, new String[]{"CollectionCount", "CollectionTime"});
long rid = ((Number)((Attribute)list.get(0)).getValue()).longValue();
if (id == rid) {
return ((Number)((Attribute)list.get(1)).getValue()).longValue();
}
else {
return -1;
}
}
private CompositeData getLastGcInfo() throws JMException, IOException {
return (CompositeData)mserver.getAttribute(name, "LastGcInfo");
}
}
private static class GcSummary implements GarbageCollectionSummary {
private String name;
private long timestamp;
private long durationMs;
private long collectionCount;
private long collectionTotalTime;
private Map<String, MemUsage> before = new HashMap<String, MemUsage>();
private Map<String, MemUsage> after = new HashMap<String, MemUsage>();
@Override
public long timestamp() {
return timestamp;
}
@Override
public long duration() {
return TimeUnit.MILLISECONDS.toMicros(durationMs);
}
@Override
public String collectorName() {
return name;
}
@Override
public long collectionCount() {
return collectionCount;
}
@Override
public long collectionTotalTime() {
return collectionTotalTime;
}
@Override
public Iterable<String> memorySpaces() {
return before.keySet();
}
@Override
public long memoryBefore(String spaceId) {
MemUsage mu = before.get(spaceId);
if (mu == null) {
return Long.MIN_VALUE;
}
else {
return mu.used;
}
}
@Override
public long memoryAfter(String spaceId) {
MemUsage mu = after.get(spaceId);
if (mu == null) {
return Long.MIN_VALUE;
}
else {
return mu.used;
}
}
@Override
public long memoryMax(String spaceId) {
MemUsage mu = after.get(spaceId);
if (mu == null) {
return Long.MIN_VALUE;
}
else {
return mu.max;
}
}
@Override
public String memorySpaceName(String spaceId) {
MemUsage mu = after.get(spaceId);
if (mu == null) {
return null;
}
else {
return mu.name;
}
}
}
private static class MemUsage {
String name;
@SuppressWarnings("unused")
long init;
long used;
@SuppressWarnings("unused")
long commited;
long max;
public MemUsage(String name, MemoryUsage mu) {
this(name, mu.getInit(), mu.getUsed(), mu.getCommitted(), mu.getMax());
}
public MemUsage(String name, long init, long used, long commited, long max) {
this.name = name;
this.init = init;
this.used = used;
this.commited = commited;
this.max = max;
}
}
}