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("ConcurrentMarkSweep.SerialOld"); 187 collectorOverrides.add("SerialOld.PSMarkSweep"); 188 189 requiredEvents.put(gcG1New, new String[] {event_heap_summary, event_young_garbage_collection}); 190 requiredEvents.put(gcParNew, new String[] {event_heap_summary, event_heap_metaspace_summary, event_phases_pause, event_phases_level_1, event_young_garbage_collection}); 191 requiredEvents.put(gcDefNew, new String[] {event_heap_summary, event_heap_metaspace_summary, event_phases_pause, event_phases_level_1, event_young_garbage_collection}); 192 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}); 193 requiredEvents.put(gcG1Old, new String[] {event_heap_summary, event_old_garbage_collection}); 194 requiredEvents.put(gcG1Full, new String[] {event_heap_summary, event_heap_metaspace_summary, event_phases_pause, event_phases_level_1, event_old_garbage_collection}); 195 requiredEvents.put(gcConcurrentMarkSweep, new String[] {event_phases_pause, event_phases_level_1, event_old_garbage_collection}); 196 requiredEvents.put(gcSerialOld, new String[] {event_heap_summary, event_heap_metaspace_summary, event_phases_pause, event_phases_level_1, event_old_garbage_collection}); 197 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}); 198 199 String[] g1HeapRegionTypeLiterals = new String[] { 200 "Free", 201 "Eden", 202 "Survivor", 203 "Starts Humongous", 204 "Continues Humongous", 205 "Old", 206 "Archive" 207 }; 208 209 g1HeapRegionTypes = Collections.unmodifiableList(Arrays.asList(g1HeapRegionTypeLiterals)); 210 } 211 212 /** 213 * Contains all GC events belonging to the same GC (same gcId). 214 */ 215 public static class GcBatch { 216 private List<RecordedEvent> events = new ArrayList<>(); 217 218 public int getGcId() { 219 if (events.isEmpty()) { 220 return -1; 221 } 222 return GCHelper.getGcId(events.get(0)); 223 } 224 225 public String getName() { 226 RecordedEvent endEvent = getEndEvent(); 227 String name = endEvent == null ? null : Events.assertField(endEvent, "name").getValue(); 228 return name == null ? "null" : name; 229 } 230 231 public RecordedEvent getEndEvent() { 232 return getEvent(event_garbage_collection); 233 } 234 235 public boolean addEvent(RecordedEvent event) { 236 if (!events.isEmpty()) { 237 assertEquals(getGcId(), GCHelper.getGcId(event), "Wrong gcId in event. Error in test code."); 238 } 239 boolean isEndEvent = event_garbage_collection.equals(event.getEventType().getName()); 240 if (isEndEvent) { 241 // Verify that we have not already got a garbage_collection event with this gcId. 242 assertNull(getEndEvent(), String.format("Multiple %s for gcId %d", event_garbage_collection, getGcId())); 243 } 244 events.add(event); 245 return isEndEvent; 246 } 247 248 public boolean isYoungCollection() { 249 boolean isYoung = containsEvent(event_young_garbage_collection); 250 boolean isOld = containsEvent(event_old_garbage_collection); 251 assertNotEquals(isYoung, isOld, "isYoung and isOld was same for batch: " + toString()); 252 return isYoung; 253 } 254 255 public int getEventCount() { 256 return events.size(); 257 } 258 259 public RecordedEvent getEvent(int index) { 260 return events.get(index); 261 } 262 263 public List<RecordedEvent> getEvents() { 264 return events; 265 } 266 267 public RecordedEvent getEvent(String eventPath) { 268 for (RecordedEvent event : events) { 269 if (eventPath.equals(event.getEventType().getName())) { 270 return event; 271 } 272 } 273 return null; 274 } 275 276 public boolean containsEvent(String eventPath) { 277 return getEvent(eventPath) != null; 278 } 279 280 public String toString() { 281 RecordedEvent endEvent = getEndEvent(); 282 Instant startTime = Instant.EPOCH; 283 String cause = "?"; 284 String name = "?"; 285 if (endEvent != null) { 286 name = getName(); 287 startTime = endEvent.getStartTime(); 288 cause = Events.assertField(endEvent, "cause").getValue(); 289 } 290 return String.format("GcEvent: gcId=%d, method=%s, cause=%s, startTime=%s", 291 getGcId(), name, cause, startTime); 292 } 293 294 public String getLog() { 295 StringBuilder sb = new StringBuilder(); 296 sb.append(this.toString() + System.getProperty("line.separator")); 297 for (RecordedEvent event : events) { 298 sb.append(String.format("event: %s%n", event)); 299 } 300 return sb.toString(); 301 } 302 303 // Group all events info batches. 304 public static List<GcBatch> createFromEvents(List<RecordedEvent> events) throws Exception { 305 Stack<Integer> openGcIds = new Stack<>(); 306 List<GcBatch> batches = new ArrayList<>(); 307 GcBatch currBatch = null; 308 309 for (RecordedEvent event : events) { 310 if (!isGcEvent(event)) { 311 continue; 312 } 313 int gcId = GCHelper.getGcId(event); 314 if (currBatch == null || currBatch.getGcId() != gcId) { 315 currBatch = null; 316 // Search for existing batch 317 for (GcBatch loopBatch : batches) { 318 if (gcId == loopBatch.getGcId()) { 319 currBatch = loopBatch; 320 break; 321 } 322 } 323 if (currBatch == null) { 324 // No existing batch. Create new. 325 currBatch = new GcBatch(); 326 batches.add(currBatch); 327 openGcIds.push(new Integer(gcId)); 328 } 329 } 330 boolean isEndEvent = currBatch.addEvent(event); 331 if (isEndEvent) { 332 openGcIds.pop(); 333 } 334 } 335 // Verify that all start_garbage_collection events have received a corresponding "garbage_collection" event. 336 for (GcBatch batch : batches) { 337 if (batch.getEndEvent() == null) { 338 System.out.println(batch.getLog()); 339 } 340 assertNotNull(batch.getEndEvent(), "GcBatch has no end event"); 341 } 342 return batches; 343 } 344 } 345 346 /** 347 * Contains number of collections and sum pause time for young and old collections. 348 */ 349 public static class CollectionSummary { 350 public long collectionCountOld; 351 public long collectionCountYoung; 352 public long collectionTimeOld; 353 public long collectionTimeYoung; 354 private Set<String> names = new HashSet<>(); 355 356 public void add(String collectorName, boolean isYoung, long count, long time) { 357 if (isYoung) { 358 collectionCountYoung += count; 359 collectionTimeYoung += time; 360 } else { 361 collectionCountOld += count; 362 collectionTimeOld += time; 363 } 364 if (!names.contains(collectorName)) { 365 names.add(collectorName); 366 } 367 } 368 369 public long sum() { 370 return collectionCountOld + collectionCountYoung; 371 } 372 373 public CollectionSummary calcDelta(CollectionSummary prev) { 374 CollectionSummary delta = new CollectionSummary(); 375 delta.collectionCountOld = this.collectionCountOld - prev.collectionCountOld; 376 delta.collectionTimeOld = this.collectionTimeOld - prev.collectionTimeOld; 377 delta.collectionCountYoung = this.collectionCountYoung - prev.collectionCountYoung; 378 delta.collectionTimeYoung = this.collectionTimeYoung - prev.collectionTimeYoung; 379 delta.names.addAll(this.names); 380 delta.names.addAll(prev.names); 381 return delta; 382 } 383 384 public static CollectionSummary createFromMxBeans() { 385 CollectionSummary summary = new CollectionSummary(); 386 List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); 387 for (int c=0; c<gcBeans.size(); c++) { 388 GarbageCollectorMXBean currBean = gcBeans.get(c); 389 Boolean isYoung = beanCollectorTypes.get(currBean.getName()); 390 assertNotNull(isYoung, "Unknown MXBean name: " + currBean.getName()); 391 long collectionTime = currBean.getCollectionTime() * 1000; // Convert from millis to micros. 392 summary.add(currBean.getName(), isYoung.booleanValue(), currBean.getCollectionCount(), collectionTime); 393 } 394 return summary; 395 } 396 397 public static CollectionSummary createFromEvents(List<GcBatch> batches) { 398 CollectionSummary summary = new CollectionSummary(); 399 for (GcBatch batch : batches) { 400 RecordedEvent endEvent = batch.getEndEvent(); 401 assertNotNull(endEvent, "No end event in batch with gcId " + batch.getGcId()); 402 String name = batch.getName(); 403 summary.add(name, batch.isYoungCollection(), 1, Events.assertField(endEvent, "sumOfPauses").getValue()); 404 } 405 return summary; 406 } 407 408 public String toString() { 409 StringBuilder collectorNames = new StringBuilder(); 410 for (String s : names) { 411 if (collectorNames.length() > 0) { 412 collectorNames.append(", "); 413 } 414 collectorNames.append(s); 415 } 416 return String.format("CollectionSummary: young.collections=%d, young.time=%d, old.collections=%d, old.time=%d, collectors=(%s)", 417 collectionCountYoung, collectionTimeYoung, collectionCountOld, collectionTimeOld, collectorNames); 418 } 419 } 420 421 public static PrintStream getDefaultErrorLog() { 422 if (defaultErrorLog == null) { 423 try { 424 defaultErrorLog = new PrintStream(new FileOutputStream("error.log", true)); 425 } catch (IOException e) { 426 e.printStackTrace(); 427 defaultErrorLog = System.err; 428 } 429 } 430 return defaultErrorLog; 431 } 432 433 public static void log(Object msg) { 434 log(msg, System.err); 435 log(msg, getDefaultErrorLog()); 436 } 437 438 public static void log(Object msg, PrintStream ps) { 439 ps.println(msg); 440 } 441 442 public static boolean isValidG1HeapRegionType(final String type) { 443 return g1HeapRegionTypes.contains(type); 444 } 445 446 /** 447 * Helper function to align heap size up. 448 * 449 * @param value 450 * @param alignment 451 * @return aligned value 452 */ 453 public static long alignUp(long value, long alignment) { 454 return (value + alignment - 1) & ~(alignment - 1); 455 } 456 457 /** 458 * Helper function to align heap size down. 459 * 460 * @param value 461 * @param alignment 462 * @return aligned value 463 */ 464 public static long alignDown(long value, long alignment) { 465 return value & ~(alignment - 1); 466 } 467 }