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 static private 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. ran java in 102 * compilation mode for compiler options. 103 * 104 * @param option Option 105 * @param type Type of the option 106 */ 107 static private 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 120 * @param acceptOrigin predicate for option origins. Origins can be 121 * "product", "diagnostic" etc. Accept option only if acceptOrigin return 122 * true. 123 * @return Map from option name to the JVMOption object 124 * @throws IOException Error occurred while reading the data 125 */ 126 static private 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 static public 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 Java process can not be started 247 */ 248 public final 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. Tested only writeable options. 261 * 262 * @param options List of options to test 263 * @return Number of failed tests 264 * @throws Exception 265 */ 266 public final static int runDynamicTests(List<? extends JVMOption> options) throws Exception { 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. Tested only writeable options. 278 * 279 * @param options List of options to test 280 * @return Number of failed tests 281 * @throws Exception 282 */ 283 public final static int runJcmdTests(List<? extends JVMOption> options) throws Exception { 284 int failed = 0; 285 286 for (JVMOption option : options) { 287 failed += option.testJcmd(); 288 } 289 290 return failed; 291 } 292 293 /** 294 * Test passed option using attach method. Tested only writeable options. 295 * 296 * @param options List of options to test 297 * @return Number of failed tests 298 * @throws Exception 299 */ 300 public final static int runAttachTests(List<? extends JVMOption> options) throws Exception { 301 int failed = 0; 302 303 for (JVMOption option : options) { 304 failed += option.testAttach(); 305 } 306 307 return failed; 308 } 309 310 /** 311 * Get JVM options as map. Can return options with defined ranges or options 312 * without range depending on "withRanges" argument. "acceptOrigin" 313 * predicate can be used to filter option origin. 314 * 315 * @param withRanges true if needed options with defined ranges 316 * @param acceptOrigin predicate for option origins. Origins can be 317 * "product", "diagnostic" etc. Accept option only if acceptOrigin return 318 * true. 319 * @param additionalArgs Additional arguments to the Java process which ran 320 * with "-XX:+PrintFlagsRanges" 321 * @return Map from option name to the JVMOption object 322 * @throws Exception 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 runJava.add(PRINT_FLAGS_RANGES); 334 runJava.add("-version"); 335 336 p = ProcessTools.createJavaProcessBuilder(true, runJava.toArray(new String[0])).start(); 337 338 result = getJVMOptions(new InputStreamReader(p.getInputStream()), withRanges, acceptOrigin); 339 340 p.waitFor(); 341 342 return result; 343 } 344 345 /** 346 * Get JVM options as list. Can return options with defined ranges or 347 * options without range depending on "withRanges" argument. "acceptOrigin" 348 * predicate can be used to filter option origin. 349 * 350 * @param withRanges true if needed options with defined ranges 351 * @param acceptOrigin predicate for option origins. Origins can be 352 * "product", "diagnostic" etc. Accept option only if acceptOrigin return 353 * true. 354 * @param additionalArgs Additional arguments to the Java process which ran 355 * with "-XX:+PrintFlagsRanges" 356 * @return List of options 357 * @throws Exception 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 return 370 * true. 371 * @param additionalArgs Additional arguments to the Java process which ran 372 * with "-XX:+PrintFlagsRanges" 373 * @return List of options 374 * @throws Exception 375 */ 376 public static List<JVMOption> getOptionsWithRange(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception { 377 return getOptions(true, acceptOrigin, additionalArgs); 378 } 379 380 /** 381 * Get JVM options with ranges as list. 382 * 383 * @param additionalArgs Additional arguments to the Java process which ran 384 * with "-XX:+PrintFlagsRanges" 385 * @return List of options 386 * @throws Exception 387 */ 388 public static List<JVMOption> getOptionsWithRange(String... additionalArgs) throws Exception { 389 return getOptionsWithRange(origin -> true, additionalArgs); 390 } 391 392 /** 393 * Get JVM options with range as map. "acceptOrigin" predicate can be used 394 * to filter option origin. 395 * 396 * @param acceptOrigin predicate for option origins. Origins can be 397 * "product", "diagnostic" etc. Accept option only if acceptOrigin return 398 * true. 399 * @param additionalArgs Additional arguments to the Java process which ran 400 * with "-XX:+PrintFlagsRanges" 401 * @return Map from option name to the JVMOption object 402 * @throws Exception 403 */ 404 public static Map<String, JVMOption> getOptionsWithRangeAsMap(Predicate<String> acceptOrigin, String... additionalArgs) throws Exception { 405 return getOptionsAsMap(true, acceptOrigin, additionalArgs); 406 } 407 408 /** 409 * Get JVM options with range as map 410 * 411 * @param additionalArgs Additional arguments to the Java process which ran 412 * with "-XX:+PrintFlagsRanges" 413 * @return Map from option name to the JVMOption object 414 * @throws Exception 415 */ 416 public static Map<String, JVMOption> getOptionsWithRangeAsMap(String... additionalArgs) throws Exception { 417 return getOptionsWithRangeAsMap(origin -> true, additionalArgs); 418 } 419 420 /* Simple method to test that java start-up. Used for testing options. */ 421 static public void main(String[] args) { 422 System.out.print("Java start-up!"); 423 } 424 }