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 }