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