1 /*
   2  * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.jfr.event.gc.collection;
  27 
  28 import java.lang.management.ManagementFactory;
  29 import java.time.Duration;
  30 import java.time.Instant;
  31 import java.util.ArrayList;
  32 import java.util.Arrays;
  33 import java.util.HashSet;
  34 import java.util.List;
  35 import java.util.Random;
  36 import java.util.Set;
  37 import java.util.stream.Collectors;
  38 
  39 import jdk.jfr.EventType;
  40 import jdk.jfr.FlightRecorder;
  41 import jdk.jfr.Recording;
  42 import jdk.jfr.consumer.RecordedEvent;
  43 import jdk.test.lib.Asserts;
  44 import jdk.test.lib.jfr.EventNames;
  45 import jdk.test.lib.jfr.Events;
  46 import jdk.test.lib.jfr.GCHelper;
  47 
  48 /**
  49  * Tests for event garbage_collection.
  50  * The test function is called from TestGCEvent*.java, with different worker threads.
  51  * Groups all events belonging to the same garbage collection (the same gcId).
  52  * The group of events belonging to the same GC is called a batch.
  53  *
  54  * This class contains the verifications done and the worker threads used to generate GCs.
  55  * The helper logic are in class GCHelper.
  56  *
  57  * Summary of verifications:
  58  *   All gcIds in garbage_collection event are unique.
  59  *
  60  *   All events in batch has the same gcId.
  61  *
  62  *   Number of garbage_collection events == GarbageCollectionMXBean.getCollectionCount()
  63  *
  64  *   garbage_collection.sum_pause_time approximately equals GarbageCollectionMXBean.getCollectionTime()
  65  *
  66  *   Batch contains expected events depending on garbage_collection.name
  67  *
  68  *   garbage_collection_start.timestamp == garbage_collection.startTime.
  69  *
  70  *   garbage_collection.timestamp >= timestamp for all other events in batch.
  71  *
  72  *   The start_garbage_collection and garbage_collection events must be synchronized.
  73  *     This means that there may be multiple start_garbage_collection before a garbage_collection,
  74  *     but garbage_collection.gcId must be equal to latest start_garbage_collection.gcId.
  75  *
  76  *   start_garbage_collection must be the first event in the batch,
  77  *     that means no event with same gcId before garbage_collection_start event.
  78  *
  79  *   garbage_collection.name matches what is expected by the collectors specified in initial_configuration.
  80  *
  81  *   Duration for event "vm/gc/phases/pause" >= 1. Duration for phase level events >= 0.
  82  *
  83  *
  84  */
  85 public class GCEventAll {
  86     private String youngCollector = null;
  87     private String oldCollector = null;
  88 
  89     /**
  90      *  Trigger GC events by generating garbage and calling System.gc() concurrently.
  91      */
  92     public static void doTest() throws Throwable {
  93         // Trigger GC events by generating garbage and calling System.gc() concurrently.
  94         Thread[] workerThreads = new Thread[] {
  95                 new Thread(GCEventAll.GarbageRunner.create(10)),
  96                 new Thread(GCEventAll.SystemGcWaitRunner.create(10, 2, 1000))};
  97         GCEventAll test = new GCEventAll();
  98         test.doSingleTest(workerThreads);
  99     }
 100 
 101     /**
 102      * Runs the test once with given worker threads.
 103      * @param workerThreads Threads that generates GCs.
 104      * @param gcIds Set of all used gcIds
 105      * @throws Exception
 106      */
 107     private void doSingleTest(Thread[] workerThreads) throws Throwable {
 108         Recording recording = new Recording();
 109         enableAllGcEvents(recording);
 110 
 111         // Start with a full GC to minimize risk of getting extra GC between
 112         // getBeanCollectionCount() and recording.start().
 113         doSystemGc();
 114         GCHelper.CollectionSummary startBeanCount = GCHelper.CollectionSummary.createFromMxBeans();
 115         recording.start();
 116 
 117         for (Thread t : workerThreads) {
 118             t.start();
 119         }
 120         for (Thread t : workerThreads) {
 121             t.join();
 122         }
 123 
 124         // End with a full GC to minimize risk of getting extra GC between
 125         // recording.stop and getBeanCollectionCount().
 126         doSystemGc();
 127         // Add an extra System.gc() to make sure we get at least one full garbage_collection batch at
 128         // the end of the test. This extra System.gc() is only necessary when using "UseConcMarkSweepGC" and "+ExplicitGCInvokesConcurrent".
 129         doSystemGc();
 130 
 131         recording.stop();
 132         GCHelper.CollectionSummary deltaBeanCount = GCHelper.CollectionSummary.createFromMxBeans();
 133         deltaBeanCount = deltaBeanCount.calcDelta(startBeanCount);
 134 
 135         List<RecordedEvent> events = Events.fromRecording(recording).stream()
 136             .filter(evt -> EventNames.isGcEvent(evt.getEventType()))
 137             .collect(Collectors.toList());
 138         RecordedEvent configEvent = GCHelper.getConfigEvent(events);
 139         youngCollector = Events.assertField(configEvent, "youngCollector").notEmpty().getValue();
 140         oldCollector = Events.assertField(configEvent, "oldCollector").notEmpty().getValue();
 141         verify(events, deltaBeanCount);
 142     }
 143 
 144     private void enableAllGcEvents(Recording recording) {
 145         FlightRecorder flightrecorder = FlightRecorder.getFlightRecorder();
 146         for (EventType et : flightrecorder.getEventTypes()) {
 147             if (EventNames.isGcEvent(et)) {
 148                 recording.enable(et.getName());
 149                 System.out.println("Enabled GC event: " + et.getName());
 150             }
 151         }
 152         System.out.println("All GC events enabled");
 153     }
 154 
 155     private static synchronized void doSystemGc() {
 156         System.gc();
 157     }
 158 
 159     /**
 160      * Does all verifications of the received events.
 161      *
 162      * @param events All flight recorder events.
 163      * @param beanCounts Number of collections and sum pause time reported by GarbageCollectionMXBeans.
 164      * @param gcIds All used gcIds. Must be unique.
 165      * @throws Exception
 166      */
 167     private void verify(List<RecordedEvent> events, GCHelper.CollectionSummary beanCounts) throws Throwable {
 168         List<GCHelper.GcBatch> gcBatches = null;
 169         GCHelper.CollectionSummary eventCounts = null;
 170 
 171         // For some GC configurations, the JFR recording may have stopped before we received the last gc event.
 172         try {
 173             events = filterIncompleteGcBatch(events);
 174             gcBatches = GCHelper.GcBatch.createFromEvents(events);
 175             eventCounts = GCHelper.CollectionSummary.createFromEvents(gcBatches);
 176 
 177             verifyUniqueIds(gcBatches);
 178             verifyCollectorNames(gcBatches);
 179             verifyCollectionCause(gcBatches);
 180             verifyCollectionCount(eventCounts, beanCounts);
 181             verifyPhaseEvents(gcBatches);
 182             verifySingleGcBatch(gcBatches);
 183         } catch (Throwable t) {
 184             log(events, gcBatches, eventCounts, beanCounts);
 185             if (gcBatches != null) {
 186                 for (GCHelper.GcBatch batch : gcBatches) {
 187                     System.out.println(String.format("Batch:%n%s", batch.getLog()));
 188                 }
 189             }
 190             throw t;
 191         }
 192     }
 193 
 194     /**
 195      * When using collector ConcurrentMarkSweep with -XX:+ExplicitGCInvokesConcurrent, the JFR recording may
 196      * stop before we have received the last garbage_collection event.
 197      *
 198      * This function does 3 things:
 199      * 1. Check if the last batch is incomplete.
 200      * 2. If it is incomplete, then asserts that incomplete batches are allowed for this configuration.
 201      * 3. If incomplete batches are allowed, then the incomplete batch is removed.
 202      *
 203      * @param events All events
 204      * @return All events with any incomplete batch removed.
 205      * @throws Throwable
 206      */
 207     private List<RecordedEvent> filterIncompleteGcBatch(List<RecordedEvent> events) throws Throwable {
 208         List<RecordedEvent> returnEvents = new ArrayList<RecordedEvent>(events);
 209         int lastGcId = getLastGcId(events);
 210         List<RecordedEvent> lastBatchEvents = getEventsWithGcId(events, lastGcId);
 211         String[] endEvents = {GCHelper.event_garbage_collection, GCHelper.event_old_garbage_collection, GCHelper.event_young_garbage_collection};
 212         boolean isComplete = containsAnyPath(lastBatchEvents, endEvents);
 213         if (!isComplete) {
 214             // The last GC batch does not contain an end event. The batch is incomplete.
 215             // This is only allowed if we are using old_collector="ConcurrentMarkSweep" and "-XX:+ExplicitGCInvokesConcurrent"
 216             boolean isExplicitGCInvokesConcurrent = hasInputArgument("-XX:+ExplicitGCInvokesConcurrent");
 217             boolean isConcurrentMarkSweep = GCHelper.gcConcurrentMarkSweep.equals(oldCollector);
 218             String msg = String.format(
 219                     "Incomplete batch only allowed for '%s' with -XX:+ExplicitGCInvokesConcurrent",
 220                     GCHelper.gcConcurrentMarkSweep);
 221             Asserts.assertTrue(isConcurrentMarkSweep && isExplicitGCInvokesConcurrent, msg);
 222 
 223             // Incomplete batch is allowed with the current settings. Remove incomplete batch.
 224             returnEvents.removeAll(lastBatchEvents);
 225         }
 226         return returnEvents;
 227     }
 228 
 229     private boolean hasInputArgument(String arg) {
 230         return ManagementFactory.getRuntimeMXBean().getInputArguments().contains(arg);
 231     }
 232 
 233     private List<RecordedEvent> getEventsWithGcId(List<RecordedEvent> events, int gcId) {
 234         List<RecordedEvent> batchEvents = new ArrayList<>();
 235         for (RecordedEvent event : events) {
 236             if (GCHelper.isGcEvent(event) && GCHelper.getGcId(event) == gcId) {
 237                 batchEvents.add(event);
 238             }
 239         }
 240         return batchEvents;
 241     }
 242 
 243     private boolean containsAnyPath(List<RecordedEvent> events, String[] paths) {
 244         List<String> pathList = Arrays.asList(paths);
 245         for (RecordedEvent event : events) {
 246             if (pathList.contains(event.getEventType().getName())) {
 247                 return true;
 248             }
 249         }
 250         return false;
 251     }
 252 
 253     private int getLastGcId(List<RecordedEvent> events) {
 254         int lastGcId = -1;
 255         for (RecordedEvent event : events) {
 256             if (GCHelper.isGcEvent(event)) {
 257                 int gcId = GCHelper.getGcId(event);
 258                 if (gcId > lastGcId) {
 259                     lastGcId = gcId;
 260                 }
 261             }
 262         }
 263         Asserts.assertTrue(lastGcId != -1, "No gcId found");
 264         return lastGcId;
 265     }
 266 
 267     /**
 268      * Verifies collection count reported by flight recorder events against the values
 269      * reported by GarbageCollectionMXBean.
 270      * Number of collections should match exactly.
 271      * Sum pause time are allowed some margin of error because of rounding errors in measurements.
 272      */
 273     private void verifyCollectionCount(GCHelper.CollectionSummary eventCounts, GCHelper.CollectionSummary beanCounts) {
 274         verifyCollectionCount(youngCollector, eventCounts.collectionCountYoung, beanCounts.collectionCountYoung);
 275         verifyCollectionCount(oldCollector, eventCounts.collectionCountOld, beanCounts.collectionCountOld);
 276     }
 277 
 278     private void verifyCollectionCount(String collector, long eventCounts, long beanCounts) {
 279         if (GCHelper.gcConcurrentMarkSweep.equals(collector) || GCHelper.gcG1Old.equals(oldCollector)) {
 280             // ConcurrentMarkSweep mixes old and new collections. Not same values as in MXBean.
 281             // MXBean does not report old collections for G1Old, so we have nothing to compare with.
 282             return;
 283         }
 284         // JFR events and GarbageCollectorMXBean events are not updated at the same time.
 285         // This means that number of collections may diff.
 286         // We allow a diff of +- 1 collection count.
 287         long minCount = Math.max(0, beanCounts - 1);
 288         long maxCount = beanCounts + 1;
 289         Asserts.assertGreaterThanOrEqual(eventCounts, minCount, "Too few event counts for collector " + collector);
 290         Asserts.assertLessThanOrEqual(eventCounts, maxCount, "Too many event counts for collector " + collector);
 291     }
 292 
 293     /**
 294      * Verifies that all events belonging to a single GC are ok.
 295      * A GcBatch contains all flight recorder events that belong to a single GC.
 296      */
 297     private void verifySingleGcBatch(List<GCHelper.GcBatch> batches) {
 298         for (GCHelper.GcBatch batch : batches) {
 299             //System.out.println("batch:\r\n" + batch.getLog());
 300             try {
 301                 RecordedEvent endEvent = batch.getEndEvent();
 302                 Asserts.assertNotNull(endEvent, "No end event in batch.");
 303                 Asserts.assertNotNull(batch.getName(), "No method name in end event.");
 304                 long longestPause = Events.assertField(endEvent, "longestPause").atLeast(0L).getValue();
 305                 Events.assertField(endEvent, "sumOfPauses").atLeast(longestPause).getValue();
 306                 Instant batchStartTime = endEvent.getStartTime();
 307                 Instant batchEndTime = endEvent.getEndTime();
 308                 for (RecordedEvent event : batch.getEvents()) {
 309                     if (event.getEventType().getName().contains("AllocationRequiringGC")) {
 310                         // Unlike other events, these are sent *before* a GC.
 311                         Asserts.assertLessThanOrEqual(event.getStartTime(), batchStartTime, "Timestamp in event after start event, should be sent before GC start");
 312                     } else {
 313                         Asserts.assertGreaterThanOrEqual(event.getStartTime(), batchStartTime, "startTime in event before batch start event, should be sent after GC start");
 314                     }
 315                     Asserts.assertLessThanOrEqual(event.getEndTime(), batchEndTime, "endTime in event after batch end event, should be sent before GC end");
 316                 }
 317 
 318                 // Verify that all required events has been received.
 319                 String[] requiredEvents = GCHelper.requiredEvents.get(batch.getName());
 320                 Asserts.assertNotNull(requiredEvents, "No required events specified for " + batch.getName());
 321                 for (String requiredEvent : requiredEvents) {
 322                     boolean b = batch.containsEvent(requiredEvent);
 323                     Asserts.assertTrue(b, String.format("%s does not contain event %s", batch, requiredEvent));
 324                 }
 325 
 326                 // Verify that we have exactly one heap_summary "Before GC" and one "After GC".
 327                 int countBeforeGc = 0;
 328                 int countAfterGc = 0;
 329                 for (RecordedEvent event : batch.getEvents()) {
 330                     if (GCHelper.event_heap_summary.equals(event.getEventType().getName())) {
 331                         String when = Events.assertField(event, "when").notEmpty().getValue();
 332                         if ("Before GC".equals(when)) {
 333                             countBeforeGc++;
 334                         } else if ("After GC".equals(when)) {
 335                             countAfterGc++;
 336                         } else {
 337                             Asserts.fail("Unknown value for heap_summary.when: '" + when + "'");
 338                         }
 339                     }
 340                 }
 341                 if (!GCHelper.gcConcurrentMarkSweep.equals(batch.getName())) {
 342                     // We do not get heap_summary events for ConcurrentMarkSweep
 343                     Asserts.assertEquals(1, countBeforeGc, "Unexpected number of heap_summary.before_gc");
 344                     Asserts.assertEquals(1, countAfterGc, "Unexpected number of heap_summary.after_gc");
 345                 }
 346             } catch (Throwable e) {
 347                 GCHelper.log("verifySingleGcBatch failed for gcEvent:");
 348                 GCHelper.log(batch.getLog());
 349                 throw e;
 350             }
 351         }
 352     }
 353 
 354     private Set<Integer> verifyUniqueIds(List<GCHelper.GcBatch> batches) {
 355         Set<Integer> gcIds = new HashSet<>();
 356         for (GCHelper.GcBatch batch : batches) {
 357             Integer gcId = new Integer(batch.getGcId());
 358             Asserts.assertFalse(gcIds.contains(gcId), "Duplicate gcId: " + gcId);
 359             gcIds.add(gcId);
 360         }
 361         return gcIds;
 362     }
 363 
 364     private void verifyPhaseEvents(List<GCHelper.GcBatch> batches) {
 365         for (GCHelper.GcBatch batch : batches) {
 366             for(RecordedEvent event : batch.getEvents()) {
 367                 if (event.getEventType().getName().contains(GCHelper.pauseLevelEvent)) {
 368                     Instant batchStartTime = batch.getEndEvent().getStartTime();
 369                     Asserts.assertGreaterThanOrEqual(
 370                         event.getStartTime(), batchStartTime, "Phase startTime >= batch startTime. Event:" + event);
 371 
 372                     // Duration for event "vm/gc/phases/pause" must be >= 1. Other phase event durations must be >= 0.
 373                     Duration minDuration = Duration.ofNanos(GCHelper.event_phases_pause.equals(event.getEventType().getName()) ? 1 : 0);
 374                     Duration duration = event.getDuration();
 375                     Asserts.assertGreaterThanOrEqual(duration, minDuration, "Wrong duration. Event:" + event);
 376                 }
 377             }
 378         }
 379     }
 380 
 381     /**
 382      * Verifies that the collector name in initial configuration matches the name in garbage configuration event.
 383      * If the names are not equal, then we check if this is an expected collector override.
 384      * For example, if old collector in initial config is "G1Old" we allow both event "G1Old" and "SerialOld".
 385      */
 386     private void verifyCollectorNames(List<GCHelper.GcBatch> batches) {
 387         for (GCHelper.GcBatch batch : batches) {
 388             String name = batch.getName();
 389             Asserts.assertNotNull(name, "garbage_collection.name was null");
 390             boolean isYoung = batch.isYoungCollection();
 391             String expectedName = isYoung ? youngCollector : oldCollector;
 392             if (!expectedName.equals(name)) {
 393                 // Collector names not equal. Check if the collector has been overridden by an expected collector.
 394                 String overrideKey = expectedName + "." + name;
 395                 boolean isOverride = GCHelper.collectorOverrides.contains(overrideKey);
 396                 Asserts.assertTrue(isOverride, String.format("Unexpected event name(%s) for collectors(%s, %s)", name, youngCollector, oldCollector));
 397             }
 398         }
 399     }
 400 
 401     /**
 402      * Verifies field "cause" in garbage_collection event.
 403      * Only check that at cause is not null and that at least 1 cause is "System.gc()"
 404      * We might want to check more cause reasons later.
 405      */
 406     private void verifyCollectionCause(List<GCHelper.GcBatch> batches) {
 407         int systemGcCount = 0;
 408         for (GCHelper.GcBatch batch : batches) {
 409             RecordedEvent endEvent = batch.getEndEvent();
 410             String cause = Events.assertField(endEvent, "cause").notEmpty().getValue();
 411             // A System.GC() can be consolidated into a GCLocker GC
 412             if (cause.equals("System.gc()") || cause.equals("GCLocker Initiated GC")) {
 413                 systemGcCount++;
 414             }
 415             Asserts.assertNotNull(batch.getName(), "garbage_collection.name was null");
 416         }
 417         final String msg = "No event with cause=System.gc(), collectors(%s, %s)";
 418         Asserts.assertTrue(systemGcCount > 0, String.format(msg, youngCollector, oldCollector));
 419     }
 420 
 421     private void log(List<RecordedEvent> events, List<GCHelper.GcBatch> batches,
 422         GCHelper.CollectionSummary eventCounts, GCHelper.CollectionSummary beanCounts) {
 423         GCHelper.log("EventCounts:");
 424         if (eventCounts != null) {
 425             GCHelper.log(eventCounts.toString());
 426         }
 427         GCHelper.log("BeanCounts:");
 428         if (beanCounts != null) {
 429             GCHelper.log(beanCounts.toString());
 430         }
 431     }
 432 
 433     /**
 434      * Thread that does a number of System.gc().
 435      */
 436     public static class SystemGcRunner implements Runnable {
 437         private final int totalCollections;
 438 
 439         public SystemGcRunner(int totalCollections) {
 440             this.totalCollections = totalCollections;
 441         }
 442 
 443         public static SystemGcRunner create(int totalCollections) {
 444             return new SystemGcRunner(totalCollections);
 445         }
 446 
 447         public void run() {
 448             for (int i = 0; i < totalCollections; i++) {
 449                 GCEventAll.doSystemGc();
 450             }
 451         }
 452     }
 453 
 454     /**
 455      * Thread that creates garbage until a certain number of GCs has been run.
 456      */
 457     public static class GarbageRunner implements Runnable {
 458         private final int totalCollections;
 459         public byte[] dummyBuffer = null;
 460 
 461         public GarbageRunner(int totalCollections) {
 462             this.totalCollections = totalCollections;
 463         }
 464 
 465         public static GarbageRunner create(int totalCollections) {
 466             return new GarbageRunner(totalCollections);
 467         }
 468 
 469         public void run() {
 470             long currCollections = GCHelper.CollectionSummary.createFromMxBeans().sum();
 471             long endCollections = totalCollections + currCollections;
 472             Random r = new Random(0);
 473             while (true) {
 474                 for (int i = 0; i < 1000; i++) {
 475                     dummyBuffer = new byte[r.nextInt(10000)];
 476                 }
 477                 if (GCHelper.CollectionSummary.createFromMxBeans().sum() >= endCollections) {
 478                     break;
 479                 }
 480             }
 481         }
 482     }
 483 
 484     /**
 485      * Thread that runs System.gc() and then wait for a number of GCs or a maximum time.
 486      */
 487     public static class SystemGcWaitRunner implements Runnable {
 488         private final int totalCollections;
 489         private final int minWaitCollections;
 490         private final long maxWaitMillis;
 491 
 492         public SystemGcWaitRunner(int totalCollections, int minWaitCollections, long maxWaitMillis) {
 493             this.totalCollections = totalCollections;
 494             this.minWaitCollections = minWaitCollections;
 495             this.maxWaitMillis = maxWaitMillis;
 496         }
 497 
 498         public static SystemGcWaitRunner create(int deltaCollections, int minWaitCollections, long maxWaitMillis) {
 499             return new SystemGcWaitRunner(deltaCollections, minWaitCollections, maxWaitMillis);
 500         }
 501 
 502         public void run() {
 503             long currCount = GCHelper.CollectionSummary.createFromMxBeans().sum();
 504             long endCount = totalCollections + currCount;
 505             long nextSystemGcCount = currCount + minWaitCollections;
 506             long now = System.currentTimeMillis();
 507             long nextSystemGcMillis = now + maxWaitMillis;
 508 
 509             while (true) {
 510                 if (currCount >= nextSystemGcCount || System.currentTimeMillis() > nextSystemGcMillis) {
 511                     GCEventAll.doSystemGc();
 512                     currCount = GCHelper.CollectionSummary.createFromMxBeans().sum();
 513                     nextSystemGcCount = currCount + minWaitCollections;
 514                 } else {
 515                     try {
 516                         Thread.sleep(20);
 517                     } catch (InterruptedException e) {
 518                         e.printStackTrace();
 519                         break;
 520                     }
 521                 }
 522                 currCount = GCHelper.CollectionSummary.createFromMxBeans().sum();
 523                 if (currCount >= endCount) {
 524                     break;
 525                 }
 526             }
 527         }
 528     }
 529 
 530 }