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





 558     }
 559 }
--- EOF ---