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 }