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