JVMOptionsUtils.java revision 10273:695127299575
1/*
2 * Copyright (c) 2015, 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
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.OutputAnalyzer;
41import jdk.test.lib.Platform;
42import jdk.test.lib.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 "SharedReadWriteSize":
236            case "SharedReadOnlySize":
237            case "SharedMiscDataSize":
238            case "SharedMiscCodeSize":
239            case "SharedBaseAddress":
240            case "SharedSymbolTableBucketSize":
241                option.addPrepend("-XX:+UnlockDiagnosticVMOptions");
242                option.addPrepend("-XX:SharedArchiveFile=TestOptionsWithRanges.jsa");
243                option.addPrepend("-Xshare:dump");
244                break;
245            default:
246                /* Do nothing */
247                break;
248        }
249    }
250
251    /**
252     * Parse JVM Options. Get input from "inputReader". Parse using
253     * "-XX:+PrintFlagsRanges" output format.
254     *
255     * @param inputReader input data for parsing
256     * @param withRanges true if needed options with defined ranges inside JVM
257     * @param acceptOrigin predicate for option origins. Origins can be
258     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
259     * to true.
260     * @return map from option name to the JVMOption object
261     * @throws IOException if an error occurred while reading the data
262     */
263    private static Map<String, JVMOption> getJVMOptions(Reader inputReader,
264            boolean withRanges, Predicate<String> acceptOrigin) throws IOException {
265        BufferedReader reader = new BufferedReader(inputReader);
266        String type;
267        String line;
268        String token;
269        String name;
270        StringTokenizer st;
271        JVMOption option;
272        Map<String, JVMOption> allOptions = new LinkedHashMap<>();
273
274        // Skip first line
275        line = reader.readLine();
276
277        while ((line = reader.readLine()) != null) {
278            /*
279             * Parse option from following line:
280             * <type> <name> [ <min, optional> ... <max, optional> ] {<origin>}
281             */
282            st = new StringTokenizer(line);
283
284            type = st.nextToken();
285
286            name = st.nextToken();
287
288            option = JVMOption.createVMOption(type, name);
289
290            /* Skip '[' */
291            token = st.nextToken();
292
293            /* Read min range or "..." if range is absent */
294            token = st.nextToken();
295
296            if (token.equals("...") == false) {
297                if (!withRanges) {
298                    /*
299                     * Option have range, but asked for options without
300                     * ranges => skip it
301                     */
302                    continue;
303                }
304
305                /* Mark this option as option which range is defined in VM */
306                option.optionWithRange();
307
308                option.setMin(token);
309
310                /* Read "..." and skip it */
311                token = st.nextToken();
312
313                /* Get max value */
314                token = st.nextToken();
315                option.setMax(token);
316            } else if (withRanges) {
317                /*
318                 * Option not have range, but asked for options with
319                 * ranges => skip it
320                 */
321                continue;
322            }
323
324            /* Skip ']' */
325            token = st.nextToken();
326
327            /* Read origin of the option */
328            token = st.nextToken();
329
330            while (st.hasMoreTokens()) {
331                token += st.nextToken();
332            };
333            token = token.substring(1, token.indexOf("}"));
334
335            if (acceptOrigin.test(token)) {
336                addNameDependency(option);
337
338                allOptions.put(name, option);
339            }
340        }
341
342        return allOptions;
343    }
344
345    static void failedMessage(String optionName, String value, boolean valid, String message) {
346        String temp;
347
348        if (valid) {
349            temp = "valid";
350        } else {
351            temp = "invalid";
352        }
353
354        failedMessage(String.format("Error processing option %s with %s value '%s'! %s",
355                optionName, temp, value, message));
356    }
357
358    static void failedMessage(String message) {
359        System.err.println("TEST FAILED: " + message);
360        finalFailedMessage.append(String.format("(%s)%n", message));
361    }
362
363    static void printOutputContent(OutputAnalyzer output) {
364        System.err.println(String.format("stdout content[%s]", output.getStdout()));
365        System.err.println(String.format("stderr content[%s]%n", output.getStderr()));
366    }
367
368    /**
369     * Return string with accumulated failure messages
370     *
371     * @return string with accumulated failure messages
372     */
373    public static String getMessageWithFailures() {
374        return finalFailedMessage.toString();
375    }
376
377    /**
378     * Run command line tests for options passed in the list
379     *
380     * @param options list of options to test
381     * @return number of failed tests
382     * @throws Exception if java process can not be started
383     */
384    public static int runCommandLineTests(List<? extends JVMOption> options) throws Exception {
385        int failed = 0;
386
387        for (JVMOption option : options) {
388            failed += option.testCommandLine();
389        }
390
391        return failed;
392    }
393
394    /**
395     * Test passed options using DynamicVMOption isValidValue and isInvalidValue
396     * methods. Only tests writeable options.
397     *
398     * @param options list of options to test
399     * @return number of failed tests
400     */
401    public static int runDynamicTests(List<? extends JVMOption> options) {
402        int failed = 0;
403
404        for (JVMOption option : options) {
405            failed += option.testDynamic();
406        }
407
408        return failed;
409    }
410
411    /**
412     * Test passed options using Jcmd. Only tests writeable options.
413     *
414     * @param options list of options to test
415     * @return number of failed tests
416     */
417    public static int runJcmdTests(List<? extends JVMOption> options) {
418        int failed = 0;
419
420        for (JVMOption option : options) {
421            failed += option.testJcmd();
422        }
423
424        return failed;
425    }
426
427    /**
428     * Test passed option using attach method. Only tests writeable options.
429     *
430     * @param options list of options to test
431     * @return number of failed tests
432     * @throws Exception if an error occurred while attaching to the target JVM
433     */
434    public static int runAttachTests(List<? extends JVMOption> options) throws Exception {
435        int failed = 0;
436
437        for (JVMOption option : options) {
438            failed += option.testAttach();
439        }
440
441        return failed;
442    }
443
444    /**
445     * Get JVM options as map. Can return options with defined ranges or options
446     * without range depending on "withRanges" argument. "acceptOrigin"
447     * predicate can be used to filter option origin.
448     *
449     * @param withRanges true if needed options with defined ranges inside JVM
450     * @param acceptOrigin predicate for option origins. Origins can be
451     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
452     * to true.
453     * @param additionalArgs additional arguments to the Java process which ran
454     * with "-XX:+PrintFlagsRanges"
455     * @return map from option name to the JVMOption object
456     * @throws Exception if a new process can not be created or an error
457     * occurred while reading the data
458     */
459    public static Map<String, JVMOption> getOptionsAsMap(boolean withRanges, Predicate<String> acceptOrigin,
460            String... additionalArgs) throws Exception {
461        Map<String, JVMOption> result;
462        Process p;
463        List<String> runJava = new ArrayList<>();
464
465        if (additionalArgs.length > 0) {
466            runJava.addAll(Arrays.asList(additionalArgs));
467        }
468
469        if (VMType != null) {
470            runJava.add(VMType);
471        }
472
473        if (GCType != null) {
474            runJava.add(GCType);
475        }
476        runJava.add(PRINT_FLAGS_RANGES);
477        runJava.add("-version");
478
479        p = ProcessTools.createJavaProcessBuilder(runJava.toArray(new String[0])).start();
480
481        result = getJVMOptions(new InputStreamReader(p.getInputStream()), withRanges, acceptOrigin);
482
483        p.waitFor();
484
485        return result;
486    }
487
488    /**
489     * Get JVM options as list. Can return options with defined ranges or
490     * options without range depending on "withRanges" argument. "acceptOrigin"
491     * predicate can be used to filter option origin.
492     *
493     * @param withRanges true if needed options with defined ranges inside JVM
494     * @param acceptOrigin predicate for option origins. Origins can be
495     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
496     * to true.
497     * @param additionalArgs additional arguments to the Java process which ran
498     * with "-XX:+PrintFlagsRanges"
499     * @return List of options
500     * @throws Exception if a new process can not be created or an error
501     * occurred while reading the data
502     */
503    public static List<JVMOption> getOptions(boolean withRanges, Predicate<String> acceptOrigin,
504            String... additionalArgs) throws Exception {
505        return new ArrayList<>(getOptionsAsMap(withRanges, acceptOrigin, additionalArgs).values());
506    }
507
508    /**
509     * Get JVM options with ranges as list. "acceptOrigin" predicate can be used
510     * to filter option origin.
511     *
512     * @param acceptOrigin predicate for option origins. Origins can be
513     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
514     * to true.
515     * @param additionalArgs additional arguments to the Java process which ran
516     * with "-XX:+PrintFlagsRanges"
517     * @return List of options
518     * @throws Exception if a new process can not be created or an error
519     * occurred while reading the data
520     */
521    public static List<JVMOption> getOptionsWithRange(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception {
522        return getOptions(true, acceptOrigin, additionalArgs);
523    }
524
525    /**
526     * Get JVM options with ranges as list.
527     *
528     * @param additionalArgs additional arguments to the Java process which ran
529     * with "-XX:+PrintFlagsRanges"
530     * @return list of options
531     * @throws Exception if a new process can not be created or an error
532     * occurred while reading the data
533     */
534    public static List<JVMOption> getOptionsWithRange(String... additionalArgs) throws Exception {
535        return getOptionsWithRange(origin -> true, additionalArgs);
536    }
537
538    /**
539     * Get JVM options with range as map. "acceptOrigin" predicate can be used
540     * to filter option origin.
541     *
542     * @param acceptOrigin predicate for option origins. Origins can be
543     * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates
544     * to true.
545     * @param additionalArgs additional arguments to the Java process which ran
546     * with "-XX:+PrintFlagsRanges"
547     * @return Map from option name to the JVMOption object
548     * @throws Exception if a new process can not be created or an error
549     * occurred while reading the data
550     */
551    public static Map<String, JVMOption> getOptionsWithRangeAsMap(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception {
552        return getOptionsAsMap(true, acceptOrigin, additionalArgs);
553    }
554
555    /**
556     * Get JVM options with range as map
557     *
558     * @param additionalArgs additional arguments to the Java process which ran
559     * with "-XX:+PrintFlagsRanges"
560     * @return map from option name to the JVMOption object
561     * @throws Exception if a new process can not be created or an error
562     * occurred while reading the data
563     */
564    public static Map<String, JVMOption> getOptionsWithRangeAsMap(String... additionalArgs) throws Exception {
565        return getOptionsWithRangeAsMap(origin -> true, additionalArgs);
566    }
567}
568