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