blob: 549cdd924e97ee33a7f6f3ca76c0c746f5a011d8 [file] [log] [blame]
/*
* Copyright (C) 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.caliper.platform.jvm;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Thread.currentThread;
import com.google.caliper.platform.Platform;
import com.google.caliper.platform.VirtualMachineException;
import com.google.caliper.util.Util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.util.Collection;
import java.util.Map;
import javax.annotation.Nullable;
/**
* An abstraction of a standard Java Virtual Machine platform.
*/
public final class JvmPlatform extends Platform {
/**
* Some default JVM args to keep worker VMs somewhat predictable.
*/
@VisibleForTesting
public static final ImmutableSet<String> INSTRUMENT_JVM_ARGS = ImmutableSet.of(
// do compilation serially
"-Xbatch",
// make sure compilation doesn't run in parallel with itself
"-XX:CICompilerCount=1",
// ensure the parallel garbage collector
"-XX:+UseParallelGC",
// generate classes or don't, but do it immediately
"-Dsun.reflect.inflationThreshold=0");
private static final ImmutableSet<String> WORKER_PROCESS_ARGS = ImmutableSet.of(
"-XX:+PrintFlagsFinal",
"-XX:+PrintCompilation",
"-XX:+PrintGC");
private static final Predicate<String> PROPERTIES_TO_RETAIN = new Predicate<String>() {
@Override public boolean apply(String input) {
return input.startsWith("java.vm")
|| input.startsWith("java.runtime")
|| input.equals("java.version")
|| input.equals("java.vendor")
|| input.equals("sun.reflect.noInflation")
|| input.equals("sun.reflect.inflationThreshold");
}
};
public JvmPlatform() {
super(Type.JVM);
}
@Override
public File vmExecutable(File javaHome) {
// TODO(gak): support other platforms. This currently supports finding the java executable on
// standard configurations of unix systems and windows.
File bin = new File(javaHome, "bin");
Preconditions.checkState(bin.exists() && bin.isDirectory(),
"Could not find %s under java home %s", bin, javaHome);
File jvm = new File(bin, "java");
if (!jvm.exists() || jvm.isDirectory()) {
jvm = new File(bin, "java.exe");
if (!jvm.exists() || jvm.isDirectory()) {
throw new IllegalStateException(
String.format("Cannot find java binary in %s, looked for java and java.exe", bin));
}
}
return jvm;
}
@Override
public ImmutableSet<String> commonInstrumentVmArgs() {
return INSTRUMENT_JVM_ARGS;
}
@Override
public ImmutableSet<String> workerProcessArgs() {
return WORKER_PROCESS_ARGS;
}
@Override
public String workerClassPath() {
return getClassPath();
}
private static String getClassPath() {
// Use the effective class path in case this is being invoked in an isolated class loader
String classpath =
EffectiveClassPath.getClassPathForClassLoader(currentThread().getContextClassLoader());
return classpath;
}
@Override
public Collection<String> inputArguments() {
return Collections2.filter(ManagementFactory.getRuntimeMXBean().getInputArguments(),
new Predicate<String>() {
@Override
public boolean apply(String input) {
// Exclude the -agentlib:jdwp param which configures the socket debugging protocol.
// If this is set in the parent VM we do not want it to be inherited by the child
// VM. If it is, the child will die immediately on startup because it will fail to
// bind to the debug port (because the parent VM is already bound to it).
return !input.startsWith("-agentlib:jdwp");
}
});
}
@Override
public Predicate<String> vmPropertiesToRetain() {
return PROPERTIES_TO_RETAIN;
}
@Override
public void checkVmProperties(Map<String, String> options) {
checkState(!options.isEmpty());
}
@Override
public File customVmHomeDir(Map<String, String> vmGroupMap, String vmConfigName)
throws VirtualMachineException {
// Configuration can either be:
// vm.<vmConfigName>.home = <homeDir>
// or
// vm.baseDirectory = <baseDir>
// homeDir = <baseDir>/<vmConfigName>
ImmutableMap<String, String> vmMap = Util.subgroupMap(vmGroupMap, vmConfigName);
return getJdkHomeDir(vmGroupMap.get("baseDirectory"), vmMap.get("home"), vmConfigName);
}
// TODO(gak): check that the directory seems to be a jdk home (with a java binary and all of that)
// TODO(gak): make this work with different directory layouts. I'm looking at you OS X...
public static File getJdkHomeDir(@Nullable String baseDirectoryPath,
@Nullable String homeDirPath, String vmConfigName)
throws VirtualMachineException {
if (homeDirPath == null) {
File baseDirectory = getBaseDirectory(baseDirectoryPath);
File homeDir = new File(baseDirectory, vmConfigName);
checkConfiguration(homeDir.isDirectory(), "%s is not a directory", homeDir);
return homeDir;
} else {
File potentialHomeDir = new File(homeDirPath);
if (potentialHomeDir.isAbsolute()) {
checkConfiguration(potentialHomeDir.isDirectory(), "%s is not a directory",
potentialHomeDir);
return potentialHomeDir;
} else {
File baseDirectory = getBaseDirectory(baseDirectoryPath);
File homeDir = new File(baseDirectory, homeDirPath);
checkConfiguration(homeDir.isDirectory(), "%s is not a directory", potentialHomeDir);
return homeDir;
}
}
}
private static File getBaseDirectory(@Nullable String baseDirectoryPath)
throws VirtualMachineException {
if (baseDirectoryPath == null) {
throw new VirtualMachineException(
"must set either a home directory or a base directory");
} else {
File baseDirectory = new File(baseDirectoryPath);
checkConfiguration(baseDirectory.isAbsolute(), "base directory cannot be a relative path");
checkConfiguration(baseDirectory.isDirectory(), "base directory must be a directory");
return baseDirectory;
}
}
private static void checkConfiguration(boolean check, String message)
throws VirtualMachineException {
if (!check) {
throw new VirtualMachineException(message);
}
}
private static void checkConfiguration(boolean check, String messageFormat, Object... args)
throws VirtualMachineException {
if (!check) {
throw new VirtualMachineException(String.format(messageFormat, args));
}
}
}