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 PrintStream defaultErrorLog = null; 84 85 public static int getGcId(RecordedEvent event) { 86 return Events.assertField(event, "gcId").getValue(); 87 } 88 89 public static boolean isGcEvent(RecordedEvent event) { 90 for (ValueDescriptor v : event.getFields()) { 91 if ("gcId".equals(v.getName())) { 92 return true; 93 } 94 } 95 return false; 96 } 97 98 // public static String getEventDesc(RecordedEvent event) { 99 // final String path = event.getEventType().getName(); 100 // if (!isGcEvent(event)) { 101 // return path; 102 // } 103 // if (event_garbage_collection.equals(path)) { 104 // String name = Events.assertField(event, "name").getValue(); 105 // String cause = Events.assertField(event, "cause").getValue(); 106 // return String.format("path=%s, gcId=%d, endTime=%d, name=%s, cause=%s, startTime=%d", 107 // path, getGcId(event), event.getEndTime(), name, cause, event.getStartTime()); 108 // } else { 109 // return String.format("path=%s, gcId=%d, endTime=%d", path, getGcId(event), event.getEndTime()); 110 // } 111 // } 112 113 public static RecordedEvent getConfigEvent(List<RecordedEvent> events) throws Exception { 114 for (RecordedEvent event : events) { 115 if (EventNames.GCConfiguration.equals(event.getEventType().getName())) { 116 return event; 117 } 118 } 119 fail("Could not find event " + EventNames.GCConfiguration); 120 return null; 121 } 122 123 public static void callSystemGc(int num, boolean withGarbage) { 124 for (int i = 0; i < num; i++) { 125 if (withGarbage) { 126 makeGarbage(); 127 } 128 System.gc(); 129 } 130 } 131 132 private static void makeGarbage() { 133 Object[] garbage = new Object[1024]; 134 for (int i = 0; i < 1024; i++) { 135 garbage[i] = new Object(); 136 } 137 } 138 139 // Removes gcEvents with lowest and highest gcID. This is used to filter out 140 // any incomplete GCs if the recording started/stopped in the middle of a GC. 141 // We also filters out events without gcId. Those events are not needed. 142 public static List<RecordedEvent> removeFirstAndLastGC(List<RecordedEvent> events) { 143 int minGcId = Integer.MAX_VALUE; 144 int maxGcId = Integer.MIN_VALUE; 145 // Find min/max gcId 146 for (RecordedEvent event : events) { 147 if (Events.hasField(event, "gcId")) { 148 int gcId = Events.assertField(event, "gcId").getValue(); 149 minGcId = Math.min(gcId, minGcId); 150 maxGcId = Math.max(gcId, maxGcId); 151 } 152 } 153 154 // Add all events except those with gcId = min/max gcId 155 List<RecordedEvent> filteredEvents = new ArrayList<>(); 156 for (RecordedEvent event : events) { 157 if (Events.hasField(event, "gcId")) { 158 int gcId = Events.assertField(event, "gcId").getValue(); 159 if (gcId != minGcId && gcId != maxGcId) { 160 filteredEvents.add(event); 161 } 162 } 163 } 164 return filteredEvents; 165 } 166 167 public static Map<String, Boolean> beanCollectorTypes = new HashMap<>(); 168 public static Set<String> collectorOverrides = new HashSet<>(); 169 public static Map<String, String[]> requiredEvents = new HashMap<>(); 170 171 static { 172 // young GarbageCollectionMXBeans. 173 beanCollectorTypes.put("G1 Young Generation", true); 174 beanCollectorTypes.put("Copy", true); 175 beanCollectorTypes.put("PS Scavenge", true); 176 beanCollectorTypes.put("ParNew", true); 177 178 // old GarbageCollectionMXBeans. 179 beanCollectorTypes.put("G1 Old Generation", false); 180 beanCollectorTypes.put("ConcurrentMarkSweep", false); 181 beanCollectorTypes.put("PS MarkSweep", false); 182 beanCollectorTypes.put("MarkSweepCompact", false); 183 184 // List of expected collector overrides. "A.B" means that collector A may use collector B. 185 collectorOverrides.add("G1Old.G1Full"); 186 collectorOverrides.add("G1Old.SerialOld"); 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 213 /** 214 * Contains all GC events belonging to the same GC (same gcId). 215 */ 216 public static class GcBatch { 217 private List<RecordedEvent> events = new ArrayList<>(); 218 219 public int getGcId() { 220 if (events.isEmpty()) { 221 return -1; 222 } 223 return GCHelper.getGcId(events.get(0)); 224 } 225 226 public String getName() { 227 RecordedEvent endEvent = getEndEvent(); 228 String name = endEvent == null ? null : Events.assertField(endEvent, "name").getValue(); 229 return name == null ? "null" : name; 230 } 231 232 public RecordedEvent getEndEvent() { 233 return getEvent(event_garbage_collection); 234 } 235 236 public boolean addEvent(RecordedEvent event) { 237 if (!events.isEmpty()) { 238 assertEquals(getGcId(), GCHelper.getGcId(event), "Wrong gcId in event. Error in test code."); 239 } 240 boolean isEndEvent = event_garbage_collection.equals(event.getEventType().getName()); 241 if (isEndEvent) { 242 // Verify that we have not already got a garbage_collection event with this gcId. 243 assertNull(getEndEvent(), String.format("Multiple %s for gcId %d", event_garbage_collection, getGcId())); 244 } 245 events.add(event); 246 return isEndEvent; 247 } 248 249 public boolean isYoungCollection() { 250 boolean isYoung = containsEvent(event_young_garbage_collection); 251 boolean isOld = containsEvent(event_old_garbage_collection); 252 assertNotEquals(isYoung, isOld, "isYoung and isOld was same for batch: " + toString()); 253 return isYoung; 254 } 255 256 public int getEventCount() { 257 return events.size(); 258 } 259 260 public RecordedEvent getEvent(int index) { 261 return events.get(index); 262 } 263 264 public List<RecordedEvent> getEvents() { 265 return events; 266 } 267 268 public RecordedEvent getEvent(String eventPath) { 269 for (RecordedEvent event : events) { 270 if (eventPath.equals(event.getEventType().getName())) { 271 return event; 272 } 273 } 274 return null; 275 } 276 277 public boolean containsEvent(String eventPath) { 278 return getEvent(eventPath) != null; 279 } 280 281 public String toString() { 282 RecordedEvent endEvent = getEndEvent(); 283 Instant startTime = Instant.EPOCH; 284 String cause = "?"; 285 String name = "?"; 286 if (endEvent != null) { 287 name = getName(); 288 startTime = endEvent.getStartTime(); 289 cause = Events.assertField(endEvent, "cause").getValue(); 290 } 291 return String.format("GcEvent: gcId=%d, method=%s, cause=%s, startTime=%s", 292 getGcId(), name, cause, startTime); 293 } 294 295 public String getLog() { 296 StringBuilder sb = new StringBuilder(); 297 sb.append(this.toString() + System.getProperty("line.separator")); 298 for (RecordedEvent event : events) { 299 sb.append(String.format("event: %s%n", event)); 300 } 301 return sb.toString(); 302 } 303 304 // Group all events info batches. 305 public static List<GcBatch> createFromEvents(List<RecordedEvent> events) throws Exception { 306 Stack<Integer> openGcIds = new Stack<>(); 307 List<GcBatch> batches = new ArrayList<>(); 308 GcBatch currBatch = null; 309 310 for (RecordedEvent event : events) { 311 if (!isGcEvent(event)) { 312 continue; 313 } 314 int gcId = GCHelper.getGcId(event); 315 if (currBatch == null || currBatch.getGcId() != gcId) { 316 currBatch = null; 317 // Search for existing batch 318 for (GcBatch loopBatch : batches) { 319 if (gcId == loopBatch.getGcId()) { 320 currBatch = loopBatch; 321 break; 322 } 323 } 324 if (currBatch == null) { 325 // No existing batch. Create new. 326 currBatch = new GcBatch(); 327 batches.add(currBatch); 328 openGcIds.push(new Integer(gcId)); 329 } 330 } 331 boolean isEndEvent = currBatch.addEvent(event); 332 if (isEndEvent) { 333 openGcIds.pop(); 334 } 335 } 336 // Verify that all start_garbage_collection events have received a corresponding "garbage_collection" event. 337 for (GcBatch batch : batches) { 338 if (batch.getEndEvent() == null) { 339 System.out.println(batch.getLog()); 340 } 341 assertNotNull(batch.getEndEvent(), "GcBatch has no end event"); 342 } 343 return batches; 344 } 345 } 346 347 /** 348 * Contains number of collections and sum pause time for young and old collections. 349 */ 350 public static class CollectionSummary { 351 public long collectionCountOld; 352 public long collectionCountYoung; 353 public long collectionTimeOld; 354 public long collectionTimeYoung; 355 private Set<String> names = new HashSet<>(); 356 357 public void add(String collectorName, boolean isYoung, long count, long time) { 358 if (isYoung) { 359 collectionCountYoung += count; 360 collectionTimeYoung += time; 361 } else { 362 collectionCountOld += count; 363 collectionTimeOld += time; 364 } 365 if (!names.contains(collectorName)) { 366 names.add(collectorName); 367 } 368 } 369 370 public long sum() { 371 return collectionCountOld + collectionCountYoung; 372 } 373 374 public CollectionSummary calcDelta(CollectionSummary prev) { 375 CollectionSummary delta = new CollectionSummary(); 376 delta.collectionCountOld = this.collectionCountOld - prev.collectionCountOld; 377 delta.collectionTimeOld = this.collectionTimeOld - prev.collectionTimeOld; 378 delta.collectionCountYoung = this.collectionCountYoung - prev.collectionCountYoung; 379 delta.collectionTimeYoung = this.collectionTimeYoung - prev.collectionTimeYoung; 380 delta.names.addAll(this.names); 381 delta.names.addAll(prev.names); 382 return delta; 383 } 384 385 public static CollectionSummary createFromMxBeans() { 386 CollectionSummary summary = new CollectionSummary(); 387 List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); 388 for (int c=0; c<gcBeans.size(); c++) { 389 GarbageCollectorMXBean currBean = gcBeans.get(c); 390 Boolean isYoung = beanCollectorTypes.get(currBean.getName()); 391 assertNotNull(isYoung, "Unknown MXBean name: " + currBean.getName()); 392 long collectionTime = currBean.getCollectionTime() * 1000; // Convert from millis to micros. 393 summary.add(currBean.getName(), isYoung.booleanValue(), currBean.getCollectionCount(), collectionTime); 394 } 395 return summary; 396 } 397 398 public static CollectionSummary createFromEvents(List<GcBatch> batches) { 399 CollectionSummary summary = new CollectionSummary(); 400 for (GcBatch batch : batches) { 401 RecordedEvent endEvent = batch.getEndEvent(); 402 assertNotNull(endEvent, "No end event in batch with gcId " + batch.getGcId()); 403 String name = batch.getName(); 404 summary.add(name, batch.isYoungCollection(), 1, Events.assertField(endEvent, "sumOfPauses").getValue()); 405 } 406 return summary; 407 } 408 409 public String toString() { 410 StringBuilder collectorNames = new StringBuilder(); 411 for (String s : names) { 412 if (collectorNames.length() > 0) { 413 collectorNames.append(", "); 414 } 415 collectorNames.append(s); 416 } 417 return String.format("CollectionSummary: young.collections=%d, young.time=%d, old.collections=%d, old.time=%d, collectors=(%s)", 418 collectionCountYoung, collectionTimeYoung, collectionCountOld, collectionTimeOld, collectorNames); 419 } 420 } 421 422 public static PrintStream getDefaultErrorLog() { 423 if (defaultErrorLog == null) { 424 try { 425 defaultErrorLog = new PrintStream(new FileOutputStream("error.log", true)); 426 } catch (IOException e) { 427 e.printStackTrace(); 428 defaultErrorLog = System.err; 429 } 430 } 431 return defaultErrorLog; 432 } 433 434 public static void log(Object msg) { 435 log(msg, System.err); 436 log(msg, getDefaultErrorLog()); 437 } 438 439 public static void log(Object msg, PrintStream ps) { 440 ps.println(msg); 441 } 442 443 public static boolean isValidG1HeapRegionType(final String type) { 444 return g1HeapRegionTypes.contains(type); 445 } 446 447 /** 448 * Helper function to align heap size up. 449 * 450 * @param value 451 * @param alignment 452 * @return aligned value 453 */ 454 public static long alignUp(long value, long alignment) { 455 return (value + alignment - 1) & ~(alignment - 1); 456 } 457 458 /** 459 * Helper function to align heap size down. 460 * 461 * @param value 462 * @param alignment 463 * @return aligned value 464 */ 465 public static long alignDown(long value, long alignment) { 466 return value & ~(alignment - 1); 467 } 468 }