import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.Stack; import jrockit.Asserts; import jrockit.jfr.ParseHelper; import jrockit.jfr.PathFilter; import oracle.jrockit.jfr.parser.FLREvent; import oracle.jrockit.jfr.parser.FLRStruct; /** * Mixed helper classes to test GC events. * */ public class GCHelper { public static final String event_garbage_collection = "vm/gc/collector/garbage_collection"; public static final String event_young_garbage_collection = "vm/gc/collector/young_garbage_collection"; public static final String event_old_garbage_collection = "vm/gc/collector/old_garbage_collection"; public static final String event_parold_garbage_collection = "vm/gc/collector/parold_garbage_collection"; public static final String event_g1_garbage_collection = "vm/gc/collector/g1_garbage_collection"; public static final String event_heap_summary = "vm/gc/heap/summary"; public static final String event_heap_ps_summary = "vm/gc/heap/ps_summary"; public static final String event_heap_metaspace_summary = "vm/gc/heap/metaspace_summary"; public static final String event_reference_statistics = "vm/gc/reference/statistics"; public static final String event_phases_pause = "vm/gc/phases/pause"; public static final String event_phases_level_1 = "vm/gc/phases/pause_level_1"; public static final String event_phases_level_2 = "vm/gc/phases/pause_level_2"; public static final String event_phases_level_3 = "vm/gc/phases/pause_level_3"; public static final String event_phases_group = "vm/gc/phases/"; public static final String gcG1New = "G1New"; public static final String gcParNew = "ParNew"; public static final String gcDefNew = "DefNew"; public static final String gcParallelScavenge = "ParallelScavenge"; public static final String gcG1Old = "G1Old"; public static final String gcConcurrentMarkSweep = "ConcurrentMarkSweep"; public static final String gcSerialOld = "SerialOld"; public static final String gcPSMarkSweep = "PSMarkSweep"; public static final String gcParallelOld = "ParallelOld"; private static PrintStream defaultErrorLog = null; public static int getGcId(FLREvent event) { return ((Integer) event.getValue("gcId")).intValue(); } /** * Check if this is a GC event. We count it as a GC event if it has the value "gcId". */ public static boolean isGcEvent(FLREvent event) { try { event.getValue("gcId"); } catch (NoSuchElementException e) { return false; } return true; } public static String getEventDesc(FLREvent event) { if (!isGcEvent(event)) { return event.getPath(); } if (event_garbage_collection.equals(event.getPath())) { String name = "null"; String cause = "null"; if (event.getResolvedValue("name") != null) { name = event.getResolvedValue("name").toString(); } FLRStruct causeStruct = (FLRStruct)event.getResolvedValue("cause"); if (causeStruct != null && causeStruct.getValue("cause") != null) { cause = (String)causeStruct.getValue("cause"); } return String.format("path=%s, gcId=%d, timestamp=%d, name=%s, cause=%s, startTime=%d", event.getPath(), getGcId(event), event.getTimestamp(), name, cause, event.getStartTime()); } else { return String.format("path=%s, gcId=%d, timestamp=%d", event.getPath(), getGcId(event), event.getTimestamp()); } } public static FLREvent getConfigEvent(List events) throws Exception { List configEvents = ParseHelper.filter(events, new PathFilter("vm/gc/configuration/gc")); Asserts.assertFalse(configEvents.isEmpty(), "Could not find event vm/gc/configuration/gc"); FLREvent configEvent = configEvents.get(0); return configEvent; } public static Map beanCollectorTypes = new HashMap(); public static Set collectorOverrides = new HashSet(); public static Map requiredEvents = new HashMap(); static { // young GarbageCollectionMXBeans. beanCollectorTypes.put("G1 Young Generation", true); beanCollectorTypes.put("Copy", true); beanCollectorTypes.put("PS Scavenge", true); beanCollectorTypes.put("ParNew", true); // old GarbageCollectionMXBeans. beanCollectorTypes.put("G1 Old Generation", false); beanCollectorTypes.put("ConcurrentMarkSweep", false); beanCollectorTypes.put("PS MarkSweep", false); beanCollectorTypes.put("MarkSweepCompact", false); // List of expected collector overrides. "A.B" means that collector A may use collector B. collectorOverrides.add("G1Old.SerialOld"); collectorOverrides.add("ConcurrentMarkSweep.SerialOld"); collectorOverrides.add("SerialOld.PSMarkSweep"); requiredEvents.put(gcG1New, new String[] {event_heap_summary, event_young_garbage_collection}); requiredEvents.put(gcParNew, new String[] {event_heap_summary, event_phases_pause, event_phases_level_1, event_young_garbage_collection}); requiredEvents.put(gcDefNew, new String[] {event_heap_summary, event_phases_pause, event_phases_level_1, event_young_garbage_collection}); requiredEvents.put(gcParallelScavenge, new String[] {event_heap_summary, event_heap_ps_summary, event_reference_statistics, event_phases_pause, event_phases_level_1, event_young_garbage_collection}); requiredEvents.put(gcG1Old, new String[] {event_heap_summary, event_old_garbage_collection}); requiredEvents.put(gcConcurrentMarkSweep, new String[] {event_phases_pause, event_phases_level_1, event_old_garbage_collection}); requiredEvents.put(gcSerialOld, new String[] {event_heap_summary, event_phases_pause, event_phases_level_1, event_old_garbage_collection}); requiredEvents.put(gcParallelOld, new String[] {event_heap_summary, event_heap_ps_summary, event_reference_statistics, event_phases_pause, event_phases_level_1, event_old_garbage_collection, event_parold_garbage_collection}); } /** * Contains all GC events belonging to the same GC (same gcId). */ public static class GcBatch { private List events = new ArrayList(); public int getGcId() { if (events.isEmpty()) { return -1; } return GCHelper.getGcId(events.get(0)); } public String getName() { FLREvent endEvent = getEndEvent(); String name = "null"; if (endEvent != null) { Object obj = endEvent.getResolvedValue("name"); if (obj != null) { name = obj.toString(); } } return name; } public FLREvent getEndEvent() { return getEvent(event_garbage_collection); } public boolean addEvent(FLREvent event) { if (!events.isEmpty()) { Asserts.assertEquals(getGcId(), GCHelper.getGcId(event), "Wrong gcId in event. Error in test code."); } boolean isEndEvent = event_garbage_collection.equals(event.getPath()); if (isEndEvent) { // Verify that we have not already got a garbage_collection event with this gcId. Asserts.assertNull(getEndEvent(), String.format("Multiple %s for gcId %d", event_garbage_collection, GCHelper.getGcId(event))); } events.add(event); return isEndEvent; } public boolean isYoungCollection() { boolean isYoung = containsEvent(event_young_garbage_collection); boolean isOld = containsEvent(event_old_garbage_collection); Asserts.assertNotEquals(isYoung, isOld, "isYoung and isOld was same for batch: " + toString()); return isYoung; } public int getEventCount() { return events.size(); } public FLREvent getEvent(int index) { return events.get(index); } public List getEvents() { return events; } public FLREvent getEvent(String eventPath) { for (FLREvent event : events) { if (eventPath.equals(event.getPath())) { return event; } } return null; } public boolean containsEvent(String eventPath) { return getEvent(eventPath) != null; } public String toString() { FLREvent endEvent = getEndEvent(); long startTime = -1; String cause = "?"; String name = "?"; if (endEvent != null) { name = getName(); startTime = endEvent.getStartTime(); FLRStruct causeStruct = (FLRStruct)endEvent.getResolvedValue("cause"); if (causeStruct != null) { cause = (String)causeStruct.getValue("cause"); } } return String.format("GcEvent: gcId=%d, method=%s, cause=%s, startTime=%d", getGcId(), name, cause, startTime); } public String getLog() { StringBuilder sb = new StringBuilder(); sb.append(this.toString() + System.getProperty("line.separator")); for (FLREvent event : events) { sb.append(String.format("event: %s", getEventDesc(event))); sb.append(System.getProperty("line.separator")); } return sb.toString(); } /** * Parse all events and group them into batches */ public static List createFromEvents(List events) throws Exception { Stack openGcIds = new Stack(); List batches = new ArrayList(); GcBatch currBatch = null; for (FLREvent event : events) { if (!isGcEvent(event)) { continue; } int gcId = GCHelper.getGcId(event); if (currBatch == null || currBatch.getGcId() != gcId) { currBatch = null; // Search for existing batch for (GcBatch loopBatch : batches) { if (gcId == loopBatch.getGcId()) { currBatch = loopBatch; break; } } if (currBatch == null) { // No existing batch. Create new. currBatch = new GcBatch(); batches.add(currBatch); openGcIds.push(new Integer(gcId)); } } boolean isEndEvent = currBatch.addEvent(event); if (isEndEvent) { openGcIds.pop(); } } // Verify that all start_garbage_collection events have received a corresponding "garbage_collection" event. for (GcBatch batch : batches) { Asserts.assertNotNull(batch.getEndEvent(), "Not all gc batches closed."); } return batches; } } /** * Contains number of collections and sum pause time for young and old collections. */ public static class CollectionSummary { public long collectionCountOld; public long collectionCountYoung; public long collectionTimeOld; public long collectionTimeYoung; private Set names = new HashSet(); public void add(String collectorName, boolean isYoung, long count, long time) { if (isYoung) { collectionCountYoung += count; collectionTimeYoung += time; } else { collectionCountOld += count; collectionTimeOld += time; } if (!names.contains(collectorName)) { names.add(collectorName); } } public long sum() { return collectionCountOld + collectionCountYoung; } public CollectionSummary calcDelta(CollectionSummary prev) { CollectionSummary delta = new CollectionSummary(); delta.collectionCountOld = this.collectionCountOld - prev.collectionCountOld; delta.collectionTimeOld = this.collectionTimeOld - prev.collectionTimeOld; delta.collectionCountYoung = this.collectionCountYoung - prev.collectionCountYoung; delta.collectionTimeYoung = this.collectionTimeYoung - prev.collectionTimeYoung; delta.names.addAll(this.names); delta.names.addAll(prev.names); return delta; } public static CollectionSummary createFromMxBeans() { CollectionSummary summary = new CollectionSummary(); List gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); for (int c=0; c batches) { CollectionSummary summary = new CollectionSummary(); for (GcBatch batch : batches) { FLREvent endEvent = batch.getEndEvent(); Asserts.assertNotNull(endEvent, "No end event in batch with gcId " + batch.getGcId()); String name = batch.getName(); summary.add(name, batch.isYoungCollection(), 1, ((Long) endEvent.getValue("sumOfPauses")).longValue()); } return summary; } public String toString() { StringBuilder collectorNames = new StringBuilder(); for (String s : names) { if (collectorNames.length() > 0) { collectorNames.append(", "); } collectorNames.append(s); } return String.format("CollectionSummary: young.collections=%d, young.time=%d, old.collections=%d, old.time=%d, collectors=(%s)", collectionCountYoung, collectionTimeYoung, collectionCountOld, collectionTimeOld, collectorNames); } } public static PrintStream getDefaultErrorLog() { if (defaultErrorLog == null) { try { defaultErrorLog = new PrintStream(new FileOutputStream("error.log", true)); } catch (IOException e) { e.printStackTrace(); defaultErrorLog = System.err; } } return defaultErrorLog; } /** * Log both to log file and to System.err. */ public static void log(Object msg) { log(msg, System.err); log(msg, getDefaultErrorLog()); } public static void log(Object msg, PrintStream ps) { ps.println(msg); } }