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