1 /* 2 * Copyright (C) 2015 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.caliper.platform.jvm; 18 19 import static com.google.common.base.Preconditions.checkState; 20 import static java.lang.Thread.currentThread; 21 22 import com.google.caliper.platform.Platform; 23 import com.google.caliper.platform.VirtualMachineException; 24 import com.google.caliper.util.Util; 25 import com.google.common.annotations.VisibleForTesting; 26 import com.google.common.base.Preconditions; 27 import com.google.common.base.Predicate; 28 import com.google.common.collect.Collections2; 29 import com.google.common.collect.ImmutableMap; 30 import com.google.common.collect.ImmutableSet; 31 32 import java.io.File; 33 import java.lang.management.ManagementFactory; 34 import java.util.Collection; 35 import java.util.Map; 36 37 import javax.annotation.Nullable; 38 39 /** 40 * An abstraction of a standard Java Virtual Machine platform. 41 */ 42 public final class JvmPlatform extends Platform { 43 44 /** 45 * Some default JVM args to keep worker VMs somewhat predictable. 46 */ 47 @VisibleForTesting 48 public static final ImmutableSet<String> INSTRUMENT_JVM_ARGS = ImmutableSet.of( 49 // do compilation serially 50 "-Xbatch", 51 // make sure compilation doesn't run in parallel with itself 52 "-XX:CICompilerCount=1", 53 // ensure the parallel garbage collector 54 "-XX:+UseParallelGC", 55 // generate classes or don't, but do it immediately 56 "-Dsun.reflect.inflationThreshold=0"); 57 58 private static final ImmutableSet<String> WORKER_PROCESS_ARGS = ImmutableSet.of( 59 "-XX:+PrintFlagsFinal", 60 "-XX:+PrintCompilation", 61 "-XX:+PrintGC"); 62 63 64 private static final Predicate<String> PROPERTIES_TO_RETAIN = new Predicate<String>() { 65 @Override public boolean apply(String input) { 66 return input.startsWith("java.vm") 67 || input.startsWith("java.runtime") 68 || input.equals("java.version") 69 || input.equals("java.vendor") 70 || input.equals("sun.reflect.noInflation") 71 || input.equals("sun.reflect.inflationThreshold"); 72 } 73 }; 74 JvmPlatform()75 public JvmPlatform() { 76 super(Type.JVM); 77 } 78 79 @Override vmExecutable(File javaHome)80 public File vmExecutable(File javaHome) { 81 // TODO(gak): support other platforms. This currently supports finding the java executable on 82 // standard configurations of unix systems and windows. 83 File bin = new File(javaHome, "bin"); 84 Preconditions.checkState(bin.exists() && bin.isDirectory(), 85 "Could not find %s under java home %s", bin, javaHome); 86 File jvm = new File(bin, "java"); 87 if (!jvm.exists() || jvm.isDirectory()) { 88 jvm = new File(bin, "java.exe"); 89 if (!jvm.exists() || jvm.isDirectory()) { 90 throw new IllegalStateException( 91 String.format("Cannot find java binary in %s, looked for java and java.exe", bin)); 92 } 93 } 94 95 return jvm; 96 } 97 98 @Override commonInstrumentVmArgs()99 public ImmutableSet<String> commonInstrumentVmArgs() { 100 return INSTRUMENT_JVM_ARGS; 101 } 102 103 @Override workerProcessArgs()104 public ImmutableSet<String> workerProcessArgs() { 105 return WORKER_PROCESS_ARGS; 106 } 107 108 @Override workerClassPath()109 public String workerClassPath() { 110 return getClassPath(); 111 } 112 getClassPath()113 private static String getClassPath() { 114 // Use the effective class path in case this is being invoked in an isolated class loader 115 String classpath = 116 EffectiveClassPath.getClassPathForClassLoader(currentThread().getContextClassLoader()); 117 return classpath; 118 } 119 120 @Override inputArguments()121 public Collection<String> inputArguments() { 122 return Collections2.filter(ManagementFactory.getRuntimeMXBean().getInputArguments(), 123 new Predicate<String>() { 124 @Override 125 public boolean apply(String input) { 126 // Exclude the -agentlib:jdwp param which configures the socket debugging protocol. 127 // If this is set in the parent VM we do not want it to be inherited by the child 128 // VM. If it is, the child will die immediately on startup because it will fail to 129 // bind to the debug port (because the parent VM is already bound to it). 130 return !input.startsWith("-agentlib:jdwp"); 131 } 132 }); 133 } 134 135 @Override 136 public Predicate<String> vmPropertiesToRetain() { 137 return PROPERTIES_TO_RETAIN; 138 } 139 140 @Override 141 public void checkVmProperties(Map<String, String> options) { 142 checkState(!options.isEmpty()); 143 } 144 145 @Override 146 public File customVmHomeDir(Map<String, String> vmGroupMap, String vmConfigName) 147 throws VirtualMachineException { 148 // Configuration can either be: 149 // vm.<vmConfigName>.home = <homeDir> 150 // or 151 // vm.baseDirectory = <baseDir> 152 // homeDir = <baseDir>/<vmConfigName> 153 ImmutableMap<String, String> vmMap = Util.subgroupMap(vmGroupMap, vmConfigName); 154 return getJdkHomeDir(vmGroupMap.get("baseDirectory"), vmMap.get("home"), vmConfigName); 155 } 156 157 // TODO(gak): check that the directory seems to be a jdk home (with a java binary and all of that) 158 // TODO(gak): make this work with different directory layouts. I'm looking at you OS X... 159 public static File getJdkHomeDir(@Nullable String baseDirectoryPath, 160 @Nullable String homeDirPath, String vmConfigName) 161 throws VirtualMachineException { 162 if (homeDirPath == null) { 163 File baseDirectory = getBaseDirectory(baseDirectoryPath); 164 File homeDir = new File(baseDirectory, vmConfigName); 165 checkConfiguration(homeDir.isDirectory(), "%s is not a directory", homeDir); 166 return homeDir; 167 } else { 168 File potentialHomeDir = new File(homeDirPath); 169 if (potentialHomeDir.isAbsolute()) { 170 checkConfiguration(potentialHomeDir.isDirectory(), "%s is not a directory", 171 potentialHomeDir); 172 return potentialHomeDir; 173 } else { 174 File baseDirectory = getBaseDirectory(baseDirectoryPath); 175 File homeDir = new File(baseDirectory, homeDirPath); 176 checkConfiguration(homeDir.isDirectory(), "%s is not a directory", potentialHomeDir); 177 return homeDir; 178 } 179 } 180 } 181 182 private static File getBaseDirectory(@Nullable String baseDirectoryPath) 183 throws VirtualMachineException { 184 if (baseDirectoryPath == null) { 185 throw new VirtualMachineException( 186 "must set either a home directory or a base directory"); 187 } else { 188 File baseDirectory = new File(baseDirectoryPath); 189 checkConfiguration(baseDirectory.isAbsolute(), "base directory cannot be a relative path"); 190 checkConfiguration(baseDirectory.isDirectory(), "base directory must be a directory"); 191 return baseDirectory; 192 } 193 } 194 195 private static void checkConfiguration(boolean check, String message) 196 throws VirtualMachineException { 197 if (!check) { 198 throw new VirtualMachineException(message); 199 } 200 } 201 202 private static void checkConfiguration(boolean check, String messageFormat, Object... args) 203 throws VirtualMachineException { 204 if (!check) { 205 throw new VirtualMachineException(String.format(messageFormat, args)); 206 } 207 } 208 } 209