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.math.BigDecimal; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.LinkedHashMap; 35 import java.util.Map; 36 import java.util.StringTokenizer; 37 import java.util.function.Predicate; 38 import jdk.test.lib.OutputAnalyzer; 39 import jdk.test.lib.Platform; 40 import jdk.test.lib.ProcessTools; 41 42 public class JVMOptionsUtils { 43 44 /* Java option which print options with ranges */ 45 private static final String PRINT_FLAGS_RANGES = "-XX:+PrintFlagsRanges"; 46 47 /* StringBuilder to accumulate failed message */ 48 private static final StringBuilder finalFailedMessage = new StringBuilder(); 49 50 /* Used to start the JVM with the same type as current */ 51 static String VMType; 52 53 private static Map<String, JVMOption> optionsAsMap; 54 55 static { 56 if (Platform.isServer()) { 57 VMType = "-server"; 58 } else if (Platform.isClient()) { 59 VMType = "-client"; 60 } else if (Platform.isMinimal()) { 61 VMType = "-minimal"; 62 } else if (Platform.isGraal()) { 63 VMType = "-graal"; 64 } else { 65 VMType = null; 66 } 67 } 68 69 public static boolean fitsRange(String optionName, BigDecimal number) throws Exception { 70 JVMOption option; 71 String minRangeString = null; 72 String maxRangeString = null; 73 boolean fits = true; 74 75 if (optionsAsMap == null) { 76 optionsAsMap = getOptionsWithRangeAsMap(); 77 } 78 79 option = optionsAsMap.get(optionName); 80 if (option != null) { 81 minRangeString = option.getMin(); 82 if (minRangeString != null) { 83 fits = (number.compareTo(new BigDecimal(minRangeString)) >= 0); 84 } 85 maxRangeString = option.getMax(); 86 if (maxRangeString != null) { 87 fits &= (number.compareTo(new BigDecimal(maxRangeString)) <= 0); 88 } 89 } 90 91 return fits; 92 } 93 94 public static boolean fitsRange(String optionName, String number) throws Exception { 95 String lowerCase = number.toLowerCase(); 96 String multiplier = "1"; 97 if (lowerCase.endsWith("k")) { 98 multiplier = "1024"; 99 lowerCase = lowerCase.substring(0, lowerCase.length()-1); 100 } else if (lowerCase.endsWith("m")) { 101 multiplier = "1048576"; //1024*1024 102 lowerCase = lowerCase.substring(0, lowerCase.length()-1); 103 } else if (lowerCase.endsWith("g")) { 104 multiplier = "1073741824"; //1024*1024*1024 105 lowerCase = lowerCase.substring(0, lowerCase.length()-1); 106 } else if (lowerCase.endsWith("t")) { 107 multiplier = "1099511627776"; //1024*1024*1024*1024 108 lowerCase = lowerCase.substring(0, lowerCase.length()-1); 109 } 110 BigDecimal valueBig = new BigDecimal(lowerCase); 111 BigDecimal multiplierBig = new BigDecimal(multiplier); 112 return fitsRange(optionName, valueBig.multiply(multiplierBig)); 113 } 114 115 public static String getMinOptionRange(String optionName) throws Exception { 116 JVMOption option; 117 String minRange = null; 118 119 if (optionsAsMap == null) { 120 optionsAsMap = getOptionsWithRangeAsMap(); 121 } 122 123 option = optionsAsMap.get(optionName); 124 if (option != null) { 125 minRange = option.getMin(); 126 } 127 128 return minRange; 129 } 130 131 public static String getMaxOptionRange(String optionName) throws Exception { 132 JVMOption option; 133 String maxRange = null; 134 135 if (optionsAsMap == null) { 136 optionsAsMap = getOptionsWithRangeAsMap(); 137 } 138 139 option = optionsAsMap.get(optionName); 140 if (option != null) { 141 maxRange = option.getMax(); 142 } 143 144 return maxRange; 145 } 146 147 /** 148 * Add dependency for option depending on it's name. E.g. enable G1 GC for 149 * G1 options or add prepend options to not hit constraints. 150 * 151 * @param option option 152 */ 153 private static void addNameDependency(JVMOption option) { 154 String name = option.getName(); 155 156 if (name.startsWith("G1")) { 157 option.addPrepend("-XX:+UseG1GC"); 158 } 159 160 if (name.startsWith("CMS")) { 161 option.addPrepend("-XX:+UseConcMarkSweepGC"); 162 } 163 164 if (name.startsWith("NUMA")) { 165 option.addPrepend("-XX:+UseNUMA"); 166 } 167 168 switch (name) { 169 case "MinHeapFreeRatio": 170 option.addPrepend("-XX:MaxHeapFreeRatio=100"); 171 break; 172 case "MaxHeapFreeRatio": 173 option.addPrepend("-XX:MinHeapFreeRatio=0"); 174 break; 175 case "MinMetaspaceFreeRatio": 176 option.addPrepend("-XX:MaxMetaspaceFreeRatio=100"); 177 break; 178 case "MaxMetaspaceFreeRatio": 179 option.addPrepend("-XX:MinMetaspaceFreeRatio=0"); 180 break; 181 case "CMSOldPLABMin": 182 option.addPrepend("-XX:CMSOldPLABMax=" + option.getMax()); 183 break; 184 case "CMSOldPLABMax": 185 option.addPrepend("-XX:CMSOldPLABMin=" + option.getMin()); 186 break; 187 case "CMSPrecleanNumerator": 188 option.addPrepend("-XX:CMSPrecleanDenominator=" + option.getMax()); 189 break; 190 case "CMSPrecleanDenominator": 191 option.addPrepend("-XX:CMSPrecleanNumerator=" + ((new Integer(option.getMin())) - 1)); 192 break; 193 case "InitialTenuringThreshold": 194 option.addPrepend("-XX:MaxTenuringThreshold=" + option.getMax()); 195 break; 196 case "NUMAInterleaveGranularity": 197 option.addPrepend("-XX:+UseNUMAInterleaving"); 198 break; 199 case "CPUForCMSThread": 200 option.addPrepend("-XX:+BindCMSThreadToCPU"); 201 break; 202 case "VerifyGCStartAt": 203 option.addPrepend("-XX:+VerifyBeforeGC"); 204 option.addPrepend("-XX:+VerifyAfterGC"); 205 break; 206 case "NewSizeThreadIncrease": 207 option.addPrepend("-XX:+UseSerialGC"); 208 break; 209 case "SharedReadWriteSize": 210 case "SharedReadOnlySize": 211 case "SharedMiscDataSize": 212 case "SharedMiscCodeSize": 213 case "SharedBaseAddress": 214 case "SharedSymbolTableBucketSize": 215 option.addPrepend("-XX:+UnlockDiagnosticVMOptions"); 216 option.addPrepend("-XX:SharedArchiveFile=TestOptionsWithRanges.jsa"); 217 option.addPrepend("-Xshare:dump"); 218 break; 219 default: 220 /* Do nothing */ 221 break; 222 } 223 } 224 225 /** 226 * Parse JVM Options. Get input from "inputReader". Parse using 227 * "-XX:+PrintFlagsRanges" output format. 228 * 229 * @param inputReader input data for parsing 230 * @param withRanges true if needed options with defined ranges inside JVM 231 * @param acceptOrigin predicate for option origins. Origins can be 232 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 233 * to true. 234 * @return map from option name to the JVMOption object 235 * @throws IOException if an error occurred while reading the data 236 */ 237 private static Map<String, JVMOption> getJVMOptions(Reader inputReader, 238 boolean withRanges, Predicate<String> acceptOrigin) throws IOException { 239 BufferedReader reader = new BufferedReader(inputReader); 240 String type; 241 String line; 242 String token; 243 String name; 244 StringTokenizer st; 245 JVMOption option; 246 Map<String, JVMOption> allOptions = new LinkedHashMap<>(); 247 248 // Skip first line 249 line = reader.readLine(); 250 251 while ((line = reader.readLine()) != null) { 252 /* 253 * Parse option from following line: 254 * <type> <name> [ <min, optional> ... <max, optional> ] {<origin>} 255 */ 256 st = new StringTokenizer(line); 257 258 type = st.nextToken(); 259 260 name = st.nextToken(); 261 262 option = JVMOption.createVMOption(type, name); 263 264 /* Skip '[' */ 265 token = st.nextToken(); 266 267 /* Read min range or "..." if range is absent */ 268 token = st.nextToken(); 269 270 if (token.equals("...") == false) { 271 if (!withRanges) { 272 /* 273 * Option have range, but asked for options without 274 * ranges => skip it 275 */ 276 continue; 277 } 278 279 /* Mark this option as option which range is defined in VM */ 280 option.optionWithRange(); 281 282 option.setMin(token); 283 284 /* Read "..." and skip it */ 285 token = st.nextToken(); 286 287 /* Get max value */ 288 token = st.nextToken(); 289 option.setMax(token); 290 } else if (withRanges) { 291 /* 292 * Option not have range, but asked for options with 293 * ranges => skip it 294 */ 295 continue; 296 } 297 298 /* Skip ']' */ 299 token = st.nextToken(); 300 301 /* Read origin of the option */ 302 token = st.nextToken(); 303 304 while (st.hasMoreTokens()) { 305 token += st.nextToken(); 306 }; 307 token = token.substring(1, token.indexOf("}")); 308 309 if (acceptOrigin.test(token)) { 310 addNameDependency(option); 311 312 allOptions.put(name, option); 313 } 314 } 315 316 return allOptions; 317 } 318 319 static void failedMessage(String optionName, String value, boolean valid, String message) { 320 String temp; 321 322 if (valid) { 323 temp = "valid"; 324 } else { 325 temp = "invalid"; 326 } 327 328 failedMessage(String.format("Error processing option %s with %s value '%s'! %s", 329 optionName, temp, value, message)); 330 } 331 332 static void failedMessage(String message) { 333 System.err.println("TEST FAILED: " + message); 334 finalFailedMessage.append(String.format("(%s)%n", message)); 335 } 336 337 static void printOutputContent(OutputAnalyzer output) { 338 System.err.println(String.format("stdout content[%s]", output.getStdout())); 339 System.err.println(String.format("stderr content[%s]%n", output.getStderr())); 340 } 341 342 /** 343 * Return string with accumulated failure messages 344 * 345 * @return string with accumulated failure messages 346 */ 347 public static String getMessageWithFailures() { 348 return finalFailedMessage.toString(); 349 } 350 351 /** 352 * Run command line tests for options passed in the list 353 * 354 * @param options list of options to test 355 * @return number of failed tests 356 * @throws Exception if java process can not be started 357 */ 358 public static int runCommandLineTests(List<? extends JVMOption> options) throws Exception { 359 int failed = 0; 360 361 for (JVMOption option : options) { 362 failed += option.testCommandLine(); 363 } 364 365 return failed; 366 } 367 368 /** 369 * Test passed options using DynamicVMOption isValidValue and isInvalidValue 370 * methods. Only tests writeable options. 371 * 372 * @param options list of options to test 373 * @return number of failed tests 374 */ 375 public static int runDynamicTests(List<? extends JVMOption> options) { 376 int failed = 0; 377 378 for (JVMOption option : options) { 379 failed += option.testDynamic(); 380 } 381 382 return failed; 383 } 384 385 /** 386 * Test passed options using Jcmd. Only tests writeable options. 387 * 388 * @param options list of options to test 389 * @return number of failed tests 390 */ 391 public static int runJcmdTests(List<? extends JVMOption> options) { 392 int failed = 0; 393 394 for (JVMOption option : options) { 395 failed += option.testJcmd(); 396 } 397 398 return failed; 399 } 400 401 /** 402 * Test passed option using attach method. Only tests writeable options. 403 * 404 * @param options list of options to test 405 * @return number of failed tests 406 * @throws Exception if an error occurred while attaching to the target JVM 407 */ 408 public static int runAttachTests(List<? extends JVMOption> options) throws Exception { 409 int failed = 0; 410 411 for (JVMOption option : options) { 412 failed += option.testAttach(); 413 } 414 415 return failed; 416 } 417 418 /** 419 * Get JVM options as map. Can return options with defined ranges or options 420 * without range depending on "withRanges" argument. "acceptOrigin" 421 * predicate can be used to filter option origin. 422 * 423 * @param withRanges true if needed options with defined ranges inside JVM 424 * @param acceptOrigin predicate for option origins. Origins can be 425 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 426 * to true. 427 * @param additionalArgs additional arguments to the Java process which ran 428 * with "-XX:+PrintFlagsRanges" 429 * @return map from option name to the JVMOption object 430 * @throws Exception if a new process can not be created or an error 431 * occurred while reading the data 432 */ 433 public static Map<String, JVMOption> getOptionsAsMap(boolean withRanges, Predicate<String> acceptOrigin, 434 String... additionalArgs) throws Exception { 435 Map<String, JVMOption> result; 436 Process p; 437 List<String> runJava = new ArrayList<>(); 438 439 if (additionalArgs.length > 0) { 440 runJava.addAll(Arrays.asList(additionalArgs)); 441 } 442 443 if (VMType != null) { 444 runJava.add(VMType); 445 } 446 runJava.add(PRINT_FLAGS_RANGES); 447 runJava.add("-version"); 448 449 p = ProcessTools.createJavaProcessBuilder(runJava.toArray(new String[0])).start(); 450 451 result = getJVMOptions(new InputStreamReader(p.getInputStream()), withRanges, acceptOrigin); 452 453 p.waitFor(); 454 455 return result; 456 } 457 458 /** 459 * Get JVM options as list. Can return options with defined ranges or 460 * options without range depending on "withRanges" argument. "acceptOrigin" 461 * predicate can be used to filter option origin. 462 * 463 * @param withRanges true if needed options with defined ranges inside JVM 464 * @param acceptOrigin predicate for option origins. Origins can be 465 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 466 * to true. 467 * @param additionalArgs additional arguments to the Java process which ran 468 * with "-XX:+PrintFlagsRanges" 469 * @return List of options 470 * @throws Exception if a new process can not be created or an error 471 * occurred while reading the data 472 */ 473 public static List<JVMOption> getOptions(boolean withRanges, Predicate<String> acceptOrigin, 474 String... additionalArgs) throws Exception { 475 return new ArrayList<>(getOptionsAsMap(withRanges, acceptOrigin, additionalArgs).values()); 476 } 477 478 /** 479 * Get JVM options with ranges as list. "acceptOrigin" predicate can be used 480 * to filter option origin. 481 * 482 * @param acceptOrigin predicate for option origins. Origins can be 483 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 484 * to true. 485 * @param additionalArgs additional arguments to the Java process which ran 486 * with "-XX:+PrintFlagsRanges" 487 * @return List of options 488 * @throws Exception if a new process can not be created or an error 489 * occurred while reading the data 490 */ 491 public static List<JVMOption> getOptionsWithRange(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception { 492 return getOptions(true, acceptOrigin, additionalArgs); 493 } 494 495 /** 496 * Get JVM options with ranges as list. 497 * 498 * @param additionalArgs additional arguments to the Java process which ran 499 * with "-XX:+PrintFlagsRanges" 500 * @return list of options 501 * @throws Exception if a new process can not be created or an error 502 * occurred while reading the data 503 */ 504 public static List<JVMOption> getOptionsWithRange(String... additionalArgs) throws Exception { 505 return getOptionsWithRange(origin -> true, additionalArgs); 506 } 507 508 /** 509 * Get JVM options with range as map. "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 Map from option name to the JVMOption object 518 * @throws Exception if a new process can not be created or an error 519 * occurred while reading the data 520 */ 521 public static Map<String, JVMOption> getOptionsWithRangeAsMap(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception { 522 return getOptionsAsMap(true, acceptOrigin, additionalArgs); 523 } 524 525 /** 526 * Get JVM options with range as map 527 * 528 * @param additionalArgs additional arguments to the Java process which ran 529 * with "-XX:+PrintFlagsRanges" 530 * @return map from option name to the JVMOption object 531 * @throws Exception if a new process can not be created or an error 532 * occurred while reading the data 533 */ 534 public static Map<String, JVMOption> getOptionsWithRangeAsMap(String... additionalArgs) throws Exception { 535 return getOptionsWithRangeAsMap(origin -> true, additionalArgs); 536 } 537 538 /* Simple method to test that java start-up. Used for testing options. */ 539 public static void main(String[] args) { 540 System.out.print("Java start-up!"); 541 } 542 }