1 /*
   2  * Copyright (c) 2013, 2019, 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 gc.arguments;
  25 
  26 import java.util.regex.Matcher;
  27 import java.util.regex.Pattern;
  28 import java.util.ArrayList;
  29 import java.util.Arrays;
  30 
  31 import jdk.test.lib.process.ProcessTools;
  32 import jdk.test.lib.process.OutputAnalyzer;
  33 import sun.hotspot.WhiteBox;
  34 
  35 class ErgoArgsPrinter {
  36   public static void main(String[] args) throws Exception {
  37     WhiteBox wb = WhiteBox.getWhiteBox();
  38     wb.printHeapSizes();
  39   }
  40 }
  41 
  42 final class MinInitialMaxValues {
  43   public long minHeapSize;
  44   public long initialHeapSize;
  45   public long maxHeapSize;
  46 
  47   public long spaceAlignment;
  48   public long heapAlignment;
  49 }
  50 
  51 class TestMaxHeapSizeTools {
  52 
  53   public static void checkMinInitialMaxHeapFlags(String gcflag) throws Exception {
  54     checkInvalidMinInitialHeapCombinations(gcflag);
  55     checkValidMinInitialHeapCombinations(gcflag);
  56     checkInvalidInitialMaxHeapCombinations(gcflag);
  57     checkValidInitialMaxHeapCombinations(gcflag);
  58     checkInvalidMinMaxHeapCombinations(gcflag);
  59     checkValidMinMaxHeapCombinations(gcflag);
  60   }
  61 
  62   public static void checkMinInitialErgonomics(String gcflag) throws Exception {
  63     // heap sizing ergonomics use the value NewSize + OldSize as default values
  64     // for ergonomics calculation. Retrieve these values.
  65     long[] values = new long[2];
  66     getNewOldSize(gcflag, values);
  67 
  68     // we check cases with values smaller and larger than this default value.
  69     long newPlusOldSize = values[0] + values[1];
  70     long smallValue = newPlusOldSize / 2;
  71     long largeValue = newPlusOldSize * 2;
  72     long maxHeapSize = largeValue + (2 * 1024 * 1024);
  73 
  74     // -Xms is not set
  75     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize }, values, -1, -1);
  76     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:MinHeapSize=" + smallValue }, values, smallValue, -1);
  77     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:MinHeapSize=" + largeValue }, values, largeValue, -1);
  78     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:MinHeapSize=0" }, values, -1, -1);
  79     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:InitialHeapSize=" + smallValue }, values, -1, smallValue);
  80     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:InitialHeapSize=" + largeValue }, values, -1, largeValue);
  81     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:InitialHeapSize=0" }, values, -1, -1);
  82     // Some extra checks when both are set.
  83     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:MinHeapSize=" + smallValue, "-XX:InitialHeapSize=" + smallValue }, values, smallValue, smallValue);
  84     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:MinHeapSize=" + smallValue, "-XX:InitialHeapSize=" + largeValue }, values, smallValue, largeValue);
  85     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-XX:MinHeapSize=" + largeValue, "-XX:InitialHeapSize=" + largeValue }, values, largeValue, largeValue);
  86 
  87     // -Xms is set to zero
  88     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0" }, values, -1, -1);
  89     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:MinHeapSize=" + smallValue }, values, smallValue, -1);
  90     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:MinHeapSize=" + largeValue }, values, largeValue, -1);
  91     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:MinHeapSize=0" }, values, -1, -1);
  92     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:InitialHeapSize=" + smallValue }, values, -1, smallValue);
  93     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:InitialHeapSize=" + largeValue }, values, -1, largeValue);
  94     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:InitialHeapSize=0" }, values, -1, -1);
  95     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:MinHeapSize=" + smallValue, "-XX:InitialHeapSize=" + smallValue }, values, smallValue, smallValue);
  96     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:MinHeapSize=" + smallValue, "-XX:InitialHeapSize=" + largeValue }, values, smallValue, largeValue);
  97     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms0", "-XX:MinHeapSize=" + largeValue, "-XX:InitialHeapSize=" + largeValue }, values, largeValue, largeValue);
  98 
  99     // -Xms is set to small value
 100     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + smallValue }, values, -1, -1);
 101     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + smallValue, "-XX:MinHeapSize=" + smallValue }, values, smallValue, smallValue);
 102     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + smallValue, "-XX:MinHeapSize=0" }, values, -1, smallValue);
 103     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + smallValue, "-XX:InitialHeapSize=" + smallValue }, values, smallValue, smallValue);
 104     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + smallValue, "-XX:InitialHeapSize=" + largeValue }, values, smallValue, largeValue);
 105     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + smallValue, "-XX:InitialHeapSize=0" }, values, smallValue, -1);
 106 
 107     // -Xms is set to large value
 108     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + largeValue }, values, largeValue, largeValue);
 109     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + largeValue, "-XX:InitialHeapSize=0" }, values, largeValue, -1);
 110     checkErgonomics(new String[] { gcflag, "-Xmx" + maxHeapSize, "-Xms" + largeValue, "-XX:MinHeapSize=0" }, values, -1, largeValue);
 111   }
 112 
 113   private static long align_up(long value, long alignment) {
 114     long alignmentMinusOne = alignment - 1;
 115     return (value + alignmentMinusOne) & ~alignmentMinusOne;
 116   }
 117 
 118   private static void getNewOldSize(String gcflag, long[] values) throws Exception {
 119     ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(gcflag,
 120       "-XX:+PrintFlagsFinal", "-version");
 121     OutputAnalyzer output = new OutputAnalyzer(pb.start());
 122     output.shouldHaveExitValue(0);
 123 
 124     String stdout = output.getStdout();
 125     values[0] = getFlagValue(" NewSize", stdout);
 126     values[1] = getFlagValue(" OldSize", stdout);
 127   }
 128 
 129   public static void checkGenMaxHeapErgo(String gcflag) throws Exception {
 130     TestMaxHeapSizeTools.checkGenMaxHeapSize(gcflag, 4);
 131     TestMaxHeapSizeTools.checkGenMaxHeapSize(gcflag, 5);
 132   }
 133 
 134   private static void checkInvalidMinInitialHeapCombinations(String gcflag) throws Exception {
 135     expectError(new String[] { gcflag, "-XX:InitialHeapSize=1023K", "-version" });
 136     expectError(new String[] { gcflag, "-Xms64M", "-XX:InitialHeapSize=32M", "-version" });
 137     expectError(new String[] { gcflag, "-XX:MinHeapSize=1023K", "-version" });
 138     expectError(new String[] { gcflag, "-Xms4M", "-XX:MinHeapSize=8M", "-version" });
 139     expectError(new String[] { gcflag, "-XX:MinHeapSize=8M -XX:InitialHeapSize=4m" });
 140   }
 141 
 142   private static void checkValidMinInitialHeapCombinations(String gcflag) throws Exception {
 143     expectValid(new String[] { gcflag, "-XX:InitialHeapSize=1024K", "-version" });
 144     expectValid(new String[] { gcflag, "-XX:InitialHeapSize=8M", "-Xms4M", "-version" });
 145     expectValid(new String[] { gcflag, "-Xms4M", "-XX:InitialHeapSize=8M", "-version" });
 146     expectValid(new String[] { gcflag, "-XX:InitialHeapSize=8M", "-Xms8M", "-version" });
 147     expectValid(new String[] { gcflag, "-XX:MinHeapSize=1024K", "-version" });
 148     expectValid(new String[] { gcflag, "-XX:MinHeapSize=8M", "-Xms4M", "-version" });
 149     expectValid(new String[] { gcflag, "-XX:MinHeapSize=8M", "-Xms8M", "-version" });
 150     expectValid(new String[] { gcflag, "-Xms8M", "-XX:MinHeapSize=4M", "-version" });
 151     // the following is not an error as -Xms sets both minimal and initial heap size
 152     expectValid(new String[] { gcflag, "-XX:InitialHeapSize=4M", "-Xms8M", "-version" });
 153     expectValid(new String[] { gcflag, "-XX:MinHeapSize=4M", "-Xms8M", "-version" });
 154   }
 155 
 156   private static void checkInvalidInitialMaxHeapCombinations(String gcflag) throws Exception {
 157     expectError(new String[] { gcflag, "-XX:MaxHeapSize=2047K", "-version" });
 158     expectError(new String[] { gcflag, "-XX:MaxHeapSize=4M", "-XX:InitialHeapSize=8M", "-version" });
 159     expectError(new String[] { gcflag, "-XX:InitialHeapSize=8M", "-XX:MaxHeapSize=4M", "-version" });
 160   }
 161 
 162   private static void checkInvalidMinMaxHeapCombinations(String gcflag) throws Exception {
 163     expectError(new String[] { gcflag, "-XX:MaxHeapSize=4M", "-XX:MinHeapSize=8M", "-version" });
 164     expectError(new String[] { gcflag, "-XX:MinHeapSize=8M", "-XX:MaxHeapSize=4M", "-version" });
 165   }
 166 
 167 
 168   private static void checkValidInitialMaxHeapCombinations(String gcflag) throws Exception {
 169     expectValid(new String[] { gcflag, "-XX:InitialHeapSize=4M", "-XX:MaxHeapSize=8M", "-version" });
 170     expectValid(new String[] { gcflag, "-XX:MaxHeapSize=8M", "-XX:InitialHeapSize=4M", "-version" });
 171     expectValid(new String[] { gcflag, "-XX:MaxHeapSize=4M", "-XX:InitialHeapSize=4M", "-version" });
 172     // a value of "0" for initial heap size means auto-detect
 173     expectValid(new String[] { gcflag, "-XX:MaxHeapSize=4M", "-XX:InitialHeapSize=0M", "-version" });
 174   }
 175 
 176   private static void checkValidMinMaxHeapCombinations(String gcflag) throws Exception {
 177     expectValid(new String[] { gcflag, "-XX:MinHeapSize=4M", "-XX:MaxHeapSize=8M", "-version" });
 178     expectValid(new String[] { gcflag, "-XX:MaxHeapSize=8M", "-XX:MinHeapSize=4M", "-version" });
 179     expectValid(new String[] { gcflag, "-XX:MaxHeapSize=4M", "-XX:MinHeapSize=4M", "-version" });
 180     // a value of "0" for min heap size means auto-detect
 181     expectValid(new String[] { gcflag, "-XX:MaxHeapSize=4M", "-XX:MinHeapSize=0M", "-version" });
 182   }
 183 
 184   private static long valueAfter(String source, String match) {
 185     int start = source.indexOf(match) + match.length();
 186     String tail = source.substring(start).split(" ")[0];
 187     return Long.parseLong(tail);
 188   }
 189 
 190   /**
 191    * Executes a new VM process with the given class and parameters.
 192    * @param vmargs Arguments to the VM to run
 193    * @param classname Name of the class to run
 194    * @param arguments Arguments to the class
 195    * @param useTestDotJavaDotOpts Use test.java.opts as part of the VM argument string
 196    * @return The OutputAnalyzer with the results for the invocation.
 197    */
 198   public static OutputAnalyzer runWhiteBoxTest(String[] vmargs, String classname, String[] arguments, boolean useTestDotJavaDotOpts) throws Exception {
 199     ArrayList<String> finalargs = new ArrayList<String>();
 200 
 201     String[] whiteboxOpts = new String[] {
 202       "-Xbootclasspath/a:.",
 203       "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI",
 204       "-cp", System.getProperty("java.class.path"),
 205     };
 206 
 207     if (useTestDotJavaDotOpts) {
 208       // System.getProperty("test.java.opts") is '' if no options is set,
 209       // we need to skip such a result
 210       String[] externalVMOpts = new String[0];
 211       if (System.getProperty("test.java.opts") != null && System.getProperty("test.java.opts").length() != 0) {
 212         externalVMOpts = System.getProperty("test.java.opts").split(" ");
 213       }
 214       finalargs.addAll(Arrays.asList(externalVMOpts));
 215     }
 216 
 217     finalargs.addAll(Arrays.asList(vmargs));
 218     finalargs.addAll(Arrays.asList(whiteboxOpts));
 219     finalargs.add(classname);
 220     finalargs.addAll(Arrays.asList(arguments));
 221 
 222     ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(finalargs.toArray(new String[0]));
 223     OutputAnalyzer output = new OutputAnalyzer(pb.start());
 224     output.shouldHaveExitValue(0);
 225 
 226     return output;
 227   }
 228 
 229   private static void getMinInitialMaxHeap(String[] args, MinInitialMaxValues val) throws Exception {
 230     OutputAnalyzer output = runWhiteBoxTest(args, ErgoArgsPrinter.class.getName(), new String[] {}, false);
 231 
 232     // the output we watch for has the following format:
 233     //
 234     // "Minimum heap X Initial heap Y Maximum heap Z Min alignment A Max Alignment B"
 235     //
 236     // where A, B, X, Y and Z are sizes in bytes.
 237     // Unfortunately there is no other way to retrieve the minimum heap size and
 238     // the alignments.
 239 
 240     Matcher m = Pattern.compile("Minimum heap \\d+ Initial heap \\d+ Maximum heap \\d+ Space alignment \\d+ Heap alignment \\d+").
 241       matcher(output.getStdout());
 242     if (!m.find()) {
 243       throw new RuntimeException("Could not find heap size string.");
 244     }
 245 
 246     String match = m.group();
 247 
 248     // actual values
 249     val.minHeapSize = valueAfter(match, "Minimum heap ");
 250     val.initialHeapSize = valueAfter(match, "Initial heap ");
 251     val.maxHeapSize = valueAfter(match, "Maximum heap ");
 252     val.spaceAlignment = valueAfter(match, "Space alignment ");
 253     val.heapAlignment = valueAfter(match, "Heap alignment ");
 254   }
 255 
 256   /**
 257    * Verify whether the VM automatically synchronizes minimum and initial heap size if only
 258    * one is given for the GC specified.
 259    */
 260   public static void checkErgonomics(String[] args, long[] newoldsize,
 261     long expectedMin, long expectedInitial) throws Exception {
 262 
 263     MinInitialMaxValues v = new MinInitialMaxValues();
 264     getMinInitialMaxHeap(args, v);
 265 
 266     if ((expectedMin != -1) && (align_up(expectedMin, v.heapAlignment) != v.minHeapSize)) {
 267       throw new RuntimeException("Actual minimum heap size of " + v.minHeapSize +
 268         " differs from expected minimum heap size of " + expectedMin);
 269     }
 270 
 271     if ((expectedInitial != -1) && (align_up(expectedInitial, v.heapAlignment) != v.initialHeapSize)) {
 272       throw new RuntimeException("Actual initial heap size of " + v.initialHeapSize +
 273         " differs from expected initial heap size of " + expectedInitial);
 274     }
 275 
 276     // always check the invariant min <= initial <= max heap size
 277     if (!(v.minHeapSize <= v.initialHeapSize && v.initialHeapSize <= v.maxHeapSize)) {
 278       throw new RuntimeException("Inconsistent min/initial/max heap sizes, they are " +
 279         v.minHeapSize + "/" + v.initialHeapSize + "/" + v.maxHeapSize);
 280     }
 281   }
 282 
 283   /**
 284    * Verify whether the VM respects the given maximum heap size in MB for the
 285    * GC specified.
 286    * @param gcflag The garbage collector to test as command line flag. E.g. -XX:+UseG1GC
 287    * @param maxHeapSize the maximum heap size to verify, in MB.
 288    */
 289   public static void checkGenMaxHeapSize(String gcflag, long maxHeapsize) throws Exception {
 290     final long K = 1024;
 291 
 292     MinInitialMaxValues v = new MinInitialMaxValues();
 293     getMinInitialMaxHeap(new String[] { gcflag, "-XX:MaxHeapSize=" + maxHeapsize + "M" }, v);
 294 
 295     long expectedHeapSize = align_up(maxHeapsize * K * K, v.heapAlignment);
 296     long actualHeapSize = v.maxHeapSize;
 297 
 298     if (actualHeapSize > expectedHeapSize) {
 299       throw new RuntimeException("Heap has " + actualHeapSize  +
 300         " bytes, expected to be less than " + expectedHeapSize);
 301     }
 302   }
 303 
 304   private static long getFlagValue(String flag, String where) {
 305     Matcher m = Pattern.compile(flag + "\\s+:?=\\s+\\d+").matcher(where);
 306     if (!m.find()) {
 307       throw new RuntimeException("Could not find value for flag " + flag + " in output string");
 308     }
 309     String match = m.group();
 310     return Long.parseLong(match.substring(match.lastIndexOf(" ") + 1, match.length()));
 311   }
 312 
 313   private static void shouldContainOrNot(OutputAnalyzer output, boolean contains, String message) throws Exception {
 314     if (contains) {
 315       output.shouldContain(message);
 316     } else {
 317       output.shouldNotContain(message);
 318     }
 319   }
 320 
 321   private static void expect(String[] flags, boolean hasWarning, boolean hasError, int errorcode) throws Exception {
 322     ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(flags);
 323     OutputAnalyzer output = new OutputAnalyzer(pb.start());
 324     shouldContainOrNot(output, hasWarning, "Warning");
 325     shouldContainOrNot(output, hasError, "Error");
 326     output.shouldHaveExitValue(errorcode);
 327   }
 328 
 329   private static void expectError(String[] flags) throws Exception {
 330     expect(flags, false, true, 1);
 331   }
 332 
 333   private static void expectValid(String[] flags) throws Exception {
 334     expect(flags, false, false, 0);
 335   }
 336 }