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("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 }