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