1 /*
   2  * Copyright (c) 2015, 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 package optionsvalidation;
  25 
  26 import java.io.BufferedReader;
  27 import java.io.IOException;
  28 import java.io.InputStreamReader;
  29 import java.io.Reader;
  30 import java.util.ArrayList;
  31 import java.util.Arrays;
  32 import java.util.List;
  33 import java.util.HashMap;
  34 import java.util.Map;
  35 import java.util.function.Predicate;
  36 import jdk.test.lib.OutputAnalyzer;
  37 import jdk.test.lib.ProcessTools;
  38 
  39 public class JVMOptionsUtils {
  40 
  41     /* Java option which print options with ranges */
  42     private static final String PRINT_FLAGS_RANGES = "-XX:+PrintFlagsRanges";
  43 
  44     /* StringBuilder to accumulate failed message */
  45     private static final StringBuilder finalFailedMessage = new StringBuilder();
  46 
  47     /**
  48      * Add dependency for option depending on it's name. E.g. enable G1 GC for
  49      * G1 options or add prepend options to not hit constraints.
  50      *
  51      * @param option Option
  52      */
  53     static private void addNameDependency(JVMOption option) {
  54         String name = option.getName();
  55 
  56         if (name.startsWith("G1")) {
  57             option.addPrepend("-XX:+UseG1GC");
  58         }
  59 
  60         if (name.startsWith("CMS")) {
  61             option.addPrepend("-XX:+UseConcMarkSweepGC");
  62         }
  63 
  64         switch (name) {
  65             case "MinHeapFreeRatio":
  66                 option.addPrepend("-XX:MaxHeapFreeRatio=100");
  67                 break;
  68             case "MaxHeapFreeRatio":
  69                 option.addPrepend("-XX:MinHeapFreeRatio=0");
  70                 break;
  71             case "MinMetaspaceFreeRatio":
  72                 option.addPrepend("-XX:MaxMetaspaceFreeRatio=100");
  73                 break;
  74             case "MaxMetaspaceFreeRatio":
  75                 option.addPrepend("-XX:MinMetaspaceFreeRatio=0");
  76                 break;
  77             case "CMSOldPLABMin":
  78                 option.addPrepend("-XX:CMSOldPLABMax=" + option.getMax());
  79                 break;
  80             case "CMSOldPLABMax":
  81                 option.addPrepend("-XX:CMSOldPLABMin=" + option.getMin());
  82                 break;
  83             case "CMSPrecleanNumerator":
  84                 option.addPrepend("-XX:CMSPrecleanDenominator=" + option.getMax());
  85                 break;
  86             case "CMSPrecleanDenominator":
  87                 option.addPrepend("-XX:CMSPrecleanNumerator=" + ((new Integer(option.getMin())) - 1));
  88                 break;
  89             case "InitialTenuringThreshold":
  90                 option.addPrepend("-XX:MaxTenuringThreshold=" + option.getMax());
  91                 break;
  92             default:
  93                 /* Do nothing */
  94                 break;
  95         }
  96 
  97     }
  98 
  99     /**
 100      * Add dependency for option depending on it's type. E.g. ran java in
 101      * compilation mode for compiler options.
 102      *
 103      * @param option Option
 104      * @param type Type of the option
 105      */
 106     static private void addTypeDependency(JVMOption option, String type) {
 107         if (type.contains("C1") || type.contains("C2")) {
 108             /* Run in compiler mode for compiler flags */
 109             option.addPrepend("-Xcomp");
 110         }
 111     }
 112 
 113     /**
 114      * Parse JVM Options. Get input from "inputReader". Parse using 
 115      * "-XX:+PrintFlagsRanges" output format.
 116      * 
 117      * @param inputReader Input data for parsing
 118      * @param withRanges true if needed options with defined ranges
 119      * @param acceptOrigin predicate for option origins. Origins can be
 120      * "product", "diagnostic" etc. Accept option only if acceptOrigin return
 121      * true.
 122      * @return Map from option name to the JVMOption object
 123      * @throws IOException Error occurred while reading the data
 124      */
 125     static private Map<String, JVMOption> getJVMOptions(Reader inputReader,
 126             boolean withRanges, Predicate<String> acceptOrigin) throws IOException {
 127         BufferedReader reader = new BufferedReader(inputReader);
 128         String type;
 129         String line;
 130         String token;
 131         String name;
 132         JVMOption option;
 133         Map<String, JVMOption> allOptions = new HashMap<>();
 134 
 135         // Skip first line
 136         line = reader.readLine();
 137 
 138         while ((line = reader.readLine()) != null) {
 139 
 140             type = line.substring(0, 9).trim();
 141 
 142             name = line.substring(10, 61).trim();
 143 
 144             option = JVMOption.createVMOption(type, name);
 145 
 146             /* Get min value */
 147             token = line.substring(63, 89).trim();
 148             if (!token.isEmpty()) {
 149                 if (!withRanges) {
 150                     /*
 151                      * Option have range, but asked for options without
 152                      * ranges => skip it
 153                      */
 154                     continue;
 155                 }
 156 
 157                 /* Mark this option as option which range is defined in VM */
 158                 option.optionWithRange();
 159 
 160                 option.setMin(token);
 161 
 162                 /* Get max value */
 163                 token = line.substring(94, 119).trim();
 164                 option.setMax(token);
 165             } else if (withRanges) {
 166                 /*
 167                  * Option not have range, but asked for options with
 168                  * ranges => skip it
 169                  */
 170                 continue;
 171             }
 172 
 173             token = line.substring(141).trim();
 174             token = token.substring(1, token.indexOf("}"));
 175 
 176             if (acceptOrigin.test(token)) {
 177                 addTypeDependency(option, token);
 178                 addNameDependency(option);
 179 
 180                 allOptions.put(name, option);
 181             }
 182         }
 183 
 184         return allOptions;
 185     }
 186 
 187     static void failedMessage(String optionName, String value, boolean valid, String message) {
 188         String temp;
 189 
 190         if (valid) {
 191             temp = "valid";
 192         } else {
 193             temp = "invalid";
 194         }
 195 
 196         failedMessage(String.format("Error processing option %s with %s value '%s'! %s",
 197                 optionName, temp, value, message));
 198     }
 199 
 200     static void failedMessage(String message) {
 201         System.err.println("TEST FAILED: " + message);
 202         finalFailedMessage.append(String.format("(%s)%n", message));
 203     }    
 204 
 205     static void printOutputContent(OutputAnalyzer output) {
 206         System.err.println(String.format("stdout content[%s]", output.getStdout()));
 207         System.err.println(String.format("stderr content[%s]%n", output.getStderr()));
 208     }
 209     
 210     /**
 211      * Return string with accumulated failure messages
 212      * 
 213      * @return String with accumulated failure messages
 214      */
 215     static public String getMessageWithFailures() {
 216         return finalFailedMessage.toString();
 217     }
 218 
 219     /**
 220      * Run command line tests for options passed in the list
 221      *
 222      * @param options List of options to test
 223      * @return Number of failed tests
 224      * @throws Exception Java process can not be started
 225      */
 226     public final static int runCommandLineTests(List<? extends JVMOption> options) throws Exception {
 227         int failed = 0;
 228 
 229         for (JVMOption option : options) {
 230             failed += option.testCommandLine();
 231         }
 232 
 233         return failed;
 234     }
 235 
 236     /**
 237      * Test passed options using DynamicVMOption isValidValue and isInvalidValue
 238      * methods. Tested only writeable options.
 239      *
 240      * @param options List of options to test
 241      * @return Number of failed tests
 242      * @throws Exception
 243      */
 244     public final static int runDynamicTests(List<? extends JVMOption> options) throws Exception {
 245         int failed = 0;
 246 
 247         for (JVMOption option : options) {
 248             failed += option.testDynamic();
 249         }
 250 
 251         return failed;
 252     }
 253 
 254     /**
 255      * Test passed options using Jcmd. Tested only writeable options.
 256      *
 257      * @param options List of options to test
 258      * @return Number of failed tests
 259      * @throws Exception
 260      */
 261     public final static int runJcmdTests(List<? extends JVMOption> options) throws Exception {
 262         int failed = 0;
 263 
 264         for (JVMOption option : options) {
 265             failed += option.testJcmd();
 266         }
 267 
 268         return failed;
 269     }
 270 
 271     /**
 272      * Test passed option using attach method. Tested only writeable options.
 273      *
 274      * @param options List of options to test
 275      * @return Number of failed tests
 276      * @throws Exception
 277      */
 278     public final static int runAttachTests(List<? extends JVMOption> options) throws Exception {
 279         int failed = 0;
 280 
 281         for (JVMOption option : options) {
 282             failed += option.testAttach();
 283         }
 284 
 285         return failed;
 286     }
 287 
 288     /**
 289      * Get JVM options as map. Can return options with defined ranges or options
 290      * without range depending on "withRanges" argument. "acceptOrigin"
 291      * predicate can be used to filter option origin.
 292      *
 293      * @param withRanges true if needed options with defined ranges
 294      * @param acceptOrigin predicate for option origins. Origins can be
 295      * "product", "diagnostic" etc. Accept option only if acceptOrigin return
 296      * true.
 297      * @param additionalArgs Additional arguments to the Java process which ran
 298      * with "-XX:+PrintFlagsRanges"
 299      * @return Map from option name to the JVMOption object
 300      * @throws Exception
 301      */
 302     public static Map<String, JVMOption> getOptionsAsMap(boolean withRanges, Predicate<String> acceptOrigin,
 303             String... additionalArgs) throws Exception {
 304         Map<String, JVMOption> result;
 305         Process p;
 306         List<String> runJava = new ArrayList<>();
 307 
 308         if (additionalArgs.length > 0) {
 309             runJava.addAll(Arrays.asList(additionalArgs));
 310         }
 311         runJava.add(PRINT_FLAGS_RANGES);
 312         runJava.add("-version");
 313 
 314         p = ProcessTools.createJavaProcessBuilder(withRanges, runJava.toArray(new String[0])).start();
 315 
 316         result = getJVMOptions(new InputStreamReader(p.getInputStream()), true, acceptOrigin);
 317 
 318         p.waitFor();
 319 
 320         return result;
 321     }
 322 
 323     /**
 324      * Get JVM options as list. Can return options with defined ranges or
 325      * options without range depending on "withRanges" argument. "acceptOrigin"
 326      * predicate can be used to filter option origin.
 327      *
 328      * @param withRanges true if needed options with defined ranges
 329      * @param acceptOrigin predicate for option origins. Origins can be
 330      * "product", "diagnostic" etc. Accept option only if acceptOrigin return
 331      * true.
 332      * @param additionalArgs Additional arguments to the Java process which ran
 333      * with "-XX:+PrintFlagsRanges"
 334      * @return List of options
 335      * @throws Exception
 336      */
 337     public static List<JVMOption> getOptions(boolean withRanges, Predicate<String> acceptOrigin,
 338             String... additionalArgs) throws Exception {
 339         return new ArrayList<>(getOptionsAsMap(withRanges, acceptOrigin, additionalArgs).values());
 340     }
 341 
 342     /**
 343      * Get JVM options with ranges as list. "acceptOrigin" predicate can be used
 344      * to filter option origin.
 345      *
 346      * @param acceptOrigin predicate for option origins. Origins can be
 347      * "product", "diagnostic" etc. Accept option only if acceptOrigin return
 348      * true.
 349      * @param additionalArgs Additional arguments to the Java process which ran
 350      * with "-XX:+PrintFlagsRanges"
 351      * @return List of options
 352      * @throws Exception
 353      */
 354     public static List<JVMOption> getOptionsWithRange(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception {
 355         return getOptions(true, acceptOrigin, additionalArgs);
 356     }
 357 
 358     /**
 359      * Get JVM options with ranges as list.
 360      *
 361      * @param additionalArgs Additional arguments to the Java process which ran
 362      * with "-XX:+PrintFlagsRanges"
 363      * @return List of options
 364      * @throws Exception
 365      */
 366     public static List<JVMOption> getOptionsWithRange(String... additionalArgs) throws Exception {
 367         return getOptionsWithRange(origin -> true, additionalArgs);
 368     }
 369 
 370     /**
 371      * Get JVM options with range as map. "acceptOrigin" predicate can be used
 372      * to filter option origin.
 373      *
 374      * @param acceptOrigin predicate for option origins. Origins can be
 375      * "product", "diagnostic" etc. Accept option only if acceptOrigin return
 376      * true.
 377      * @param additionalArgs Additional arguments to the Java process which ran
 378      * with "-XX:+PrintFlagsRanges"
 379      * @return Map from option name to the JVMOption object
 380      * @throws Exception
 381      */
 382     public static Map<String, JVMOption> getOptionsWithRangeAsMap(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception {
 383         return getOptionsAsMap(true, acceptOrigin, additionalArgs);
 384     }
 385 
 386     /**
 387      * Get JVM options with range as map
 388      * 
 389      * @param additionalArgs Additional arguments to the Java process which ran
 390      * with "-XX:+PrintFlagsRanges"
 391      * @return Map from option name to the JVMOption object
 392      * @throws Exception
 393      */
 394     public static Map<String, JVMOption> getOptionsWithRangeAsMap(String... additionalArgs) throws Exception {
 395         return getOptionsWithRangeAsMap(origin -> true, additionalArgs);
 396     }
 397 
 398     /* Simple method to test that java start-up. Used for testing options. */
 399     static public void main(String[] args) {
 400         System.out.print("Java start-up!");
 401     }
 402 }