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