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 * Parse JVM Options. Get input from "inputReader". Parse using 120 * "-XX:+PrintFlagsRanges" output format. 121 * 122 * @param inputReader input data for parsing 123 * @param withRanges true if needed options with defined ranges inside JVM 124 * @param acceptOrigin predicate for option origins. Origins can be 125 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 126 * to true. 127 * @return map from option name to the JVMOption object 128 * @throws IOException if an error occurred while reading the data 129 */ 130 private static Map<String, JVMOption> getJVMOptions(Reader inputReader, 131 boolean withRanges, Predicate<String> acceptOrigin) throws IOException { 132 BufferedReader reader = new BufferedReader(inputReader); 133 String type; 134 String line; 135 String token; 136 String name; 137 StringTokenizer st; 138 JVMOption option; 139 Map<String, JVMOption> allOptions = new LinkedHashMap<>(); 140 141 // Skip first line 142 line = reader.readLine(); 143 144 while ((line = reader.readLine()) != null) { 145 /* 146 * Parse option from following line: 147 * <type> <name> [ <min, optional> ... <max, optional> ] {<origin>} 148 */ 149 st = new StringTokenizer(line); 150 151 type = st.nextToken(); 152 153 name = st.nextToken(); 154 155 option = JVMOption.createVMOption(type, name); 156 157 /* Skip '[' */ 158 token = st.nextToken(); 159 160 /* Read min range or "..." if range is absent */ 161 token = st.nextToken(); 162 163 if (token.equals("...") == false) { 164 if (!withRanges) { 165 /* 166 * Option have range, but asked for options without 167 * ranges => skip it 168 */ 169 continue; 170 } 171 172 /* Mark this option as option which range is defined in VM */ 173 option.optionWithRange(); 174 175 option.setMin(token); 176 177 /* Read "..." and skip it */ 178 token = st.nextToken(); 179 180 /* Get max value */ 181 token = st.nextToken(); 182 option.setMax(token); 183 } else if (withRanges) { 184 /* 185 * Option not have range, but asked for options with 186 * ranges => skip it 187 */ 188 continue; 189 } 190 191 /* Skip ']' */ 192 token = st.nextToken(); 193 194 /* Read origin of the option */ 195 token = st.nextToken(); 196 197 while (st.hasMoreTokens()) { 198 token += st.nextToken(); 199 }; 200 token = token.substring(1, token.indexOf("}")); 201 202 if (acceptOrigin.test(token)) { 203 addNameDependency(option); 204 205 allOptions.put(name, option); 206 } 207 } 208 209 return allOptions; 210 } 211 212 static void failedMessage(String optionName, String value, boolean valid, String message) { 213 String temp; 214 215 if (valid) { 216 temp = "valid"; 217 } else { 218 temp = "invalid"; 219 } 220 221 failedMessage(String.format("Error processing option %s with %s value '%s'! %s", 222 optionName, temp, value, message)); 223 } 224 225 static void failedMessage(String message) { 226 System.err.println("TEST FAILED: " + message); 227 finalFailedMessage.append(String.format("(%s)%n", message)); 228 } 229 230 static void printOutputContent(OutputAnalyzer output) { 231 System.err.println(String.format("stdout content[%s]", output.getStdout())); 232 System.err.println(String.format("stderr content[%s]%n", output.getStderr())); 233 } 234 235 /** 236 * Return string with accumulated failure messages 237 * 238 * @return string with accumulated failure messages 239 */ 240 public static String getMessageWithFailures() { 241 return finalFailedMessage.toString(); 242 } 243 244 /** 245 * Run command line tests for options passed in the list 246 * 247 * @param options list of options to test 248 * @return number of failed tests 249 * @throws Exception if java process can not be started 250 */ 251 public static int runCommandLineTests(List<? extends JVMOption> options) throws Exception { 252 int failed = 0; 253 254 for (JVMOption option : options) { 255 failed += option.testCommandLine(); 256 } 257 258 return failed; 259 } 260 261 /** 262 * Test passed options using DynamicVMOption isValidValue and isInvalidValue 263 * methods. Only tests writeable options. 264 * 265 * @param options list of options to test 266 * @return number of failed tests 267 */ 268 public static int runDynamicTests(List<? extends JVMOption> options) { 269 int failed = 0; 270 271 for (JVMOption option : options) { 272 failed += option.testDynamic(); 273 } 274 275 return failed; 276 } 277 278 /** 279 * Test passed options using Jcmd. Only tests writeable options. 280 * 281 * @param options list of options to test 282 * @return number of failed tests 283 */ 284 public static int runJcmdTests(List<? extends JVMOption> options) { 285 int failed = 0; 286 287 for (JVMOption option : options) { 288 failed += option.testJcmd(); 289 } 290 291 return failed; 292 } 293 294 /** 295 * Test passed option using attach method. Only tests writeable options. 296 * 297 * @param options list of options to test 298 * @return number of failed tests 299 * @throws Exception if an error occurred while attaching to the target JVM 300 */ 301 public static int runAttachTests(List<? extends JVMOption> options) throws Exception { 302 int failed = 0; 303 304 for (JVMOption option : options) { 305 failed += option.testAttach(); 306 } 307 308 return failed; 309 } 310 311 /** 312 * Get JVM options as map. Can return options with defined ranges or options 313 * without range depending on "withRanges" argument. "acceptOrigin" 314 * predicate can be used to filter option origin. 315 * 316 * @param withRanges true if needed options with defined ranges inside JVM 317 * @param acceptOrigin predicate for option origins. Origins can be 318 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 319 * to true. 320 * @param additionalArgs additional arguments to the Java process which ran 321 * with "-XX:+PrintFlagsRanges" 322 * @return map from option name to the JVMOption object 323 * @throws Exception if a new process can not be created or an error 324 * occurred while reading the data 325 */ 326 public static Map<String, JVMOption> getOptionsAsMap(boolean withRanges, Predicate<String> acceptOrigin, 327 String... additionalArgs) throws Exception { 328 Map<String, JVMOption> result; 329 Process p; 330 List<String> runJava = new ArrayList<>(); 331 332 if (additionalArgs.length > 0) { 333 runJava.addAll(Arrays.asList(additionalArgs)); 334 } 335 336 if (VMType != null) { 337 runJava.add(VMType); 338 } 339 runJava.add(PRINT_FLAGS_RANGES); 340 runJava.add("-version"); 341 342 p = ProcessTools.createJavaProcessBuilder(runJava.toArray(new String[0])).start(); 343 344 result = getJVMOptions(new InputStreamReader(p.getInputStream()), withRanges, acceptOrigin); 345 346 p.waitFor(); 347 348 return result; 349 } 350 351 /** 352 * Get JVM options as list. Can return options with defined ranges or 353 * options without range depending on "withRanges" argument. "acceptOrigin" 354 * predicate can be used to filter option origin. 355 * 356 * @param withRanges true if needed options with defined ranges inside JVM 357 * @param acceptOrigin predicate for option origins. Origins can be 358 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 359 * to true. 360 * @param additionalArgs additional arguments to the Java process which ran 361 * with "-XX:+PrintFlagsRanges" 362 * @return List of options 363 * @throws Exception if a new process can not be created or an error 364 * occurred while reading the data 365 */ 366 public static List<JVMOption> getOptions(boolean withRanges, Predicate<String> acceptOrigin, 367 String... additionalArgs) throws Exception { 368 return new ArrayList<>(getOptionsAsMap(withRanges, acceptOrigin, additionalArgs).values()); 369 } 370 371 /** 372 * Get JVM options with ranges as list. "acceptOrigin" predicate can be used 373 * to filter option origin. 374 * 375 * @param acceptOrigin predicate for option origins. Origins can be 376 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 377 * to true. 378 * @param additionalArgs additional arguments to the Java process which ran 379 * with "-XX:+PrintFlagsRanges" 380 * @return List of options 381 * @throws Exception if a new process can not be created or an error 382 * occurred while reading the data 383 */ 384 public static List<JVMOption> getOptionsWithRange(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception { 385 return getOptions(true, acceptOrigin, additionalArgs); 386 } 387 388 /** 389 * Get JVM options with ranges as list. 390 * 391 * @param additionalArgs additional arguments to the Java process which ran 392 * with "-XX:+PrintFlagsRanges" 393 * @return list of options 394 * @throws Exception if a new process can not be created or an error 395 * occurred while reading the data 396 */ 397 public static List<JVMOption> getOptionsWithRange(String... additionalArgs) throws Exception { 398 return getOptionsWithRange(origin -> true, additionalArgs); 399 } 400 401 /** 402 * Get JVM options with range as map. "acceptOrigin" predicate can be used 403 * to filter option origin. 404 * 405 * @param acceptOrigin predicate for option origins. Origins can be 406 * "product", "diagnostic" etc. Accept option only if acceptOrigin evaluates 407 * to true. 408 * @param additionalArgs additional arguments to the Java process which ran 409 * with "-XX:+PrintFlagsRanges" 410 * @return Map from option name to the JVMOption object 411 * @throws Exception if a new process can not be created or an error 412 * occurred while reading the data 413 */ 414 public static Map<String, JVMOption> getOptionsWithRangeAsMap(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception { 415 return getOptionsAsMap(true, acceptOrigin, additionalArgs); 416 } 417 418 /** 419 * Get JVM options with range as map 420 * 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> getOptionsWithRangeAsMap(String... additionalArgs) throws Exception { 428 return getOptionsWithRangeAsMap(origin -> true, additionalArgs); 429 } 430 431 /* Simple method to test that java start-up. Used for testing options. */ 432 public static void main(String[] args) { 433 System.out.print("Java start-up!"); 434 } 435 }