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 package jdk.test.lib.jfr; 26 27 import static jdk.test.lib.Asserts.assertEquals; 28 import static jdk.test.lib.Asserts.assertNotEquals; 29 import static jdk.test.lib.Asserts.assertNotNull; 30 import static jdk.test.lib.Asserts.assertNull; 31 import static jdk.test.lib.Asserts.fail; 32 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.PrintStream; 36 import java.lang.management.GarbageCollectorMXBean; 37 import java.lang.management.ManagementFactory; 38 import java.time.Instant; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collections; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Set; 47 import java.util.Stack; 48 49 import jdk.jfr.ValueDescriptor; 50 import jdk.jfr.consumer.RecordedEvent; 51 52 /** 53 * Mixed helper classes to test GC events. 54 */ 55 public class GCHelper { 56 public static final String event_garbage_collection = EventNames.GarbageCollection; 57 public static final String event_young_garbage_collection = EventNames.YoungGarbageCollection; 58 public static final String event_old_garbage_collection = EventNames.OldGarbageCollection; 59 public static final String event_parold_garbage_collection = EventNames.ParallelOldCollection; 60 public static final String event_g1_garbage_collection = EventNames.G1GarbageCollection; 61 public static final String event_heap_summary = EventNames.GCHeapSummary; 62 public static final String event_heap_ps_summary = EventNames.PSHeapSummary; 63 public static final String event_heap_metaspace_summary = EventNames.MetaspaceSummary; 64 public static final String event_reference_statistics = EventNames.GCReferenceStatistics; 65 public static final String event_phases_pause = EventNames.GCPhasePause; 66 public static final String event_phases_level_1 = EventNames.GCPhasePauseLevel1; 67 public static final String event_phases_level_2 = EventNames.GCPhasePauseLevel2; 68 public static final String event_phases_level_3 = EventNames.GCPhasePauseLevel3; 69 70 public static final String gcG1New = "G1New"; 71 public static final String gcParNew = "ParNew"; 72 public static final String gcDefNew = "DefNew"; 73 public static final String gcParallelScavenge = "ParallelScavenge"; 74 public static final String gcG1Old = "G1Old"; 75 public static final String gcG1Full = "G1Full"; 76 public static final String gcConcurrentMarkSweep = "ConcurrentMarkSweep"; 77 public static final String gcSerialOld = "SerialOld"; 78 public static final String gcPSMarkSweep = "PSMarkSweep"; 79 public static final String gcParallelOld = "ParallelOld"; 80 public static final String pauseLevelEvent = "GCPhasePauseLevel"; 81 82 private static final List<String> g1HeapRegionTypes; 83 private static final List<String> shenandoahHeapRegionStates; 84 private static PrintStream defaultErrorLog = null; 85 86 public static int getGcId(RecordedEvent event) { 87 return Events.assertField(event, "gcId").getValue(); 88 } 89 90 public static boolean isGcEvent(RecordedEvent event) { 91 for (ValueDescriptor v : event.getFields()) { 92 if ("gcId".equals(v.getName())) { 93 return true; 94 } 95 } 96 return false; 97 } 98 99 // public static String getEventDesc(RecordedEvent event) { 100 // final String path = event.getEventType().getName(); 101 // if (!isGcEvent(event)) { 102 // return path; 103 // } 104 // if (event_garbage_collection.equals(path)) { 105 // String name = Events.assertField(event, "name").getValue(); 106 // String cause = Events.assertField(event, "cause").getValue(); 107 // return String.format("path=%s, gcId=%d, endTime=%d, name=%s, cause=%s, startTime=%d", 108 // path, getGcId(event), event.getEndTime(), name, cause, event.getStartTime()); 109 // } else { 110 // return String.format("path=%s, gcId=%d, endTime=%d", path, getGcId(event), event.getEndTime()); 111 // } 112 // } 113 114 public static RecordedEvent getConfigEvent(List<RecordedEvent> events) throws Exception { 115 for (RecordedEvent event : events) { 116 if (EventNames.GCConfiguration.equals(event.getEventType().getName())) { 117 return event; 118 } 119 } 120 fail("Could not find event " + EventNames.GCConfiguration); 121 return null; 122 } 123 124 public static void callSystemGc(int num, boolean withGarbage) { 125 for (int i = 0; i < num; i++) { 126 if (withGarbage) { 127 makeGarbage(); 128 } 129 System.gc(); 130 } 131 } 132 133 private static void makeGarbage() { 134 Object[] garbage = new Object[1024]; 135 for (int i = 0; i < 1024; i++) { 136 garbage[i] = new Object(); 137 } 138 } 139 140 // Removes gcEvents with lowest and highest gcID. This is used to filter out 141 // any incomplete GCs if the recording started/stopped in the middle of a GC. 142 // We also filters out events without gcId. Those events are not needed. 143 public static List<RecordedEvent> removeFirstAndLastGC(List<RecordedEvent> events) { 144 int minGcId = Integer.MAX_VALUE; 145 int maxGcId = Integer.MIN_VALUE; 146 // Find min/max gcId 147 for (RecordedEvent event : events) { 148 if (Events.hasField(event, "gcId")) { 149 int gcId = Events.assertField(event, "gcId").getValue(); 150 minGcId = Math.min(gcId, minGcId); 151 maxGcId = Math.max(gcId, maxGcId); 152 } 153 } 154 155 // Add all events except those with gcId = min/max gcId 156 List<RecordedEvent> filteredEvents = new ArrayList<>(); 157 for (RecordedEvent event : events) { 158 if (Events.hasField(event, "gcId")) { 159 int gcId = Events.assertField(event, "gcId").getValue(); 160 if (gcId != minGcId && gcId != maxGcId) { 161 filteredEvents.add(event); 162 } 163 } 164 } 165 return filteredEvents; 166 } 167 168 public static Map<String, Boolean> beanCollectorTypes = new HashMap<>(); 169 public static Set<String> collectorOverrides = new HashSet<>(); 170 public static Map<String, String[]> requiredEvents = new HashMap<>(); 171 172 static { 173 // young GarbageCollectionMXBeans. 174 beanCollectorTypes.put("G1 Young Generation", true); 175 beanCollectorTypes.put("Copy", true); 176 beanCollectorTypes.put("PS Scavenge", true); 177 beanCollectorTypes.put("ParNew", true); 178 179 // old GarbageCollectionMXBeans. 180 beanCollectorTypes.put("G1 Old Generation", false); 181 beanCollectorTypes.put("ConcurrentMarkSweep", false); 182 beanCollectorTypes.put("PS MarkSweep", false); 183 beanCollectorTypes.put("MarkSweepCompact", false); 184 185 // List of expected collector overrides. "A.B" means that collector A may use collector B. 186 collectorOverrides.add("G1Old.G1Full"); 187 collectorOverrides.add("ConcurrentMarkSweep.SerialOld"); 188 collectorOverrides.add("SerialOld.PSMarkSweep"); 189 190 requiredEvents.put(gcG1New, new String[] {event_heap_summary, event_young_garbage_collection}); 191 requiredEvents.put(gcParNew, new String[] {event_heap_summary, event_heap_metaspace_summary, event_phases_pause, event_phases_level_1, event_young_garbage_collection}); 192 requiredEvents.put(gcDefNew, new String[] {event_heap_summary, event_heap_metaspace_summary, event_phases_pause, event_phases_level_1, event_young_garbage_collection}); 193 requiredEvents.put(gcParallelScavenge, new String[] {event_heap_summary, event_heap_ps_summary, event_heap_metaspace_summary, event_reference_statistics, event_phases_pause, event_phases_level_1, event_young_garbage_collection}); 194 requiredEvents.put(gcG1Old, new String[] {event_heap_summary, event_old_garbage_collection}); 195 requiredEvents.put(gcG1Full, new String[] {event_heap_summary, event_heap_metaspace_summary, event_phases_pause, event_phases_level_1, event_old_garbage_collection}); 196 requiredEvents.put(gcConcurrentMarkSweep, new String[] {event_phases_pause, event_phases_level_1, event_old_garbage_collection}); 197 requiredEvents.put(gcSerialOld, new String[] {event_heap_summary, event_heap_metaspace_summary, event_phases_pause, event_phases_level_1, event_old_garbage_collection}); 198 requiredEvents.put(gcParallelOld, new String[] {event_heap_summary, event_heap_ps_summary, event_heap_metaspace_summary, event_reference_statistics, event_phases_pause, event_phases_level_1, event_old_garbage_collection, event_parold_garbage_collection}); 199 200 String[] g1HeapRegionTypeLiterals = new String[] { 201 "Free", 202 "Eden", 203 "Survivor", 204 "Starts Humongous", 205 "Continues Humongous", 206 "Old", 207 "Archive" 208 }; 209 210 g1HeapRegionTypes = Collections.unmodifiableList(Arrays.asList(g1HeapRegionTypeLiterals)); 211 212 String[] shenandoahHeapRegionStateLiterals = new String[] { 213 "Empty Uncommitted", 214 "Empty Committed", 215 "Regular", 216 "Humongous Start", 217 "Humongous Continuation", 218 "Humongous Start, Pinned", 219 "Collection Set", 220 "Pinned", 221 "Collection Set, Pinned", 222 "Trash" 223 }; 224 225 shenandoahHeapRegionStates = Collections.unmodifiableList(Arrays.asList(shenandoahHeapRegionStateLiterals)); 226 } 227 228 /** 229 * Contains all GC events belonging to the same GC (same gcId). 230 */ 231 public static class GcBatch { 232 private List<RecordedEvent> events = new ArrayList<>(); 233 234 public int getGcId() { 235 if (events.isEmpty()) { 236 return -1; 237 } 238 return GCHelper.getGcId(events.get(0)); 239 } 240 241 public String getName() { 242 RecordedEvent endEvent = getEndEvent(); 243 String name = endEvent == null ? null : Events.assertField(endEvent, "name").getValue(); 244 return name == null ? "null" : name; 245 } 246 247 public RecordedEvent getEndEvent() { 248 return getEvent(event_garbage_collection); 249 } 250 251 public boolean addEvent(RecordedEvent event) { 252 if (!events.isEmpty()) { 253 assertEquals(getGcId(), GCHelper.getGcId(event), "Wrong gcId in event. Error in test code."); 254 } 255 boolean isEndEvent = event_garbage_collection.equals(event.getEventType().getName()); 256 if (isEndEvent) { 257 // Verify that we have not already got a garbage_collection event with this gcId. 258 assertNull(getEndEvent(), String.format("Multiple %s for gcId %d", event_garbage_collection, getGcId())); 259 } 260 events.add(event); 261 return isEndEvent; 262 } 263 264 public boolean isYoungCollection() { 265 boolean isYoung = containsEvent(event_young_garbage_collection); 266 boolean isOld = containsEvent(event_old_garbage_collection); 267 assertNotEquals(isYoung, isOld, "isYoung and isOld was same for batch: " + toString()); 268 return isYoung; 269 } 270 271 public int getEventCount() { 272 return events.size(); 273 } 274 275 public RecordedEvent getEvent(int index) { 276 return events.get(index); 277 } 278 279 public List<RecordedEvent> getEvents() { 280 return events; 281 } 282 283 public RecordedEvent getEvent(String eventPath) { 284 for (RecordedEvent event : events) { 285 if (eventPath.equals(event.getEventType().getName())) { 286 return event; 287 } 288 } 289 return null; 290 } 291 292 public boolean containsEvent(String eventPath) { 293 return getEvent(eventPath) != null; 294 } 295 296 public String toString() { 297 RecordedEvent endEvent = getEndEvent(); 298 Instant startTime = Instant.EPOCH; 299 String cause = "?"; 300 String name = "?"; 301 if (endEvent != null) { 302 name = getName(); 303 startTime = endEvent.getStartTime(); 304 cause = Events.assertField(endEvent, "cause").getValue(); 305 } 306 return String.format("GcEvent: gcId=%d, method=%s, cause=%s, startTime=%s", 307 getGcId(), name, cause, startTime); 308 } 309 310 public String getLog() { 311 StringBuilder sb = new StringBuilder(); 312 sb.append(this.toString() + System.getProperty("line.separator")); 313 for (RecordedEvent event : events) { 314 sb.append(String.format("event: %s%n", event)); 315 } 316 return sb.toString(); 317 } 318 319 // Group all events info batches. 320 public static List<GcBatch> createFromEvents(List<RecordedEvent> events) throws Exception { 321 Stack<Integer> openGcIds = new Stack<>(); 322 List<GcBatch> batches = new ArrayList<>(); 323 GcBatch currBatch = null; 324 325 for (RecordedEvent event : events) { 326 if (!isGcEvent(event)) { 327 continue; 328 } 329 int gcId = GCHelper.getGcId(event); 330 if (currBatch == null || currBatch.getGcId() != gcId) { 331 currBatch = null; 332 // Search for existing batch 333 for (GcBatch loopBatch : batches) { 334 if (gcId == loopBatch.getGcId()) { 335 currBatch = loopBatch; 336 break; 337 } 338 } 339 if (currBatch == null) { 340 // No existing batch. Create new. 341 currBatch = new GcBatch(); 342 batches.add(currBatch); 343 openGcIds.push(new Integer(gcId)); 344 } 345 } 346 boolean isEndEvent = currBatch.addEvent(event); 347 if (isEndEvent) { 348 openGcIds.pop(); 349 } 350 } 351 // Verify that all start_garbage_collection events have received a corresponding "garbage_collection" event. 352 for (GcBatch batch : batches) { 353 if (batch.getEndEvent() == null) { 354 System.out.println(batch.getLog()); 355 } 356 assertNotNull(batch.getEndEvent(), "GcBatch has no end event"); 357 } 358 return batches; 359 } 360 } 361 362 /** 363 * Contains number of collections and sum pause time for young and old collections. 364 */ 365 public static class CollectionSummary { 366 public long collectionCountOld; 367 public long collectionCountYoung; 368 public long collectionTimeOld; 369 public long collectionTimeYoung; 370 private Set<String> names = new HashSet<>(); 371 372 public void add(String collectorName, boolean isYoung, long count, long time) { 373 if (isYoung) { 374 collectionCountYoung += count; 375 collectionTimeYoung += time; 376 } else { 377 collectionCountOld += count; 378 collectionTimeOld += time; 379 } 380 if (!names.contains(collectorName)) { 381 names.add(collectorName); 382 } 383 } 384 385 public long sum() { 386 return collectionCountOld + collectionCountYoung; 387 } 388 389 public CollectionSummary calcDelta(CollectionSummary prev) { 390 CollectionSummary delta = new CollectionSummary(); 391 delta.collectionCountOld = this.collectionCountOld - prev.collectionCountOld; 392 delta.collectionTimeOld = this.collectionTimeOld - prev.collectionTimeOld; 393 delta.collectionCountYoung = this.collectionCountYoung - prev.collectionCountYoung; 394 delta.collectionTimeYoung = this.collectionTimeYoung - prev.collectionTimeYoung; 395 delta.names.addAll(this.names); 396 delta.names.addAll(prev.names); 397 return delta; 398 } 399 400 public static CollectionSummary createFromMxBeans() { 401 CollectionSummary summary = new CollectionSummary(); 402 List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); 403 for (int c=0; c<gcBeans.size(); c++) { 404 GarbageCollectorMXBean currBean = gcBeans.get(c); 405 Boolean isYoung = beanCollectorTypes.get(currBean.getName()); 406 assertNotNull(isYoung, "Unknown MXBean name: " + currBean.getName()); 407 long collectionTime = currBean.getCollectionTime() * 1000; // Convert from millis to micros. 408 summary.add(currBean.getName(), isYoung.booleanValue(), currBean.getCollectionCount(), collectionTime); 409 } 410 return summary; 411 } 412 413 public static CollectionSummary createFromEvents(List<GcBatch> batches) { 414 CollectionSummary summary = new CollectionSummary(); 415 for (GcBatch batch : batches) { 416 RecordedEvent endEvent = batch.getEndEvent(); 417 assertNotNull(endEvent, "No end event in batch with gcId " + batch.getGcId()); 418 String name = batch.getName(); 419 summary.add(name, batch.isYoungCollection(), 1, Events.assertField(endEvent, "sumOfPauses").getValue()); 420 } 421 return summary; 422 } 423 424 public String toString() { 425 StringBuilder collectorNames = new StringBuilder(); 426 for (String s : names) { 427 if (collectorNames.length() > 0) { 428 collectorNames.append(", "); 429 } 430 collectorNames.append(s); 431 } 432 return String.format("CollectionSummary: young.collections=%d, young.time=%d, old.collections=%d, old.time=%d, collectors=(%s)", 433 collectionCountYoung, collectionTimeYoung, collectionCountOld, collectionTimeOld, collectorNames); 434 } 435 } 436 437 public static PrintStream getDefaultErrorLog() { 438 if (defaultErrorLog == null) { 439 try { 440 defaultErrorLog = new PrintStream(new FileOutputStream("error.log", true)); 441 } catch (IOException e) { 442 e.printStackTrace(); 443 defaultErrorLog = System.err; 444 } 445 } 446 return defaultErrorLog; 447 } 448 449 public static void log(Object msg) { 450 log(msg, System.err); 451 log(msg, getDefaultErrorLog()); 452 } 453 454 public static void log(Object msg, PrintStream ps) { 455 ps.println(msg); 456 } 457 458 public static boolean isValidG1HeapRegionType(final String type) { 459 return g1HeapRegionTypes.contains(type); 460 } 461 462 public static boolean assertIsValidShenandoahHeapRegionState(final String state) { 463 if (!shenandoahHeapRegionStates.contains(state)) { 464 throw new AssertionError("Unknown state '" + state + "', valid heap region states are " + shenandoahHeapRegionStates); 465 } 466 return true; 467 } 468 469 /** 470 * Helper function to align heap size up. 471 * 472 * @param value 473 * @param alignment 474 * @return aligned value 475 */ 476 public static long alignUp(long value, long alignment) { 477 return (value + alignment - 1) & ~(alignment - 1); 478 } 479 480 /** 481 * Helper function to align heap size down. 482 * 483 * @param value 484 * @param alignment 485 * @return aligned value 486 */ 487 public static long alignDown(long value, long alignment) { 488 return value & ~(alignment - 1); 489 } 490 }