1 /*
   2  * Copyright (c) 2014, 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.jfr.event.gc.detailed;
  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.assertTrue;
  31 
  32 import java.lang.management.GarbageCollectorMXBean;
  33 import java.lang.management.ManagementFactory;
  34 import java.util.List;
  35 import java.util.concurrent.ThreadLocalRandom;
  36 
  37 import jdk.jfr.Recording;
  38 import jdk.jfr.consumer.RecordedEvent;
  39 import jdk.test.lib.jfr.EventNames;
  40 import jdk.test.lib.jfr.Events;
  41 
  42 /**
  43  * This is a base class for testing Promotion Events
  44  *
  45  * See TestPromotionEventWith* for actual test classes. Tests must set
  46  * -XX:MaxTenuringThreshold=5 -XX:InitialTenuringThreshold=5
  47  *
  48  * @author Staffan Friberg
  49  */
  50 public class PromotionEvent {
  51 
  52     private final static String PROMOTION_IN_NEW_PLAB_NAME = EventNames.PromoteObjectInNewPLAB;
  53     private final static String PROMOTION_OUTSIDE_PLAB_NAME = EventNames.PromoteObjectOutsidePLAB;
  54 
  55     // This value needs to match the command line option set above
  56     private final static int MAX_TENURING_THRESHOLD = 5;
  57 
  58     // Keep track of the collection count just before and after JFR recording
  59     private static int startGCCount = 0;
  60 
  61     // Dummy objects to keep things alive and assure allocation happens
  62     public static Object dummy;
  63     public static Object[] keepAlive = new Object[128];
  64     public static Object[] age = new Object[128];
  65 
  66     public static void test() throws Exception {
  67         GarbageCollectorMXBean ycBean = null;
  68 
  69         List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
  70         for (GarbageCollectorMXBean gcBean : gcBeans) {
  71             if ("PS Scavenge".equals(gcBean.getName())
  72                     || "G1 Young Generation".equals(gcBean.getName())
  73                     || "G1 Young".equals(gcBean.getName())
  74                     || "ParNew".equals(gcBean.getName())) {
  75                 ycBean = gcBean;
  76             }
  77 
  78             if (ycBean != null) {
  79                 break;
  80             }
  81         }
  82 
  83         if (ycBean == null) {
  84             assertNotNull(ycBean, "Test failed since the MXBean for the Young Collector could not be found.");
  85             return; // To remove IDE warning
  86         }
  87 
  88         System.gc(); // Clear nursery before recording
  89 
  90         // Get total GC count before recording
  91         for (GarbageCollectorMXBean gcBean : gcBeans) {
  92             startGCCount += gcBean.getCollectionCount();
  93         }
  94 
  95         Recording recording = new Recording();
  96         recording.enable(PROMOTION_IN_NEW_PLAB_NAME);
  97         recording.enable(PROMOTION_OUTSIDE_PLAB_NAME);
  98         recording.start();
  99 
 100         byte[] largeBytes = new byte[1024 * 10];
 101         byte[] smallBytes = new byte[64];
 102 
 103         // Some large strings to keep alive for tenuring
 104         for (int i = 0; i < keepAlive.length / 2; i++) {
 105             ThreadLocalRandom.current().nextBytes(largeBytes);
 106             keepAlive[i] = new String(largeBytes);
 107         }
 108 
 109         // Some small strings to keep alive for tenuring
 110         for (int i = keepAlive.length / 2; i < keepAlive.length; i++) {
 111             ThreadLocalRandom.current().nextBytes(smallBytes);
 112             keepAlive[i] = new String(smallBytes);
 113         }
 114 
 115         // Allocate temp data to force GCs until we have promoted the live data
 116         for (int gcCount = 0; gcCount < MAX_TENURING_THRESHOLD * 2; gcCount++) {
 117             long currentGCCount = ycBean.getCollectionCount();
 118 
 119             // some large strings to age
 120             for (int i = 0; i < age.length / 2; i++) {
 121                 ThreadLocalRandom.current().nextBytes(largeBytes);
 122                 age[i] = new String(largeBytes);
 123             }
 124 
 125             // Some small strings to age
 126             for (int i = age.length / 2; i < age.length; i++) {
 127                 ThreadLocalRandom.current().nextBytes(smallBytes);
 128                 age[i] = new String(smallBytes);
 129             }
 130 
 131             while (ycBean.getCollectionCount() <= currentGCCount + 3) {
 132                 ThreadLocalRandom.current().nextBytes(smallBytes);
 133                 dummy = new String(smallBytes);
 134             }
 135         }
 136 
 137         recording.stop();
 138 
 139         List<RecordedEvent> events = Events.fromRecording(recording);
 140 
 141         verifyPromotionSampleEvents(events);
 142 
 143         recording.close();
 144     }
 145 
 146     private static void verifyPromotionSampleEvents(List<RecordedEvent> events)
 147             throws Exception {
 148 
 149         boolean objectWasPromotedInNewPLAB = false;
 150         boolean objectPromotedInNewPLABWasAged = false;
 151         boolean objectPromotedInNewPLABWasTenured = false;
 152         boolean objectWasPromotedOutsidePLAB = false;
 153         boolean objectPromotedOutsidePLABWasAged = false;
 154         boolean objectPromotedOutsidePLABWasTenured = false;
 155 
 156         Events.hasEvents(events);
 157 
 158         for (RecordedEvent event : events) {
 159             // Read all common fields
 160             Events.assertField(event, "gcId").atLeast(startGCCount).getValue();
 161             String className = (event.getEventType()).getName().toString();
 162             Events.assertField(event, "tenuringAge").atLeast(0).atMost(MAX_TENURING_THRESHOLD).getValue();
 163             Boolean tenured = Events.assertField(event, "tenured").getValue();
 164             Long objectSize = Events.assertField(event, "objectSize").above(0L).getValue();
 165 
 166             // Verify Class Name
 167             assertNotNull(className, "Class name is null. Event: " + event);
 168             assertNotEquals(className.length(), 0, "Class name is of zero length. Event: " + event);
 169 
 170             // Verify PLAB size and direct allocation
 171             if (PROMOTION_IN_NEW_PLAB_NAME.equals(event.getEventType().getName())) {
 172                 // Read event specific fields
 173                 Long plabSize = Events.assertField(event, "plabSize").above(0L).getValue();
 174                 assertTrue(plabSize >= objectSize, "PLAB size is smaller than object size. Event: " + event);
 175                 objectWasPromotedInNewPLAB = true;
 176                 // Verify tenured is hard to do as objects might be tenured earlier than the max threshold
 177                 // but at least verify that we got the field set at least once during the test
 178                 if (tenured) {
 179                     objectPromotedInNewPLABWasTenured = true;
 180                 } else {
 181                     objectPromotedInNewPLABWasAged = true;
 182                 }
 183             } else if (PROMOTION_OUTSIDE_PLAB_NAME.equals(event.getEventType().getName())) {
 184                 objectWasPromotedOutsidePLAB = true;
 185                 // Verify tenured is hard to do as objects might be tenured earlier than the max threshold
 186                 // but at least verify that we got the field set at least once during the test
 187                 if (tenured) {
 188                     objectPromotedOutsidePLABWasTenured = true;
 189                 } else {
 190                     objectPromotedOutsidePLABWasAged = true;
 191                 }
 192             } else {
 193                 assertEquals(event.getEventType().getName(), "Unreachable...", "Got wrong type of event " + event);
 194             }
 195 
 196         }
 197 
 198         // Verify that at least one event of these types occured during test
 199         assertTrue(objectWasPromotedInNewPLAB, "No object in new plab was promoted in test");
 200         assertTrue(objectPromotedInNewPLABWasAged, "No object in new plab was aged in test");
 201         assertTrue(objectPromotedInNewPLABWasTenured, "No object in new plab was tenured in test");
 202         assertTrue(objectWasPromotedOutsidePLAB, "No object outside plab was promoted in test");
 203         assertTrue(objectPromotedOutsidePLABWasAged, "No object outside plab was aged in test");
 204         assertTrue(objectPromotedOutsidePLABWasTenured, "No object outside plab was tenured in test");
 205     }
 206 }