1 /*
   2  * Copyright (c) 2015, 2016 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 
  25 package gc.testlibrary;
  26 
  27 import jdk.test.lib.JDKToolLauncher;
  28 import jdk.test.lib.OutputAnalyzer;
  29 import sun.hotspot.WhiteBox;
  30 
  31 import java.io.File;
  32 import java.io.IOException;
  33 import java.nio.file.Files;
  34 import java.nio.file.Path;
  35 import java.nio.file.Paths;
  36 
  37 public class Helpers {
  38 
  39     /**
  40      * Size of a long field in bytes
  41      */
  42     public static final int SIZE_OF_LONG = 8;
  43 
  44     // In case of 128 byte padding
  45     private static final int MAX_PADDING_SIZE = 128;
  46 
  47     /**
  48      * According class file format theoretical amount of fields in class is u2 which is (256 * 256 - 1).
  49      * Some service info takes place in constant pool and we really could make a class with lesser amount of fields.
  50      *
  51      * Since the exact value is not so important and I would like to avoid issues that may be caused by future changes/
  52      * different archs etc I selected (256 * 256 - 1024) for this constant.
  53      * The test works with other values too but the smaller the number the more classes we need to generate and it takes
  54      * more time
  55      */
  56     private static final int MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS = 256 * 256 - 1024;
  57 
  58     /**
  59      * Detects amount of extra bytes required to allocate a byte array.
  60      * Allocating a byte[n] array takes more then just n bytes in the heap.
  61      * Extra bytes are required to store object reference and the length.
  62      * This amount depends on bitness and other factors.
  63      *
  64      * @return byte[] memory overhead
  65      */
  66     public static int detectByteArrayAllocationOverhead() {
  67 
  68         WhiteBox whiteBox = WhiteBox.getWhiteBox();
  69 
  70         int zeroLengthByteArraySize = (int) whiteBox.getObjectSize(new byte[0]);
  71 
  72         // Since we do not know is there any padding in zeroLengthByteArraySize we cannot just take byte[0] size as overhead
  73         for (int i = 1; i < MAX_PADDING_SIZE + 1; ++i) {
  74             int realAllocationSize = (int) whiteBox.getObjectSize(new byte[i]);
  75             if (realAllocationSize != zeroLengthByteArraySize) {
  76                 // It means we did not have any padding on previous step
  77                 return zeroLengthByteArraySize - (i - 1);
  78             }
  79         }
  80         throw new Error("We cannot find byte[] memory overhead - should not reach here");
  81     }
  82 
  83     /**
  84      * Compiles a java class
  85      *
  86      * @param className class name
  87      * @param root      root directory - where .java and .class files will be put
  88      * @param source    class source
  89      * @throws IOException if cannot write file to specified directory
  90      */
  91     public static void compileClass(String className, Path root, String source) throws IOException {
  92         Path sourceFile = root.resolve(className + ".java");
  93         Files.write(sourceFile, source.getBytes());
  94 
  95         JDKToolLauncher jar = JDKToolLauncher.create("javac")
  96                 .addToolArg("-d")
  97                 .addToolArg(root.toAbsolutePath().toString())
  98                 .addToolArg("-cp")
  99                 .addToolArg(System.getProperty("java.class.path") + File.pathSeparator + root.toAbsolutePath())
 100                 .addToolArg(sourceFile.toAbsolutePath().toString());
 101 
 102         ProcessBuilder pb = new ProcessBuilder(jar.getCommand());
 103         OutputAnalyzer output = new OutputAnalyzer(pb.start());
 104         output.shouldHaveExitValue(0);
 105     }
 106 
 107     /**
 108      * Generates class with specified name, which extends specified class, with specified constructor and specified
 109      * count of long fields
 110      * Generated class will looks like this:
 111      * public class ClassName extends SuperClass {
 112      * ClassName() {super();}
 113      * long f0;
 114      * ...
 115      * long fNNN;
 116      * <p>
 117      * }
 118      *
 119      * @param className   class name
 120      * @param superClass  super class. if null - no extends clause
 121      * @param constructor constructor. if null - no constructor
 122      * @param fieldCount  count of long fields
 123      * @return class text
 124      */
 125     public static String generate(String className, String superClass, String constructor, long fieldCount) {
 126 
 127         StringBuilder builder = new StringBuilder();
 128         builder.append(String.format("public class %s%s {\n", className, superClass == null ? ""
 129                 : " extends " + superClass));
 130 
 131         if (constructor != null) {
 132             builder.append(constructor);
 133         }
 134 
 135         for (int i = 0; i < fieldCount; ++i) {
 136             builder.append(String.format("long f%d;\n", i));
 137         }
 138 
 139         builder.append("}\n");
 140         return builder.toString();
 141     }
 142 
 143     /**
 144      * Generates specified amount of long fields
 145      * Result string will looks like this:
 146      * <p>
 147      * long f0;
 148      * ...
 149      * long fNNN;
 150      *
 151      * @param fieldCount count of long fields
 152      * @return generated fields
 153      */
 154     private static String fieldsGenerator(long fieldCount) {
 155         StringBuilder fieldsBuilder = new StringBuilder();
 156 
 157         for (int i = 0; i < fieldCount; ++i) {
 158             fieldsBuilder.append(String.format("long f%d;\n", i));
 159         }
 160 
 161         return fieldsBuilder.toString();
 162     }
 163 
 164 
 165     /**
 166      * Changes string from enum notation to class notation - i.e. "VERY_SMALL_CAT" to "VerySmallCat"
 167      *
 168      * @param enumName string in enum notation
 169      * @return string in class notation
 170      */
 171     public static String enumNameToClassName(String enumName) {
 172         if (enumName == null) {
 173             return null;
 174         }
 175 
 176         StringBuilder builder = new StringBuilder();
 177         boolean toLowerCase = false;
 178         for (int i = 0; i < enumName.length(); ++i) {
 179             if (enumName.charAt(i) == '_') {
 180                 toLowerCase = false;
 181             } else {
 182                 builder.append(toLowerCase ? String.valueOf(enumName.charAt(i)).toLowerCase() :
 183                         String.valueOf(enumName.charAt(i)));
 184                 toLowerCase = true;
 185             }
 186 
 187         }
 188         return builder.toString();
 189     }
 190 
 191     /**
 192      * Generates and compiles class with instance of specified size and load it in specified class loader
 193      * Generated class will looks like this:
 194      * public class ClassName extends SuperClass {
 195      * long f0;
 196      * ...
 197      * long fNNN;
 198      * <p>
 199      * }
 200      *
 201      * @param classLoader  class loader
 202      * @param className    generated class name
 203      * @param instanceSize size of generated class' instance. Size should be aligned by 8 bytes
 204      * @param workDir      working dir where generated classes are put and compiled
 205      * @param prefix       prefix for service classes (ones we use to create chain of inheritance).
 206      *                     The names will be prefix_1, prefix_2,.., prefix_n
 207      * @return Class object of generated and compiled class loaded in specified class loader
 208      * @throws IOException
 209      * @throws ClassNotFoundException
 210      */
 211     public static Class<?> generateCompileAndLoad(ClassLoader classLoader, String className, long instanceSize,
 212                                                   Path workDir, String prefix)
 213             throws IOException, ClassNotFoundException {
 214 
 215         if (instanceSize % SIZE_OF_LONG != 0L) {
 216             throw new Error(String.format("Test bug: only sizes aligned by 8 bytes are supported and %d was specified",
 217                     instanceSize));
 218         }
 219 
 220         long instanceSizeWithoutObjectHeader = instanceSize - WhiteBox.getWhiteBox().getObjectSize(new Object());
 221 
 222         int generatedClassesCount;
 223         int fieldsInLastClassCount;
 224 
 225         int sizeOfLastFile = (int) (instanceSizeWithoutObjectHeader
 226                 % (MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS * SIZE_OF_LONG));
 227 
 228         if (sizeOfLastFile != 0) {
 229             generatedClassesCount = (int) instanceSizeWithoutObjectHeader
 230                     / (MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS * SIZE_OF_LONG) + 1;
 231             fieldsInLastClassCount = sizeOfLastFile / SIZE_OF_LONG;
 232         } else {
 233             generatedClassesCount = (int) instanceSizeWithoutObjectHeader
 234                     / (MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS * SIZE_OF_LONG);
 235             fieldsInLastClassCount = MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS;
 236         }
 237 
 238         for (int i = 0; i < generatedClassesCount; i++) {
 239             // for the last generated class we use specified class name
 240             String clsName = (i == generatedClassesCount - 1) ? className : prefix + i;
 241 
 242             // If we already have a file with the same name we do not create it again
 243             if (Files.notExists(Paths.get(clsName + ".java"))) {
 244                 Helpers.compileClass(clsName, workDir,
 245                         Helpers.generate(
 246                                 clsName,
 247                                 // for first generated class we don't have 'extends'
 248                                 (i == 0 ? null : prefix + (i - 1)),
 249                                 null,
 250                                 // for the last generated class we use different field count
 251                                 (i == generatedClassesCount - 1) ? fieldsInLastClassCount
 252                                         : MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS));
 253             } else {
 254                 System.out.println("Class " + clsName +
 255                         ".java already exists, skipping class' generation and compilation");
 256             }
 257 
 258         }
 259         return classLoader.loadClass(className);
 260     }
 261 
 262     /**
 263      * Creates a class which instances will be approximately of the requested size.
 264      * This method produces a java source from a class template by substituting values instead of parameters.
 265      * Then the obtained source is compiled.
 266      * Generated class will looks like this:
 267      * classTemplate
 268      * constructorTemplate
 269      * long f0;
 270      * ...
 271      * long fNNN;
 272      * <p>
 273      * }
 274      *
 275      * @param className    generated class name
 276      * @param baseClass    base class
 277      * @param classTemplate class template - the first part of class. ${ClassName} and ${BaseClass} will be replaced
 278      *                      with values from className and baseClass,one entry of ${Fields} will be replaced with
 279      *                      generated long fields. Class template should look like this:
 280      *                      imports;
 281      *                      public class ${ClassName} extends ${BaseClass} {
 282      *                         public ${ClassName}  { some code here;}
 283      *                         some methods
 284      *                         ${Fields}
 285      *
 286      *                      }
 287      * @param constructorTemplate constructor template, ${ClassName} would be replaced on actual class name
 288      * @param instanceSize size of generated class' instance. Size should be aligned by 8 bytes
 289      * @param workDir      working dir where generated classes are put and compiled
 290      * @param prefix       prefix for service classes (ones we use to create chain of inheritance).
 291      *                     The names will be prefix_1, prefix_2,.., prefix_n
 292      * @return Class object of generated and compiled class loaded in specified class loader
 293      * @throws IOException if cannot write or read to workDir
 294      */
 295     public static void generateByTemplateAndCompile(String className, String baseClass, String classTemplate,
 296                                                     String constructorTemplate, long instanceSize, Path workDir,
 297                                                     String prefix) throws IOException {
 298 
 299         if (instanceSize % SIZE_OF_LONG != 0L) {
 300             throw new Error(String.format("Test bug: only sizes aligned by %d bytes are supported and %d was specified",
 301                     SIZE_OF_LONG, instanceSize));
 302         }
 303 
 304         int instanceSizeWithoutObjectHeaderInWords =
 305                 (int) (instanceSize - WhiteBox.getWhiteBox().getObjectSize(new Object())) / SIZE_OF_LONG;
 306 
 307         if (instanceSizeWithoutObjectHeaderInWords <= 0) {
 308             throw new Error(String.format("Test bug: specified instance size is too small - %d."
 309                     + " Cannot generate any classes", instanceSize));
 310         }
 311 
 312         int sizeOfLastFile = instanceSizeWithoutObjectHeaderInWords % MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS;
 313         int generatedClassesCount = instanceSizeWithoutObjectHeaderInWords / MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS;
 314 
 315         // Do all the classes have the maximum number of fields?
 316         int fieldsInLastClassCount;
 317 
 318         if (sizeOfLastFile == 0) {
 319             fieldsInLastClassCount = MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS;
 320         } else {
 321             generatedClassesCount++;
 322             fieldsInLastClassCount = sizeOfLastFile;
 323         }
 324 
 325         // first (generatedClassesCount - 1) classes are just fillers - just long fields and constructor
 326         for (int i = 0; i < generatedClassesCount - 1; i++) {
 327             String clsName = prefix + i;
 328 
 329             Helpers.compileClass(clsName, workDir,
 330                     Helpers.generate(
 331                             clsName,
 332                             // first generated class extends base class
 333                             (i == 0 ? baseClass : prefix + (i - 1)),
 334                             constructorTemplate.replace("${ClassName}", clsName),
 335                             MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS));
 336         }
 337 
 338         // generating last class - the one with specified className
 339         Helpers.compileClass(className, workDir,
 340                 classTemplate.replaceAll("\\$\\{ClassName\\}", className)
 341                         // if no fillers were generated (generatedClassesCount == 1)
 342                         // the last class should extends baseClass
 343                         // otherwise it should extend last generated filler class which name is
 344                         // prefix + (generatedClassesCount - 2)
 345                         // generatedClassesCount is always not smaller than 1
 346                         .replace("${BaseClass}",
 347                                 generatedClassesCount == 1 ? baseClass :
 348                                         prefix + (generatedClassesCount - 2))
 349                         .replace("${Fields}", fieldsGenerator(fieldsInLastClassCount))
 350         );
 351     }
 352 
 353     /**
 354      * Waits until Concurent Mark Cycle finishes
 355      *
 356      * @param wb        Whitebox instance
 357      * @param sleepTime sleep time
 358      */
 359     public static void waitTillCMCFinished(WhiteBox wb, int sleepTime) {
 360         while (wb.g1InConcurrentMark()) {
 361             if (sleepTime > -1) {
 362                 try {
 363                     Thread.sleep(sleepTime);
 364                 } catch (InterruptedException e) {
 365                     System.out.println("Got InterruptedException while waiting for ConcMarkCycle to finish");
 366                     Thread.currentThread().interrupt();
 367                     break;
 368                 }
 369             }
 370         }
 371     }
 372 
 373 }