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