1 /*
2  * Copyright (c) 2016, 2020, 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.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24  /*
25  * @test TestPLABPromotion
26  * @bug 8141278 8141141
27  * @summary Test PLAB promotion
28  * @requires vm.gc.G1
29  * @requires !vm.flightRecorder
30  * @library /test/lib /
31  * @modules java.base/jdk.internal.misc
32  * @modules java.management
33  * @build sun.hotspot.WhiteBox
34  * @run driver ClassFileInstaller sun.hotspot.WhiteBox
35  * @run main/timeout=240 gc.g1.plab.TestPLABPromotion
36  */
37 package gc.g1.plab;
38 
39 import java.util.List;
40 import java.util.Arrays;
41 import java.io.PrintStream;
42 
43 import gc.g1.plab.lib.AppPLABPromotion;
44 import gc.g1.plab.lib.LogParser;
45 import gc.g1.plab.lib.PLABUtils;
46 import gc.g1.plab.lib.PlabInfo;
47 
48 import jdk.test.lib.process.OutputAnalyzer;
49 import jdk.test.lib.process.ProcessTools;
50 
51 /**
52  * Test checks PLAB promotion of different size objects.
53  */
54 public class TestPLABPromotion {
55 
56     // GC ID with survivor PLAB statistics
57     private final static long GC_ID_SURVIVOR_STATS = 1l;
58     // GC ID with old PLAB statistics
59     private final static long GC_ID_OLD_STATS = 2l;
60 
61     private final static String PLAB_USED_FIELD_NAME = "used";
62     private final static String PLAB_DIRECT_ALLOCATED_FIELD_NAME = "direct allocated";
63     private final static List<String> FIELDS_TO_EXTRACT = Arrays.asList(PLAB_USED_FIELD_NAME, PLAB_DIRECT_ALLOCATED_FIELD_NAME);
64 
65     private static String output;
66 
67     // Allowable difference for memory consumption (percentage)
68     private final static long MEM_DIFFERENCE_PCT = 5;
69 
70     private static final int PLAB_SIZE_SMALL = 1024;
71     private static final int PLAB_SIZE_MEDIUM = 4096;
72     private static final int PLAB_SIZE_HIGH = 65536;
73     private static final int OBJECT_SIZE_SMALL = 10;
74     private static final int OBJECT_SIZE_MEDIUM = 100;
75     private static final int OBJECT_SIZE_HIGH = 1000;
76     private static final int GC_NUM_SMALL = 1;
77     private static final int GC_NUM_MEDIUM = 3;
78     private static final int GC_NUM_HIGH = 7;
79     private static final int WASTE_PCT_SMALL = 10;
80     private static final int WASTE_PCT_MEDIUM = 20;
81     private static final int WASTE_PCT_HIGH = 30;
82     private static final int YOUNG_SIZE_LOW = 16;
83     private static final int YOUNG_SIZE_HIGH = 64;
84     private static final boolean PLAB_FIXED = true;
85     private static final boolean PLAB_DYNAMIC = false;
86 
87     private final static TestCase[] TEST_CASES = {
88         // Test cases for unreachable object, PLAB size is fixed
89         new TestCase(WASTE_PCT_SMALL, PLAB_SIZE_SMALL, OBJECT_SIZE_MEDIUM, GC_NUM_SMALL, YOUNG_SIZE_LOW, PLAB_FIXED, false, false),
90         new TestCase(WASTE_PCT_HIGH, PLAB_SIZE_MEDIUM, OBJECT_SIZE_SMALL, GC_NUM_HIGH, YOUNG_SIZE_HIGH, PLAB_FIXED, false, false),
91         // Test cases for reachable objects, PLAB size is fixed
92         new TestCase(WASTE_PCT_SMALL, PLAB_SIZE_SMALL, OBJECT_SIZE_SMALL, GC_NUM_HIGH, YOUNG_SIZE_HIGH, PLAB_FIXED, true, true),
93         new TestCase(WASTE_PCT_SMALL, PLAB_SIZE_MEDIUM, OBJECT_SIZE_MEDIUM, GC_NUM_SMALL, YOUNG_SIZE_LOW, PLAB_FIXED, true, true),
94         new TestCase(WASTE_PCT_SMALL, PLAB_SIZE_SMALL, OBJECT_SIZE_HIGH, GC_NUM_MEDIUM, YOUNG_SIZE_LOW, PLAB_FIXED, true, false),
95         new TestCase(WASTE_PCT_MEDIUM, PLAB_SIZE_HIGH, OBJECT_SIZE_SMALL, GC_NUM_HIGH, YOUNG_SIZE_HIGH, PLAB_FIXED, true, true),
96         new TestCase(WASTE_PCT_MEDIUM, PLAB_SIZE_SMALL, OBJECT_SIZE_MEDIUM, GC_NUM_SMALL, YOUNG_SIZE_LOW, PLAB_FIXED, true, true),
97         new TestCase(WASTE_PCT_MEDIUM, PLAB_SIZE_MEDIUM, OBJECT_SIZE_HIGH, GC_NUM_MEDIUM, YOUNG_SIZE_LOW, PLAB_FIXED, true, true),
98         new TestCase(WASTE_PCT_HIGH, PLAB_SIZE_SMALL, OBJECT_SIZE_SMALL, GC_NUM_HIGH, YOUNG_SIZE_HIGH, PLAB_FIXED, true, true),
99         new TestCase(WASTE_PCT_HIGH, PLAB_SIZE_HIGH, OBJECT_SIZE_MEDIUM, GC_NUM_SMALL, YOUNG_SIZE_LOW, PLAB_FIXED, true, true),
100         new TestCase(WASTE_PCT_HIGH, PLAB_SIZE_SMALL, OBJECT_SIZE_HIGH, GC_NUM_MEDIUM, YOUNG_SIZE_HIGH, PLAB_FIXED, true, false),
101         // Test cases for unreachable object, PLAB size is not fixed
102         new TestCase(WASTE_PCT_MEDIUM, PLAB_SIZE_MEDIUM, OBJECT_SIZE_SMALL, GC_NUM_HIGH, YOUNG_SIZE_LOW, PLAB_DYNAMIC, false, false),
103         // Test cases for reachable objects, PLAB size is not fixed
104         new TestCase(WASTE_PCT_SMALL, PLAB_SIZE_HIGH, OBJECT_SIZE_SMALL, GC_NUM_HIGH, YOUNG_SIZE_HIGH, PLAB_DYNAMIC, true, true),
105         new TestCase(WASTE_PCT_MEDIUM, PLAB_SIZE_MEDIUM, OBJECT_SIZE_SMALL, GC_NUM_SMALL, YOUNG_SIZE_LOW, PLAB_DYNAMIC, true, true),
106         new TestCase(WASTE_PCT_SMALL, PLAB_SIZE_MEDIUM, OBJECT_SIZE_HIGH, GC_NUM_HIGH, YOUNG_SIZE_HIGH, PLAB_DYNAMIC, true, false),
107         new TestCase(WASTE_PCT_MEDIUM, PLAB_SIZE_SMALL, OBJECT_SIZE_MEDIUM, GC_NUM_MEDIUM, YOUNG_SIZE_LOW, PLAB_DYNAMIC, true, true),
108         new TestCase(WASTE_PCT_HIGH, PLAB_SIZE_HIGH, OBJECT_SIZE_MEDIUM, GC_NUM_SMALL, YOUNG_SIZE_HIGH, PLAB_DYNAMIC, true, true),
109         new TestCase(WASTE_PCT_HIGH, PLAB_SIZE_HIGH, OBJECT_SIZE_SMALL, GC_NUM_HIGH, YOUNG_SIZE_LOW, PLAB_DYNAMIC, true, true)
110     };
111 
main(String[] args)112     public static void main(String[] args) throws Throwable {
113 
114         for (TestCase testCase : TEST_CASES) {
115             // What we going to check.
116             testCase.print(System.out);
117             List<String> options = PLABUtils.prepareOptions(testCase.toOptions());
118             options.add(AppPLABPromotion.class.getName());
119             OutputAnalyzer out = ProcessTools.executeTestJvm(options);
120             PLABUtils.commonCheck(out);
121             output = out.getOutput();
122             checkResults(testCase);
123         }
124     }
125 
checkResults(TestCase testCase)126     private static void checkResults(TestCase testCase) {
127         long plabAllocatedSurvivor;
128         long directAllocatedSurvivor;
129         long plabAllocatedOld;
130         long directAllocatedOld;
131         long memAllocated = testCase.getMemToFill();
132         LogParser logParser = new LogParser(output);
133 
134         PlabInfo survivorPlabInfo = logParser.getSpecifiedStats(GC_ID_SURVIVOR_STATS, LogParser.ReportType.SURVIVOR_STATS, FIELDS_TO_EXTRACT);
135         PlabInfo oldPlabInfo = logParser.getSpecifiedStats(GC_ID_OLD_STATS, LogParser.ReportType.OLD_STATS, FIELDS_TO_EXTRACT);
136 
137         checkFields(survivorPlabInfo);
138         checkFields(oldPlabInfo);
139 
140         plabAllocatedSurvivor = survivorPlabInfo.get(PLAB_USED_FIELD_NAME);
141         directAllocatedSurvivor = survivorPlabInfo.get(PLAB_DIRECT_ALLOCATED_FIELD_NAME);
142         plabAllocatedOld = oldPlabInfo.get(PLAB_USED_FIELD_NAME);
143         directAllocatedOld = oldPlabInfo.get(PLAB_DIRECT_ALLOCATED_FIELD_NAME);
144 
145         System.out.printf("Survivor PLAB allocated:%17d Direct allocated: %17d Mem consumed:%17d%n", plabAllocatedSurvivor, directAllocatedSurvivor, memAllocated);
146         System.out.printf("Old      PLAB allocated:%17d Direct allocated: %17d Mem consumed:%17d%n", plabAllocatedOld, directAllocatedOld, memAllocated);
147 
148         // Unreachable objects case
149         if (testCase.isDeadObjectCase()) {
150             checkDeadObjectsPromotion(plabAllocatedSurvivor, directAllocatedSurvivor, memAllocated);
151             checkDeadObjectsPromotion(plabAllocatedOld, directAllocatedOld, memAllocated);
152 
153         } else {
154             // Live objects case
155             if (testCase.isPromotedByPLAB()) {
156                 checkLiveObjectsPromotion(plabAllocatedSurvivor, memAllocated, "Expect that Survivor PLAB allocation are similar to all mem consumed");
157                 checkLiveObjectsPromotion(plabAllocatedOld, memAllocated, "Expect that Old PLAB allocation are similar to all mem consumed");
158             } else {
159                 // All big objects should be directly allocated
160                 checkLiveObjectsPromotion(directAllocatedSurvivor, memAllocated, "Expect that Survivor direct allocation are similar to all mem consumed");
161                 checkLiveObjectsPromotion(directAllocatedOld, memAllocated, "Expect that Old direct allocation are similar to all mem consumed");
162             }
163 
164             checkTotalPromotion(plabAllocatedSurvivor, directAllocatedSurvivor, memAllocated, "Expect that Survivor gen total allocation are similar to all mem consumed");
165             checkTotalPromotion(plabAllocatedOld, directAllocatedOld, memAllocated, "Expect that Old gen total allocation are similar to all mem consumed");
166         }
167         System.out.println("Test passed!");
168     }
169 
checkTotalPromotion(long plabAllocatedSurvivor, long directAllocatedSurvivor, long memAllocated, String exceptionMessage)170     private static void checkTotalPromotion(long plabAllocatedSurvivor, long directAllocatedSurvivor, long memAllocated, String exceptionMessage) {
171         // All promoted objects size should be similar to all consumed memory
172         if (!checkDifferenceRatio(plabAllocatedSurvivor + directAllocatedSurvivor, memAllocated)) {
173             System.out.println(output);
174             throw new RuntimeException(exceptionMessage);
175         }
176     }
177 
178     /**
179      * Checks that live objects were promoted as expected.
180      * @param plabAllocated
181      * @param totalMemAllocated
182      * @param exceptionMessage
183      */
checkLiveObjectsPromotion(long plabAllocated, long totalMemAllocated, String exceptionMessage)184     private static void checkLiveObjectsPromotion(long plabAllocated, long totalMemAllocated, String exceptionMessage) {
185         // All live small objects should be promoted using PLAB
186         if (!checkDifferenceRatio(plabAllocated, totalMemAllocated)) {
187             System.out.println(output);
188             throw new RuntimeException(exceptionMessage);
189         }
190     }
191 
192     /**
193      * Checks that dead objects are not promoted.
194      * @param plabPromoted promoted by PLAB
195      * @param directlyPromoted
196      * @param memoryAllocated total memory allocated
197      */
checkDeadObjectsPromotion(long plabPromoted, long directlyPromoted, long memoryAllocated)198     private static void checkDeadObjectsPromotion(long plabPromoted, long directlyPromoted, long memoryAllocated) {
199         // No dead objects should be promoted
200         if (!(checkRatio(plabPromoted, memoryAllocated) && checkRatio(directlyPromoted, memoryAllocated))) {
201             System.out.println(output);
202             throw new RuntimeException("Unreachable objects should not be allocated using PLAB or directly allocated to Survivor/Old");
203         }
204     }
205 
206     /**
207      * Checks that PLAB statistics contains expected fields.
208      * @param info
209      */
checkFields(PlabInfo info)210     private static void checkFields(PlabInfo info) {
211         if (!info.checkFields(FIELDS_TO_EXTRACT)) {
212             System.out.println(output);
213             throw new RuntimeException("PLAB log does not contain expected fields");
214         }
215     }
216 
217     /**
218      * Returns true if checkedValue is less than MEM_DIFFERENCE_PCT percent of controlValue.
219      *
220      * @param checkedValue - checked value
221      * @param controlValue - referent value
222      * @return true if checkedValue is less than MEM_DIFFERENCE_PCT percent of controlValue
223      */
checkRatio(long checkedValue, long controlValue)224     private static boolean checkRatio(long checkedValue, long controlValue) {
225         return (Math.abs(checkedValue) / controlValue) * 100L < MEM_DIFFERENCE_PCT;
226     }
227 
228     /**
229      * Returns true if difference of checkedValue and controlValue is less than
230      * MEM_DIFFERENCE_PCT percent of controlValue.
231      *
232      * @param checkedValue - checked value
233      * @param controlValue - referent value
234      * @return true if difference of checkedValue and controlValue is less than
235      * MEM_DIFFERENCE_PCT percent of controlValue
236      */
checkDifferenceRatio(long checkedValue, long controlValue)237     private static boolean checkDifferenceRatio(long checkedValue, long controlValue) {
238         return (Math.abs(checkedValue - controlValue) / controlValue) * 100L < MEM_DIFFERENCE_PCT;
239     }
240 
241     /**
242      * Description of one test case.
243      */
244     private static class TestCase {
245 
246         private final int wastePct;
247         private final int plabSize;
248         private final int chunkSize;
249         private final int parGCThreads;
250         private final int edenSize;
251         private final boolean plabIsFixed;
252         private final boolean objectsAreReachable;
253         private final boolean promotedByPLAB;
254 
255         /**
256          * @param wastePct
257          * ParallelGCBufferWastePct
258          * @param plabSize
259          * -XX:OldPLABSize and -XX:YoungPLABSize
260          * @param chunkSize
261          * requested object size for memory consumption
262          * @param parGCThreads
263          * -XX:ParallelGCThreads
264          * @param edenSize
265          * NewSize and MaxNewSize
266          * @param plabIsFixed
267          * Use dynamic PLAB or fixed size PLAB
268          * @param objectsAreReachable
269          * true - allocate live objects
270          * false - allocate unreachable objects
271          * @param promotedByPLAB
272          * true - we expect to see PLAB allocation during promotion
273          * false - objects will be directly allocated during promotion
274          */
TestCase(int wastePct, int plabSize, int chunkSize, int parGCThreads, int edenSize, boolean plabIsFixed, boolean objectsAreReachable, boolean promotedByPLAB )275         public TestCase(int wastePct,
276                 int plabSize,
277                 int chunkSize,
278                 int parGCThreads,
279                 int edenSize,
280                 boolean plabIsFixed,
281                 boolean objectsAreReachable,
282                 boolean promotedByPLAB
283         ) {
284             if (wastePct == 0 || plabSize == 0 || chunkSize == 0 || parGCThreads == 0 || edenSize == 0) {
285                 throw new IllegalArgumentException("Parameters should not be 0");
286             }
287             this.wastePct = wastePct;
288             this.plabSize = plabSize;
289             this.chunkSize = chunkSize;
290             this.parGCThreads = parGCThreads;
291             this.edenSize = edenSize;
292             this.plabIsFixed = plabIsFixed;
293             this.objectsAreReachable = objectsAreReachable;
294             this.promotedByPLAB = promotedByPLAB;
295         }
296 
297         /**
298          * Convert current TestCase to List of options.
299          * Assume test will fill half of existed eden.
300          *
301          * @return
302          * List of options
303          */
toOptions()304         public List<String> toOptions() {
305             return Arrays.asList("-XX:ParallelGCThreads=" + parGCThreads,
306                     "-XX:ParallelGCBufferWastePct=" + wastePct,
307                     "-XX:OldPLABSize=" + plabSize,
308                     "-XX:YoungPLABSize=" + plabSize,
309                     "-XX:" + (plabIsFixed ? "-" : "+") + "ResizePLAB",
310                     "-Dchunk.size=" + chunkSize,
311                     "-Dreachable=" + objectsAreReachable,
312                     "-XX:NewSize=" + edenSize + "m",
313                     "-XX:MaxNewSize=" + edenSize + "m",
314                     "-Dmem.to.fill=" + getMemToFill()
315             );
316         }
317 
318         /**
319          * Print details about test case.
320          */
print(PrintStream out)321         public void print(PrintStream out) {
322             boolean expectPLABAllocation = promotedByPLAB && objectsAreReachable;
323             boolean expectDirectAllocation = (!promotedByPLAB) && objectsAreReachable;
324 
325             out.println("Test case details:");
326             out.println("  Young gen size : " + edenSize + "M");
327             out.println("  Predefined PLAB size : " + plabSize);
328             out.println("  Parallel GC buffer waste pct : " + wastePct);
329             out.println("  Chunk size : " + chunkSize);
330             out.println("  Parallel GC threads : " + parGCThreads);
331             out.println("  Objects are created : " + (objectsAreReachable ? "reachable" : "unreachable"));
332             out.println("  PLAB size is fixed: " + (plabIsFixed ? "yes" : "no"));
333             out.println("Test expectations:");
334             out.println("  PLAB allocation : " + (expectPLABAllocation ? "expected" : "unexpected"));
335             out.println("  Direct allocation : " + (expectDirectAllocation ? "expected" : "unexpected"));
336         }
337 
338         /**
339          * @return
340          * true if we expect PLAB allocation
341          * false if no
342          */
isPromotedByPLAB()343         public boolean isPromotedByPLAB() {
344             return promotedByPLAB;
345         }
346 
347         /**
348          * @return
349          * true if it is test case for unreachable objects
350          * false for live objects
351          */
isDeadObjectCase()352         public boolean isDeadObjectCase() {
353             return !objectsAreReachable;
354         }
355 
356         /**
357          * Returns amount of memory to fill
358          *
359          * @return amount of memory
360          */
getMemToFill()361         public long getMemToFill() {
362             return (long) (edenSize) * 1024l * 1024l / 2;
363         }
364     }
365 }
366