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 import java.util.Arrays; 6 import java.util.List; 7 import java.util.ArrayList; 8 import java.util.Collections; 9 import java.util.HashSet; 10 import java.util.Iterator; 11 import java.util.Random; 12 import java.util.Set; 13 14 import jrockit.Asserts; 15 import jrockit.jfr.JFRHelper; 16 import jrockit.jfr.TestRecording; 17 import oracle.jrockit.jfr.parser.ChunkParser; 18 import oracle.jrockit.jfr.parser.FLREvent; 19 import oracle.jrockit.jfr.parser.FLRStruct; 20 21 import java.lang.management.ManagementFactory; 22 import java.lang.management.RuntimeMXBean; 23 24 25 /** 26 * Tests for event garbage_collection. 27 * The test function is called from TestGCEvent*.java, with different worker threads. 28 * Groups all events belonging to the same garbage collection (the same gcId). 29 * The group of events belonging to the same GC is called a batch. 30 * 31 * This class contains the verifications done and the worker threads used to generate GCs. 32 * The helper logic are in class GCHelper. 33 * 34 * Summary of verifications: 35 * All gcIds in garbage_collection event are unique. 36 * 37 * All events in batch has the same gcId. 38 * 39 * Number of garbage_collection events == GarbageCollectionMXBean.getCollectionCount() 40 * 41 * garbage_collection.sum_pause_time approximately equals GarbageCollectionMXBean.getCollectionTime() 42 * 43 * Batch contains expected events depending on garbage_collection.name 44 * 45 * garbage_collection_start.timestamp == garbage_collection.startTime. 46 * 47 * garbage_collection.timestamp >= timestamp for all other events in batch. 48 * 49 * The start_garbage_collection and garbage_collection events must be synchronized. 50 * This means that there may be multiple start_garbage_collection before a garbage_collection, 51 * but garbage_collection.gcId must be equal to latest start_garbage_collection.gcId. 52 * 53 * start_garbage_collection must be the first event in the batch, 54 * that means no event with same gcId before garbage_collection_start event. 55 * 56 * garbage_collection.name matches what is expected by the collectors specified in initial_configuration. 57 * 58 * Duration for event "vm/gc/phases/pause" >= 1. Duration for phase level events >= 0. 59 * 60 * 61 */ 62 public class GCEventAll { 63 private static Set<Integer> gcIds = new HashSet<Integer>(); 64 private static int systemGcCount = 0; 65 private boolean isIncompleteBatchRemoved = false; 66 67 private String youngCollector = null; 68 private String oldCollector = null; 69 70 /** 71 * Runs the test once with given worker threads. 72 * @param workerThreads Threads that generates GCs. 73 * @param gcIds Set of all used gcIds 74 * @throws Exception 75 */ 76 public void doSingleTest(Thread[] workerThreads) throws Throwable { 77 TestRecording r = new TestRecording(); 78 final String gcEventRoot = "vm/gc/*"; 79 List<FLREvent> events = null; 80 try { 81 r.createJVMSetting(gcEventRoot, true, false, 0, 0); 82 83 // Start with a full GC to minimize risk of getting extra GC between 84 // getBeanCollectionCount() and recording.start(). 85 doSystemGc(); 86 GCHelper.CollectionSummary startBeanCount = GCHelper.CollectionSummary.createFromMxBeans(); 87 systemGcCount = 0; 88 r.start(); 89 90 for (Thread t : workerThreads) { 91 t.start(); 92 } 93 for (Thread t : workerThreads) { 94 t.join(); 95 } 96 97 // End with a full GC to minimize risk of getting extra GC between 98 // recording.stop and getBeanCollectionCount(). 99 doSystemGc(); 100 // Add an extra System.gc() to make sure we get at least one full garbage_collection batch at 101 // the end of the test. This extra System.gc() is only necessary when using "UseConcMarkSweepGC" and "+ExplicitGCInvokesConcurrent". 102 doSystemGc(); 103 104 r.stop(); 105 GCHelper.CollectionSummary deltaBeanCount = GCHelper.CollectionSummary.createFromMxBeans(); 106 deltaBeanCount = deltaBeanCount.calcDelta(startBeanCount); 107 events = r.parser().findJVMEvents(gcEventRoot + ".*"); 108 Collections.sort(events, new JFRHelper.EventTimestampComparator()); 109 FLREvent configEvent = GCHelper.getConfigEvent(events); 110 youngCollector = (String)((FLRStruct)configEvent.getResolvedValue("youngCollector")).getValue("name"); 111 oldCollector = (String)((FLRStruct)configEvent.getResolvedValue("oldCollector")).getValue("name"); 112 verify(events, deltaBeanCount); 113 } catch (Throwable e) { 114 String filename = this.getClass().getSimpleName() + "-" + System.currentTimeMillis() + ".jfr"; 115 r.copyTo(filename); 116 System.out.println("Failed recording saved as " + filename); 117 System.out.println("All events:"); 118 for (FLREvent event : events) { 119 System.out.println(event); 120 } 121 throw e; 122 } finally { 123 r.close(); 124 } 125 } 126 127 public static synchronized void doSystemGc() { 128 systemGcCount++; 129 System.gc(); 130 } 131 132 /** 133 * Does all verifications of the received events. 134 * 135 * @param events All flight recorder events. 136 * @param beanCounts Number of collections and sum pause time reported by GarbageCollectionMXBeans. 137 * @param gcIds All used gcIds. Must be unique. 138 * @throws Exception 139 */ 140 public void verify(List<FLREvent> events, GCHelper.CollectionSummary beanCounts) throws Throwable { 141 List<GCHelper.GcBatch> gcBatches = null; 142 GCHelper.CollectionSummary eventCounts = null; 143 Throwable firstError = null; 144 145 // For some GC configurations, the JFR recording may have stopped before we received the last gc event. 146 int oldSize = events.size(); 147 events = filterIncompleteGcBatch(events); 148 if (events.size() != oldSize) { 149 isIncompleteBatchRemoved = true; 150 } 151 152 // Parse events 153 try { 154 gcBatches = GCHelper.GcBatch.createFromEvents(events); 155 eventCounts = GCHelper.CollectionSummary.createFromEvents(gcBatches); 156 } catch (Throwable t) { 157 t.printStackTrace(); 158 firstError = firstError == null ? t : firstError; 159 } 160 161 // Verify events if parsing was ok. 162 if (firstError == null) { 163 try { 164 verifyUniqueIds(gcBatches, gcIds); 165 } catch (Throwable t) { 166 t.printStackTrace(); 167 firstError = firstError == null ? t : firstError; 168 } 169 try { 170 verifySingleGcBatch(gcBatches); 171 } catch (Throwable t) { 172 t.printStackTrace(); 173 firstError = firstError == null ? t : firstError; 174 } 175 try { 176 verifyCollectorNames(gcBatches); 177 } catch (Throwable t) { 178 t.printStackTrace(); 179 firstError = firstError == null ? t : firstError; 180 } 181 try { 182 verifyCollectionCount(eventCounts, beanCounts); 183 } catch (Throwable t) { 184 t.printStackTrace(); 185 firstError = firstError == null ? t : firstError; 186 } 187 try { 188 verifyCollectionCause(gcBatches); 189 } catch (Throwable t) { 190 t.printStackTrace(); 191 firstError = firstError == null ? t : firstError; 192 } 193 try { 194 verifyPhaseEvents(gcBatches); 195 } catch (Throwable t) { 196 t.printStackTrace(); 197 firstError = firstError == null ? t : firstError; 198 } 199 } 200 201 if (firstError != null) { 202 // At least one error found. Log all events. 203 log(events, gcBatches, eventCounts, beanCounts); 204 throw firstError; 205 } 206 } 207 208 /** 209 * When using collector ConcurrentMarkSweep with -XX:+ExplicitGCInvokesConcurrent, the JFR recording may 210 * stop before we have received the last garbage_collection event. 211 * 212 * This function does 3 things: 213 * 1. Check if the last batch is incomplete. 214 * 2. If it is incomplete, then asserts that incomplete batches are allowed for this configuration. 215 * 3. If incomplete batches are allowed, then the incomplete batch is removed. 216 * 217 * @param events All events 218 * @return All events with any incomplete batch removed. 219 * @throws Throwable 220 */ 221 private List<FLREvent> filterIncompleteGcBatch(List<FLREvent> events) throws Throwable { 222 List<FLREvent> returnEvents = new ArrayList<FLREvent>(); 223 returnEvents.addAll(events); 224 225 int lastGcId = getLastGcId(events); 226 List<FLREvent> lastBatchEvents = getEventsWithGcId(events, lastGcId); 227 String[] endEvents = {GCHelper.event_garbage_collection, GCHelper.event_old_garbage_collection, GCHelper.event_young_garbage_collection}; 228 boolean isComplete = containsAnyPath(lastBatchEvents, endEvents); 229 if (!isComplete) { 230 // The last GC batch does not contain an end event. The batch is incomplete. 231 // This is only allowed if we are using old_collector="ConcurrentMarkSweep" and "-XX:+ExplicitGCInvokesConcurrent" 232 boolean isExplicitGCInvokesConcurrent = hasInputArgument("-XX:+ExplicitGCInvokesConcurrent"); 233 boolean isConcurrentMarkSweep = GCHelper.gcConcurrentMarkSweep.equals(oldCollector); 234 String msg = String.format( 235 "Incomplete batch only allowed for '%s' with -XX:+ExplicitGCInvokesConcurrent", 236 GCHelper.gcConcurrentMarkSweep); 237 Asserts.assertTrue(isConcurrentMarkSweep && isExplicitGCInvokesConcurrent, msg); 238 239 // This batch is incomplete, but that is allowed with the current settings. 240 // Remove the incomplete batch. 241 returnEvents.removeAll(lastBatchEvents); 242 } 243 return returnEvents; 244 } 245 246 /** 247 * Checks if a given argument was specified on the command line when this program was started. 248 * @param arg The argument to search for 249 * @return true if the argument is on the command line. 250 */ 251 private boolean hasInputArgument(String arg) { 252 RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); 253 List<String> arguments = runtimeMxBean.getInputArguments(); 254 for (String currArg : arguments) { 255 if (currArg.equals(arg)) { 256 return true; 257 } 258 } 259 return false; 260 } 261 262 /** 263 * Get all events that has the specified gcId. 264 * @param events All events. 265 * @param gcId The specified gcId. 266 * @return A list of the events with the specified gcId. 267 */ 268 private List<FLREvent> getEventsWithGcId(List<FLREvent> events, int gcId) { 269 List<FLREvent> batchEvents = new ArrayList<FLREvent>(); 270 for (FLREvent event : events) { 271 if (GCHelper.isGcEvent(event) && GCHelper.getGcId(event) == gcId) { 272 batchEvents.add(event); 273 } 274 } 275 return batchEvents; 276 } 277 278 /** 279 * Checks if at least one event in the list has one of the specified paths. 280 * @param events All events. 281 * @param paths The paths to search for. 282 * @return true if at least one of the events has one of the specified paths. 283 */ 284 private boolean containsAnyPath(List<FLREvent> events, String[] paths) { 285 Set<String> pathSet = new HashSet<String>(Arrays.asList(paths)); 286 for (FLREvent event : events) { 287 if (pathSet.contains(event.getPath())) { 288 return true; 289 } 290 } 291 return false; 292 } 293 294 /** 295 * Return the highest gcId in the list. 296 * @param events All events. 297 * @return the highest gcId found in the list or -1 if no event had a gcId. 298 */ 299 private int getLastGcId(List<FLREvent> events) { 300 int lastGcId = -1; 301 for (FLREvent event : events) { 302 if (GCHelper.isGcEvent(event)) { 303 int gcId = GCHelper.getGcId(event); 304 if (gcId > lastGcId) { 305 lastGcId = gcId; 306 } 307 } 308 } 309 Asserts.assertTrue(lastGcId != -1, "No gcId found"); 310 return lastGcId; 311 } 312 313 /** 314 * Verifies collection count reported by flight recorder events against the values 315 * reported by GarbageCollectionMXBean. 316 * Number of collections should match exactly. 317 * Sum pause time are allowed some margin of error because of rounding errors in measurements. 318 */ 319 private void verifyCollectionCount(GCHelper.CollectionSummary eventCounts, GCHelper.CollectionSummary beanCounts) { 320 verifyCollectionCount(youngCollector, eventCounts.collectionCountYoung, beanCounts.collectionCountYoung); 321 verifyCollectionCount(oldCollector, eventCounts.collectionCountOld, beanCounts.collectionCountOld); 322 } 323 324 private void verifyCollectionCount(String collector, long eventCounts, long beanCounts) { 325 boolean ignoreFailure = 326 GCHelper.gcConcurrentMarkSweep.equals(collector) || // ConcurrentMarkSweep mixes old and new collections. Not same values as in MXBean. 327 GCHelper.gcG1Old.equals(oldCollector); // MXBean does not report any old collections, so we have nothing to compare with. 328 try { 329 Asserts.assertEquals(eventCounts, beanCounts, "Wrong number of collections for collector " + collector); 330 } catch (Throwable e) { 331 if (ignoreFailure) { 332 System.err.println(String .format("Collector(%s) ignores error: %s", collector, e.getMessage())); 333 } else { 334 throw e; 335 } 336 } 337 } 338 339 /** 340 * Verifies that all events belonging to a single GC are ok. 341 * A GcBatch contains all flight recorder events that belong to a single GC. 342 * 343 * That all gcIds in a batch are the same are verified when the GcBatches are created. 344 * 345 * @param batches flight recorder events grouped into batches belonging to the same GC. 346 */ 347 private void verifySingleGcBatch(List<GCHelper.GcBatch> batches) { 348 for (GCHelper.GcBatch batch : batches) { 349 //System.out.println("batch:\r\n" + batch.getLog()); 350 try { 351 FLREvent endEvent = batch.getEndEvent(); 352 353 Asserts.assertNotNull(endEvent, "No end event in batch."); 354 Asserts.assertNotNull(batch.getName(), "No method name in end event."); 355 Asserts.assertLessOrEqualThan( 356 (Long)endEvent.getValue("longestPause"), (Long)endEvent.getValue("sumOfPauses"), 357 "longest pause should be <= sum pause"); 358 359 long startTime = endEvent.getStartTime(); 360 long endTime = endEvent.getTimestamp(); 361 for (FLREvent event : batch.getEvents()) { 362 Asserts.assertGreaterOrEqualThan(event.getTimestamp(), startTime, "Timestamp in event before start event"); 363 Asserts.assertLessOrEqualThan(event.getTimestamp(), endTime, "Timestamp in event after end event"); 364 } 365 366 // Verify that all required events has been received. 367 String[] requiredEvents = GCHelper.requiredEvents.get(batch.getName()); 368 Asserts.assertNotNull(requiredEvents, "No required events specified for " + batch.getName()); 369 for (String requiredEvent : requiredEvents) { 370 boolean b = batch.containsEvent(requiredEvent); 371 Asserts.assertTrue(b, String.format("%s does not contain event %s", batch, requiredEvent)); 372 } 373 374 // Verify that we have exactly one heap_summary "Before GC" and one "After GC". 375 int countBeforeGc = 0; 376 int countAfterGc = 0; 377 for (FLREvent event : batch.getEvents()) { 378 if (GCHelper.event_heap_summary.equals(event.getPath())) { 379 String when = (String)((FLRStruct)event.getResolvedValue("when")).getValue("when"); 380 if ("Before GC".equals(when)) { 381 countBeforeGc++; 382 } else if ("After GC".equals(when)) { 383 countAfterGc++; 384 } else { 385 Asserts.assertTrue(false, "Unknown value for heap_summary.when: '" + when + "'"); 386 } 387 } 388 } 389 if (!GCHelper.gcConcurrentMarkSweep.equals(batch.getName())) { 390 // We do not get heap_summary events for ConcurrentMarkSweep 391 Asserts.assertEquals(1, countBeforeGc, "Unexpected number of heap_summary.before_gc"); 392 Asserts.assertEquals(1, countAfterGc, "Unexpected number of heap_summary.after_gc"); 393 } 394 } catch (Throwable e) { 395 throw e; 396 } 397 } 398 } 399 400 /** 401 * Verify that all gcId are unique. 402 */ 403 private void verifyUniqueIds(List<GCHelper.GcBatch> batches, Set<Integer> gcIds) { 404 for (GCHelper.GcBatch batch : batches) { 405 Integer gcId = new Integer(batch.getGcId()); 406 Asserts.assertFalse(gcIds.contains(gcId), "Duplicate gcId: " + gcId); 407 gcIds.add(gcId); 408 } 409 } 410 411 /** 412 * Verify phase events. 413 */ 414 private void verifyPhaseEvents(List<GCHelper.GcBatch> batches) { 415 for (GCHelper.GcBatch batch : batches) { 416 for(FLREvent event : batch.getEvents()) { 417 // Only check events that start with "vm/gc/phases/" 418 if (event.getPath().startsWith(GCHelper.event_phases_group)) { 419 long batchStartTime = batch.getEndEvent().getStartTime(); 420 Asserts.assertGreaterOrEqualThan(event.getStartTime(), batchStartTime, "Phase startTime >= batch startTime"); 421 long minDuration = 0; 422 long duration = event.getTimestamp() - event.getStartTime(); 423 Asserts.assertGreaterOrEqualThan(duration, minDuration, "Phase event duration >= " + minDuration); 424 } 425 } 426 } 427 } 428 429 /** 430 * Verifies that the collector name in initial configuration matches the name in garbage configuration event. 431 * If the names are not equal, then we check if this is an expected collector override. 432 * For example, if old collector in initial config is "G1Old" we allow both event "G1Old" and "SerialOld". 433 * 434 * @param batches All event batches. 435 * @param configEvent Initial configuration event. 436 */ 437 private void verifyCollectorNames(List<GCHelper.GcBatch> batches) { 438 for (GCHelper.GcBatch batch : batches) { 439 String name = batch.getName(); 440 Asserts.assertNotNull(name, "garbage_collection.name was null"); 441 boolean isYoung = batch.isYoungCollection(); 442 String expectedName = isYoung ? youngCollector : oldCollector; 443 if (!expectedName.equals(name)) { 444 // Collector names not equal. Check if the collector has been overridden by an expected collector. 445 String overrideKey = expectedName + "." + name; 446 boolean isOverride = GCHelper.collectorOverrides.contains(overrideKey); 447 Asserts.assertTrue(isOverride, String.format("Unexpected event name(%s) for collectors(%s, %s)", name, youngCollector, oldCollector)); 448 } 449 } 450 } 451 452 /** 453 * Verifies field "cause" in garbage_collection event. 454 * Only check that at cause is not null and that at least 1 cause is "System.gc()" 455 * We might want to check more cause reasons later. 456 * 457 * @param batches All event batches. 458 */ 459 private void verifyCollectionCause(List<GCHelper.GcBatch> batches) { 460 int systemGcCount = 0; 461 for (GCHelper.GcBatch batch : batches) { 462 FLREvent endEvent = batch.getEndEvent(); 463 String cause = (String)((FLRStruct)endEvent.getResolvedValue("cause")).getValue("cause"); 464 Asserts.assertNotNull(cause, "garbage_collection.cause was null"); 465 String collectorName = batch.getName(); 466 Asserts.assertNotNull(collectorName, "garbage_collection.name was null"); 467 if ("System.gc()".equals(cause)) { 468 systemGcCount++; 469 } 470 } 471 Asserts.assertGreaterThan(systemGcCount, 0, 472 String.format("Expected at least 1 cause of System.gc(), collectors(%s, %s)", youngCollector, oldCollector)); 473 } 474 475 /** 476 * Log all flight recorder events and to which batches the events belong. 477 * Only log to error file. It is too much to log to System.err. 478 * 479 * @param events All flight recorder events. 480 * @param batches The events grouped into batches belonging to a single GC. 481 */ 482 private void log(List<FLREvent> events, List<GCHelper.GcBatch> batches, GCHelper.CollectionSummary eventCounts, GCHelper.CollectionSummary beanCounts) { 483 GCHelper.log("EventCounts:"); 484 if (eventCounts != null) { 485 GCHelper.log(eventCounts.toString()); 486 } 487 GCHelper.log("BeanCounts:"); 488 if (beanCounts != null) { 489 GCHelper.log(beanCounts.toString()); 490 } 491 } 492 493 /** 494 * Thread that does a number of System.gc(). 495 */ 496 public static class SystemGcRunner implements Runnable { 497 private final int totalCollections; 498 499 public SystemGcRunner(int totalCollections) { 500 this.totalCollections = totalCollections; 501 } 502 503 public static SystemGcRunner create(int totalCollections) { 504 return new SystemGcRunner(totalCollections); 505 } 506 507 public void run() { 508 for (int i = 0; i < totalCollections; i++) { 509 GCEventAll.doSystemGc(); 510 } 511 } 512 } 513 514 /** 515 * Thread that creates garbage until a certain number of GCs has been run. 516 */ 517 public static class GarbageRunner implements Runnable { 518 private final int totalCollections; 519 public byte[] dummyBuffer = null; 520 521 public GarbageRunner(int totalCollections) { 522 this.totalCollections = totalCollections; 523 } 524 525 public static GarbageRunner create(int totalCollections) { 526 return new GarbageRunner(totalCollections); 527 } 528 529 public void run() { 530 long currCollections = GCHelper.CollectionSummary.createFromMxBeans().sum(); 531 long endCollections = totalCollections + currCollections; 532 Random r = new Random(0); 533 while (true) { 534 for (int i = 0; i < 1000; i++) { 535 dummyBuffer = new byte[r.nextInt(10000)]; 536 } 537 if (GCHelper.CollectionSummary.createFromMxBeans().sum() >= endCollections) { 538 break; 539 } 540 } 541 } 542 } 543 544 /** 545 * Thread that runs System.gc() and then wait for a number of GCs or a maximum time. 546 */ 547 public static class SystemGcWaitRunner implements Runnable { 548 private final int totalCollections; 549 private final int minWaitCollections; 550 private final long maxWaitMillis; 551 552 public SystemGcWaitRunner(int totalCollections, int minWaitCollections, long maxWaitMillis) { 553 this.totalCollections = totalCollections; 554 this.minWaitCollections = minWaitCollections; 555 this.maxWaitMillis = maxWaitMillis; 556 } 557 558 public static SystemGcWaitRunner create(int deltaCollections, int minWaitCollections, long maxWaitMillis) { 559 return new SystemGcWaitRunner(deltaCollections, minWaitCollections, maxWaitMillis); 560 } 561 562 public void run() { 563 long currCount = GCHelper.CollectionSummary.createFromMxBeans().sum(); 564 long endCount = totalCollections + currCount; 565 long nextSystemGcCount = currCount + minWaitCollections; 566 long now = System.currentTimeMillis(); 567 long nextSystemGcMillis = now + maxWaitMillis; 568 569 while (true) { 570 if (currCount >= nextSystemGcCount || System.currentTimeMillis() > nextSystemGcMillis) { 571 GCEventAll.doSystemGc(); 572 currCount = GCHelper.CollectionSummary.createFromMxBeans().sum(); 573 nextSystemGcCount = currCount + minWaitCollections; 574 } else { 575 try { 576 Thread.sleep(20); 577 } catch (InterruptedException e) { 578 e.printStackTrace(); 579 break; 580 } 581 } 582 currCount = GCHelper.CollectionSummary.createFromMxBeans().sum(); 583 if (currCount >= endCount) { 584 break; 585 } 586 } 587 } 588 } 589 590 }