JVMOptionsUtils.java revision 13370:731370f39fcd
1/*
2 * Copyright (c) 2015, 2017, 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
24package optionsvalidation;
25
26import java.io.BufferedReader;
27import java.io.IOException;
28import java.io.InputStreamReader;
29import java.io.Reader;
30import java.lang.management.GarbageCollectorMXBean;
31import java.lang.management.ManagementFactory;
32import java.math.BigDecimal;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.List;
36import java.util.LinkedHashMap;
37import java.util.Map;
38import java.util.StringTokenizer;
39import java.util.function.Predicate;
40import jdk.test.lib.process.OutputAnalyzer;
41import jdk.test.lib.Platform;
42import jdk.test.lib.process.ProcessTools;
43
44public class JVMOptionsUtils {
45
46    /* Java option which print options with ranges */
47    private static final String PRINT_FLAGS_RANGES = "-XX:+PrintFlagsRanges";
48
49    /* StringBuilder to accumulate failed message */
50    private static final StringBuilder finalFailedMessage = new StringBuilder();
51
52    /* Used to start the JVM with the same type as current */
53    static String VMType;
54
55    /* Used to start the JVM with the same GC type as current */
56    static String GCType;
57
58    private static Map<String, JVMOption> optionsAsMap;
59
60    static {
61        if (Platform.isServer()) {
62            VMType = "-server";
63        } else if (Platform.isClient()) {
64            VMType = "-client";
65        } else if (Platform.isMinimal()) {
66            VMType = "-minimal";
67        } else if (Platform.isGraal()) {
68            VMType = "-graal";
69        } else {
70            VMType = null;
71        }
72
73        List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans();
74
75        GCType = null;
76
77        for (GarbageCollectorMXBean gcMxBean : gcMxBeans) {
78            switch (gcMxBean.getName()) {
79                case "ConcurrentMarkSweep":
80                    GCType = "-XX:+UseConcMarkSweepGC";
81                    break;
82                case "MarkSweepCompact":
83                    GCType = "-XX:+UseSerialGC";
84                    break;
85                case "PS Scavenge":
86                    GCType = "-XX:+UseParallelGC";
87                    break;
88                case "G1 Old Generation":
89                    GCType = "-XX:+UseG1GC";
90                    break;
91            }
92        }
93    }
94
95    public static boolean fitsRange(String optionName, BigDecimal number) throws Exception {
96        JVMOption option;
97        String minRangeString = null;
98        String maxRangeString = null;
99        boolean fits = true;
100
101        if (optionsAsMap == null) {
102            optionsAsMap = getOptionsWithRangeAsMap();
103        }
104
105        option = optionsAsMap.get(optionName);
106        if (option != null) {
107            minRangeString = option.getMin();
108            if (minRangeString != null) {
109                fits = (number.compareTo(new BigDecimal(minRangeString)) >= 0);
110            }
111            maxRangeString = option.getMax();
112            if (maxRangeString != null) {
113                fits &= (number.compareTo(new BigDecimal(maxRangeString)) <= 0);
114            }
115        }
116
117        return fits;
118    }
119
120    public static boolean fitsRange(String optionName, String number) throws Exception {
121        String lowerCase = number.toLowerCase();
122        String multiplier = "1";
123        if (lowerCase.endsWith("k")) {
124            multiplier = "1024";
125            lowerCase = lowerCase.substring(0, lowerCase.length()-1);
126        } else if (lowerCase.endsWith("m")) {
127            multiplier = "1048576"; //1024*1024
128            lowerCase = lowerCase.substring(0, lowerCase.length()-1);
129        } else if (lowerCase.endsWith("g")) {
130            multiplier = "1073741824"; //1024*1024*1024
131            lowerCase = lowerCase.substring(0, lowerCase.length()-1);
132        } else if (lowerCase.endsWith("t")) {
133            multiplier = "1099511627776"; //1024*1024*1024*1024
134            lowerCase = lowerCase.substring(0, lowerCase.length()-1);
135        }
136        BigDecimal valueBig = new BigDecimal(lowerCase);
137        BigDecimal multiplierBig = new BigDecimal(multiplier);
138        return fitsRange(optionName, valueBig.multiply(multiplierBig));
139    }
140
141    public static String getMinOptionRange(String optionName) throws Exception {
142        JVMOption option;
143        String minRange = null;
144
145        if (optionsAsMap == null) {
146            optionsAsMap = getOptionsWithRangeAsMap();
147        }
148
149        option = optionsAsMap.get(optionName);
150        if (option != null) {
151            minRange = option.getMin();
152        }
153
154        return minRange;
155    }
156
157    public static String getMaxOptionRange(String optionName) throws Exception {
158        JVMOption option;
159        String maxRange = null;
160
161        if (optionsAsMap == null) {
162            optionsAsMap = getOptionsWithRangeAsMap();
163        }
164
165        option = optionsAsMap.get(optionName);
166        if (option != null) {
167            maxRange = option.getMax();
168        }
169
170        return maxRange;
171    }
172
173    /**
174     * Add dependency for option depending on it's name. E.g. enable G1 GC for
175     * G1 options or add prepend options to not hit constraints.
176     *
177     * @param option option
178     */
179    private static void addNameDependency(JVMOption option) {
180        String name = option.getName();
181
182        if (name.startsWith("G1")) {
183            option.addPrepend("-XX:+UseG1GC");
184        }
185
186        if (name.startsWith("CMS")) {
187            option.addPrepend("-XX:+UseConcMarkSweepGC");
188        }
189
190        if (name.startsWith("NUMA")) {
191            option.addPrepend("-XX:+UseNUMA");
192        }
193
194        switch (name) {
195            case "MinHeapFreeRatio":
196                option.addPrepend("-XX:MaxHeapFreeRatio=100");
197                break;
198            case "MaxHeapFreeRatio":
199                option.addPrepend("-XX:MinHeapFreeRatio=0");
200                break;
201            case "MinMetaspaceFreeRatio":
202                option.addPrepend("-XX:MaxMetaspaceFreeRatio=100");
203                break;
204            case "MaxMetaspaceFreeRatio":
205                option.addPrepend("-XX:MinMetaspaceFreeRatio=0");
206                break;
207            case "CMSOldPLABMin":
208                option.addPrepend("-XX:CMSOldPLABMax=" + option.getMax());
209                break;
210            case "CMSOldPLABMax":
211                option.addPrepend("-XX:CMSOldPLABMin=" + option.getMin());
212                break;
213            case "CMSPrecleanNumerator":
214                option.addPrepend("-XX:CMSPrecleanDenominator=" + option.getMax());
215                break;
216            case "CMSPrecleanDenominator":
217                option.addPrepend("-XX:CMSPrecleanNumerator=" + ((new Integer(option.getMin())) - 1));
218                break;
219            case "InitialTenuringThreshold":
220                option.addPrepend("-XX:MaxTenuringThreshold=" + option.getMax());
221                break;
222            case "NUMAInterleaveGranularity":
223                option.addPrepend("-XX:+UseNUMAInterleaving");
224                break;
225            case "CPUForCMSThread":
226                option.addPrepend("-XX:+BindCMSThreadToCPU");
227                break;
228            case "VerifyGCStartAt":
229                option.addPrepend("-XX:+VerifyBeforeGC");
230                option.addPrepend("-XX:+VerifyAfterGC");
231                break;
232            case "NewSizeThreadIncrease":
233                option.addPrepend("-XX:+UseSerialGC");
234                break;
235            case "SharedBaseAddress":
236            case "SharedSymbolTableBucketSize":
237                option.addPrepend("-XX:+UnlockDiagnosticVMOptions");
238                option.addPrepend("-XX:SharedArchiveFile=TestOptionsWithRanges.jsa");
239                option.addPrepend("-Xshare:dump");
240                break;
241            case "TLABWasteIncrement":
242                option.addPrepend("-XX:+UseParallelGC");
243                break;
244            default:
245                /* Do nothing */
246                break;
247        }
248    }
249
250    /**
251     * Parse JVM Options. Get input from "inputReader". Parse using
252     * "-XX:+PrintFlagsRanges" output format.
253     *
254     * @param inputReader input data for parsing
255     * @param withRanges true if needed options with defined ranges inside JVM
256     * @param acceptOrigin predicate for option origins. Origins can be
257     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
258     * to true.
259     * @return map from option name to the JVMOption object
260     * @throws IOException if an error occurred while reading the data
261     */
262    private static Map<String, JVMOption> getJVMOptions(Reader inputReader,
263            boolean withRanges, Predicate<String> acceptOrigin) throws IOException {
264        BufferedReader reader = new BufferedReader(inputReader);
265        String type;
266        String line;
267        String token;
268        String name;
269        StringTokenizer st;
270        JVMOption option;
271        Map<String, JVMOption> allOptions = new LinkedHashMap<>();
272
273        // Skip first line
274        line = reader.readLine();
275
276        while ((line = reader.readLine()) != null) {
277            /*
278             * Parse option from following line:
279             * <type> <name> [ <min, optional> ... <max, optional> ] {<origin>}
280             */
281            st = new StringTokenizer(line);
282
283            type = st.nextToken();
284
285            name = st.nextToken();
286
287            option = JVMOption.createVMOption(type, name);
288
289            /* Skip '[' */
290            token = st.nextToken();
291
292            /* Read min range or "..." if range is absent */
293            token = st.nextToken();
294
295            if (token.equals("...") == false) {
296                if (!withRanges) {
297                    /*
298                     * Option have range, but asked for options without
299                     * ranges => skip it
300                     */
301                    continue;
302                }
303
304                /* Mark this option as option which range is defined in VM */
305                option.optionWithRange();
306
307                option.setMin(token);
308
309                /* Read "..." and skip it */
310                token = st.nextToken();
311
312                /* Get max value */
313                token = st.nextToken();
314                option.setMax(token);
315            } else if (withRanges) {
316                /*
317                 * Option not have range, but asked for options with
318                 * ranges => skip it
319                 */
320                continue;
321            }
322
323            /* Skip ']' */
324            token = st.nextToken();
325
326            /* Read origin of the option */
327            token = st.nextToken();
328
329            while (st.hasMoreTokens()) {
330                token += st.nextToken();
331            };
332            token = token.substring(1, token.indexOf("}"));
333
334            if (acceptOrigin.test(token)) {
335                addNameDependency(option);
336
337                allOptions.put(name, option);
338            }
339        }
340
341        return allOptions;
342    }
343
344    static void failedMessage(String optionName, String value, boolean valid, String message) {
345        String temp;
346
347        if (valid) {
348            temp = "valid";
349        } else {
350            temp = "invalid";
351        }
352
353        failedMessage(String.format("Error processing option %s with %s value '%s'! %s",
354                optionName, temp, value, message));
355    }
356
357    static void failedMessage(String message) {
358        System.err.println("TEST FAILED: " + message);
359        finalFailedMessage.append(String.format("(%s)%n", message));
360    }
361
362    static void printOutputContent(OutputAnalyzer output) {
363        System.err.println(String.format("stdout content[%s]", output.getStdout()));
364        System.err.println(String.format("stderr content[%s]%n", output.getStderr()));
365    }
366
367    /**
368     * Return string with accumulated failure messages
369     *
370     * @return string with accumulated failure messages
371     */
372    public static String getMessageWithFailures() {
373        return finalFailedMessage.toString();
374    }
375
376    /**
377     * Run command line tests for options passed in the list
378     *
379     * @param options list of options to test
380     * @return number of failed tests
381     * @throws Exception if java process can not be started
382     */
383    public static int runCommandLineTests(List<? extends JVMOption> options) throws Exception {
384        int failed = 0;
385
386        for (JVMOption option : options) {
387            failed += option.testCommandLine();
388        }
389
390        return failed;
391    }
392
393    /**
394     * Test passed options using DynamicVMOption isValidValue and isInvalidValue
395     * methods. Only tests writeable options.
396     *
397     * @param options list of options to test
398     * @return number of failed tests
399     */
400    public static int runDynamicTests(List<? extends JVMOption> options) {
401        int failed = 0;
402
403        for (JVMOption option : options) {
404            failed += option.testDynamic();
405        }
406
407        return failed;
408    }
409
410    /**
411     * Test passed options using Jcmd. Only tests writeable options.
412     *
413     * @param options list of options to test
414     * @return number of failed tests
415     */
416    public static int runJcmdTests(List<? extends JVMOption> options) {
417        int failed = 0;
418
419        for (JVMOption option : options) {
420            failed += option.testJcmd();
421        }
422
423        return failed;
424    }
425
426    /**
427     * Test passed option using attach method. Only tests writeable options.
428     *
429     * @param options list of options to test
430     * @return number of failed tests
431     * @throws Exception if an error occurred while attaching to the target JVM
432     */
433    public static int runAttachTests(List<? extends JVMOption> options) throws Exception {
434        int failed = 0;
435
436        for (JVMOption option : options) {
437            failed += option.testAttach();
438        }
439
440        return failed;
441    }
442
443    /**
444     * Get JVM options as map. Can return options with defined ranges or options
445     * without range depending on "withRanges" argument. "acceptOrigin"
446     * predicate can be used to filter option origin.
447     *
448     * @param withRanges true if needed options with defined ranges inside JVM
449     * @param acceptOrigin predicate for option origins. Origins can be
450     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
451     * to true.
452     * @param additionalArgs additional arguments to the Java process which ran
453     * with "-XX:+PrintFlagsRanges"
454     * @return map from option name to the JVMOption object
455     * @throws Exception if a new process can not be created or an error
456     * occurred while reading the data
457     */
458    public static Map<String, JVMOption> getOptionsAsMap(boolean withRanges, Predicate<String> acceptOrigin,
459            String... additionalArgs) throws Exception {
460        Map<String, JVMOption> result;
461        Process p;
462        List<String> runJava = new ArrayList<>();
463
464        if (additionalArgs.length > 0) {
465            runJava.addAll(Arrays.asList(additionalArgs));
466        }
467
468        if (VMType != null) {
469            runJava.add(VMType);
470        }
471
472        if (GCType != null) {
473            runJava.add(GCType);
474        }
475        runJava.add(PRINT_FLAGS_RANGES);
476        runJava.add("-version");
477
478        p = ProcessTools.createJavaProcessBuilder(runJava.toArray(new String[0])).start();
479
480        result = getJVMOptions(new InputStreamReader(p.getInputStream()), withRanges, acceptOrigin);
481
482        p.waitFor();
483
484        return result;
485    }
486
487    /**
488     * Get JVM options as list. Can return options with defined ranges or
489     * options without range depending on "withRanges" argument. "acceptOrigin"
490     * predicate can be used to filter option origin.
491     *
492     * @param withRanges true if needed options with defined ranges inside JVM
493     * @param acceptOrigin predicate for option origins. Origins can be
494     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
495     * to true.
496     * @param additionalArgs additional arguments to the Java process which ran
497     * with "-XX:+PrintFlagsRanges"
498     * @return List of options
499     * @throws Exception if a new process can not be created or an error
500     * occurred while reading the data
501     */
502    public static List<JVMOption> getOptions(boolean withRanges, Predicate<String> acceptOrigin,
503            String... additionalArgs) throws Exception {
504        return new ArrayList<>(getOptionsAsMap(withRanges, acceptOrigin, additionalArgs).values());
505    }
506
507    /**
508     * Get JVM options with ranges as list. "acceptOrigin" predicate can be used
509     * to filter option origin.
510     *
511     * @param acceptOrigin predicate for option origins. Origins can be
512     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
513     * to true.
514     * @param additionalArgs additional arguments to the Java process which ran
515     * with "-XX:+PrintFlagsRanges"
516     * @return List of options
517     * @throws Exception if a new process can not be created or an error
518     * occurred while reading the data
519     */
520    public static List<JVMOption> getOptionsWithRange(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception {
521        return getOptions(true, acceptOrigin, additionalArgs);
522    }
523
524    /**
525     * Get JVM options with ranges as list.
526     *
527     * @param additionalArgs additional arguments to the Java process which ran
528     * with "-XX:+PrintFlagsRanges"
529     * @return list of options
530     * @throws Exception if a new process can not be created or an error
531     * occurred while reading the data
532     */
533    public static List<JVMOption> getOptionsWithRange(String... additionalArgs) throws Exception {
534        return getOptionsWithRange(origin -> true, additionalArgs);
535    }
536
537    /**
538     * Get JVM options with range as map. "acceptOrigin" predicate can be used
539     * to filter option origin.
540     *
541     * @param acceptOrigin predicate for option origins. Origins can be
542     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
543     * to true.
544     * @param additionalArgs additional arguments to the Java process which ran
545     * with "-XX:+PrintFlagsRanges"
546     * @return Map from option name to the JVMOption object
547     * @throws Exception if a new process can not be created or an error
548     * occurred while reading the data
549     */
550    public static Map<String, JVMOption> getOptionsWithRangeAsMap(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception {
551        return getOptionsAsMap(true, acceptOrigin, additionalArgs);
552    }
553
554    /**
555     * Get JVM options with range as map
556     *
557     * @param additionalArgs additional arguments to the Java process which ran
558     * with "-XX:+PrintFlagsRanges"
559     * @return map from option name to the JVMOption object
560     * @throws Exception if a new process can not be created or an error
561     * occurred while reading the data
562     */
563    public static Map<String, JVMOption> getOptionsWithRangeAsMap(String... additionalArgs) throws Exception {
564        return getOptionsWithRangeAsMap(origin -> true, additionalArgs);
565    }
566}
567