1 /*
   2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
   3  * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
   4  */
   5 
   6 import java.util.ArrayList;
   7 import java.util.List;
   8 import java.util.HashSet;
   9 import java.util.Arrays;
  10 
  11 import jrockit.jfr.TestRecording;
  12 import oracle.jrockit.jfr.parser.FLREvent;
  13 import oracle.jrockit.jfr.parser.FLRStruct;
  14 
  15 import java.lang.management.RuntimeMXBean;
  16 import java.lang.management.ManagementFactory;
  17 
  18 import static jrockit.Asserts.*;
  19 
  20 /*
  21  * @test TestReferenceStatisticsEvent
  22  * @key jfr
  23  * @library ../common
  24  *
  25  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -Xmx50m -Xmn2m -XX:+UseParallelGC TestReferenceStatisticsEvent ParallelScavenge
  26  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -Xmx50m -Xmn2m -XX:+UseParNewGC TestReferenceStatisticsEvent ParNew
  27  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -Xmx50m -Xmn2m -XX:+UseSerialGC TestReferenceStatisticsEvent DefNew
  28  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -Xmx50m -Xmn2m -XX:+UseG1GC TestReferenceStatisticsEvent G1New
  29  *
  30  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseParallelGC -XX:-UseParallelOldGC TestReferenceStatisticsEvent SerialOld
  31  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseParallelGC -XX:+UseParallelOldGC TestReferenceStatisticsEvent ParallelOld
  32  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent TestReferenceStatisticsEvent G1Old
  33  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseG1GC TestReferenceStatisticsEvent SerialOld
  34  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseConcMarkSweepGC TestReferenceStatisticsEvent SerialOld
  35  * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseConcMarkSweepGC -XX:+ExplicitGCInvokesConcurrent TestReferenceStatisticsEvent ConcurrentMarkSweep
  36  *
  37  */
  38 public class TestReferenceStatisticsEvent {
  39     private static final String gcEventPath = "vm/gc/collector/garbage_collection";
  40     private static final String refStatsEventPath = "vm/gc/reference/statistics";
  41     public static byte[] garbage;
  42 
  43     private static void enableEvent(TestRecording r, String path) throws Exception {
  44         r.createJVMSetting(path, true, false, 0, 0);
  45     }
  46 
  47     private static boolean isYoungGC(String gcName) {
  48         return Arrays.asList("ParallelScavenge", "ParNew", "DefNew", "G1New").contains(gcName);
  49     }
  50 
  51     private static boolean isExplicitGCConcurrent() {
  52         RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
  53         List<String> args = runtimeMxBean.getInputArguments();
  54         for (String arg : args) {
  55             if (arg.equals("-XX:+ExplicitGCInvokesConcurrent")) {
  56                 return true;
  57             }
  58         }
  59         return false;
  60     }
  61 
  62     private static boolean isRunningConcurrentCMS(String gcName) {
  63         return gcName.equals("ConcurrentMarkSweep") && isExplicitGCConcurrent();
  64     }
  65 
  66     private static void triggerGC(String gcName) {
  67         if (isYoungGC(gcName)) {
  68             for (int i = 0; i < 2048; i++) {
  69                 garbage = new byte[1024];
  70             }
  71         } else if (isRunningConcurrentCMS(gcName)) {
  72             // The "vm/gc/collector/garbage_collection" event is not sent at
  73             // the time when "System.gc()" returns from the VM. To be sure that
  74             // we get at least one GC event, we need to trigger to GCs.
  75             System.gc();
  76             System.gc();
  77         }else {
  78             System.gc();
  79         }
  80     }
  81 
  82     private static List<FLREvent> getEventsFromRecording(TestRecording r, String regex) throws Exception {
  83         return r.parser().findJVMEvents(regex);
  84     }
  85 
  86     private static List<FLREvent> filterEventsOnPath(List<FLREvent> events, String path)
  87         throws Exception {
  88         List<FLREvent> result = new ArrayList<FLREvent>();
  89         for (FLREvent e : events) {
  90             if (e.getPath().equals(path)) {
  91                 result.add(e);
  92             }
  93         }
  94         return result;
  95     }
  96 
  97     private static String gcName(FLREvent e) throws Exception {
  98         FLRStruct s = (FLRStruct) e.getResolvedValue("name");
  99         return (String) s.getValue("name");
 100     }
 101 
 102     private static List<FLREvent> filterEventsOnCollector(List<FLREvent> events, String name)
 103         throws Exception {
 104         List<FLREvent> result = new ArrayList<FLREvent>();
 105         List<FLREvent> allGCEvents = filterEventsOnPath(events, gcEventPath);
 106         for (FLREvent e : allGCEvents) {
 107             if (gcName(e).equals(name)) {
 108                 result.add(e);
 109             }
 110         }
 111         return result;
 112     }
 113 
 114     private static Long getGCIdFromEvent(FLREvent e) throws Exception {
 115         return (Long) e.getValue("gcId");
 116     }
 117 
 118     private static List<FLREvent> filterRefProcEventsByGcId(List<FLREvent> events, Long id)
 119         throws Exception {
 120         List<FLREvent> result = new ArrayList<FLREvent>();
 121         for (FLREvent e : events) {
 122             if (getGCIdFromEvent(e).equals(id)) {
 123                 result.add(e);
 124             }
 125         }
 126         return result;
 127     }
 128 
 129     private static List<Long> mapGCEventsToIds(List<FLREvent> events) throws Exception {
 130         List<Long> gcIds = new ArrayList<Long>();
 131         for (FLREvent e : events) {
 132             gcIds.add(getGCIdFromEvent(e));
 133         }
 134         return gcIds;
 135     }
 136 
 137     private static List<List<FLREvent>> groupRefStatEventsByGCId(List<FLREvent> allEvents, List<FLREvent> gcEvents)
 138         throws Exception {
 139         List<Long> gcIds = mapGCEventsToIds(gcEvents);
 140         List<FLREvent> refProcEvents = filterEventsOnPath(allEvents, refStatsEventPath);
 141         List<List<FLREvent>> result = new ArrayList<List<FLREvent>>();
 142         for (Long l : gcIds) {
 143             List<FLREvent> events = filterRefProcEventsByGcId(refProcEvents, l);
 144             if (events.size() > 0) {
 145                 result.add(events);
 146             }
 147         }
 148         return result;
 149     }
 150 
 151     private static String referenceType(FLREvent e) throws Exception {
 152         FLRStruct s = (FLRStruct) e.getResolvedValue("type");
 153         return (String) s.getValue("type");
 154     }
 155 
 156     private static void assertReferenceType(HashSet<String> types, String type, Long gcId)
 157         throws Exception {
 158         assertTrue(types.contains(type + " reference"),
 159                 "Expected " + refStatsEventPath + " event with type " + type +
 160                 " reference for gc with id " + gcId);
 161         assertNE(gcId, -1, "Expected " + refStatsEventPath + " to have a gcId");
 162     }
 163 
 164     private static void verifyRefStatEvents(List<FLREvent> events) throws Exception {
 165         HashSet<String> types = new HashSet<String>();
 166         long gcId = -1;
 167         for (FLREvent e : events) {
 168             types.add(referenceType(e));
 169             gcId = getGCIdFromEvent(e); // all the event should have the same gcId
 170         }
 171         assertReferenceType(types, "Soft", gcId);
 172         assertReferenceType(types, "Weak", gcId);
 173         assertReferenceType(types, "Final", gcId);
 174         assertReferenceType(types, "Phantom", gcId);
 175     }
 176 
 177     private static List<FLREvent> filterOutBadCMSEvents(List<FLREvent> events, String gcName) throws Exception {
 178         List<FLREvent> cmsEvents = filterEventsOnCollector(events, gcName);
 179         assertTrue(cmsEvents.size() == 1 || cmsEvents.size() == 2,
 180                    "Expected exactly one or two " + gcEventPath + " event(s) from concurrent CMS collector");
 181 
 182         if (cmsEvents.size() == 2) {
 183             // No need to filter any events since we got GC events for both
 184             // System.gc() calls.
 185             return events;
 186         }
 187 
 188         List<FLREvent> gcEvents = filterEventsOnPath(events, gcEventPath);
 189 
 190         List<Long> gcIds = mapGCEventsToIds(gcEvents);
 191         Long largestGCId = 0L;
 192         for (Long gcId : gcIds) {
 193             if (gcId > largestGCId) {
 194                 largestGCId = gcId;
 195             }
 196         }
 197 
 198         List<FLREvent> result = new ArrayList<FLREvent>();
 199         for (FLREvent e : events) {
 200             String path = e.getPath();
 201             if (path.startsWith("vm/gc/") && !path.startsWith("vm/gc/configuration")) {
 202                 if (getGCIdFromEvent(e) == largestGCId + 1) {
 203                     // this event comes from "dangling" CMS collection
 204                 } else {
 205                     result.add(e);
 206                 }
 207             }
 208         }
 209         return result;
 210     }
 211 
 212     public static void main(String[] args) throws Exception {
 213         String gcName = args[args.length - 1];
 214 
 215         TestRecording r = new TestRecording();
 216         try {
 217             enableEvent(r, refStatsEventPath);
 218             enableEvent(r, gcEventPath);
 219 
 220             r.start();
 221             triggerGC(gcName);
 222             r.stop();
 223 
 224             List<FLREvent> allEvents = getEventsFromRecording(r, ".*");
 225 
 226             if (isRunningConcurrentCMS(gcName)) {
 227                 allEvents = filterOutBadCMSEvents(allEvents, gcName);
 228             }
 229 
 230             List<FLREvent> gcEvents = filterEventsOnCollector(allEvents, gcName);
 231             assertTrue(gcEvents.size() > 0,
 232                     "Expected at least one " + gcEventPath + " event from gc " + gcName);
 233 
 234             List<List<FLREvent>> refStatEvents = groupRefStatEventsByGCId(allEvents, gcEvents);
 235             assertTrue(refStatEvents.size() == gcEvents.size(),
 236                     "Expected number of " + refStatsEventPath + " groups to be " +
 237                     gcEvents.size() + ", but got " + refStatEvents.size());
 238 
 239             for (List<FLREvent> refStats : refStatEvents) {
 240                 verifyRefStatEvents(refStats);
 241             }
 242         } catch (Throwable t) {
 243             r.copyTo("TestReferenceStatisticsEvent.jfr");
 244             throw t;
 245         } finally {
 246             r.close();
 247         }
 248     }
 249 }