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