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