blob: 43dd8f70166524afe56357a64cd50c16b42d68dd [file] [log] [blame]
/*
* Copyright (C) 2011 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.options;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.MINUTES;
import com.google.caliper.options.CommandLineParser.Leftovers;
import com.google.caliper.options.CommandLineParser.Option;
import com.google.caliper.util.InvalidCommandException;
import com.google.caliper.util.ShortDuration;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import java.io.File;
import java.util.List;
import java.util.Map;
final class ParsedOptions implements CaliperOptions {
public static ParsedOptions from(String[] args, boolean requireBenchmarkClassName)
throws InvalidCommandException {
ParsedOptions options = new ParsedOptions(requireBenchmarkClassName);
CommandLineParser<ParsedOptions> parser = CommandLineParser.forClass(ParsedOptions.class);
try {
parser.parseAndInject(args, options);
} catch (InvalidCommandException e) {
e.setUsage(USAGE);
throw e;
}
return options;
}
/**
* True if the benchmark class name is expected as the last argument, false if it is not allowed.
*/
private final boolean requireBenchmarkClassName;
private ParsedOptions(boolean requireBenchmarkClassName) {
this.requireBenchmarkClassName = requireBenchmarkClassName;
}
// --------------------------------------------------------------------------
// Dry run -- simple boolean, needs to be checked in some methods
// --------------------------------------------------------------------------
@Option({"-n", "--dry-run"})
private boolean dryRun;
@Override public boolean dryRun() {
return dryRun;
}
private void dryRunIncompatible(String optionName) throws InvalidCommandException {
// This only works because CLP does field injection before method injection
if (dryRun) {
throw new InvalidCommandException("Option not available in dry-run mode: " + optionName);
}
}
// --------------------------------------------------------------------------
// Delimiter -- injected early so methods can use it
// --------------------------------------------------------------------------
@Option({"-d", "--delimiter"})
private String delimiter = ",";
private ImmutableSet<String> split(String string) {
return ImmutableSet.copyOf(Splitter.on(delimiter).split(string));
}
// --------------------------------------------------------------------------
// Benchmark method names to run
// --------------------------------------------------------------------------
private ImmutableSet<String> benchmarkNames = ImmutableSet.of();
@Option({"-b", "--benchmark"})
private void setBenchmarkNames(String benchmarksString) {
benchmarkNames = split(benchmarksString);
}
@Override public ImmutableSet<String> benchmarkMethodNames() {
return benchmarkNames;
}
// --------------------------------------------------------------------------
// Print configuration?
// --------------------------------------------------------------------------
@Option({"-p", "--print-config"})
private boolean printConfiguration = false;
@Override public boolean printConfiguration() {
return printConfiguration;
}
// --------------------------------------------------------------------------
// Trials
// --------------------------------------------------------------------------
private int trials = 1;
@Option({"-t", "--trials"})
private void setTrials(int trials) throws InvalidCommandException {
dryRunIncompatible("trials");
if (trials < 1) {
throw new InvalidCommandException("trials must be at least 1: " + trials);
}
this.trials = trials;
}
@Override public int trialsPerScenario() {
return trials;
}
// --------------------------------------------------------------------------
// Time limit
// --------------------------------------------------------------------------
private ShortDuration runTime = ShortDuration.of(5, MINUTES);
@Option({"-l", "--time-limit"})
private void setTimeLimit(String timeLimitString) throws InvalidCommandException {
try {
this.runTime = ShortDuration.valueOf(timeLimitString);
} catch (IllegalArgumentException e) {
throw new InvalidCommandException("Invalid time limit: " + timeLimitString);
}
}
@Override public ShortDuration timeLimit() {
return runTime;
}
// --------------------------------------------------------------------------
// Run name
// --------------------------------------------------------------------------
private String runName = "";
@Option({"-r", "--run-name"})
private void setRunName(String runName) {
this.runName = checkNotNull(runName);
}
@Override public String runName() {
return runName;
}
// --------------------------------------------------------------------------
// VM specifications
// --------------------------------------------------------------------------
private ImmutableSet<String> vmNames = ImmutableSet.of();
@Option({"-m", "--vm"})
private void setVms(String vmsString) throws InvalidCommandException {
dryRunIncompatible("vm");
vmNames = split(vmsString);
}
@Override public ImmutableSet<String> vmNames() {
return vmNames;
}
// --------------------------------------------------------------------------
// Measuring instruments to use
// --------------------------------------------------------------------------
private static final ImmutableSet<String> DEFAULT_INSTRUMENT_NAMES =
new ImmutableSet.Builder<String>()
.add("allocation")
.add("runtime")
.build();
private ImmutableSet<String> instrumentNames = DEFAULT_INSTRUMENT_NAMES;
@Option({"-i", "--instrument"})
private void setInstruments(String instrumentsString) {
instrumentNames = split(instrumentsString);
}
@Override public ImmutableSet<String> instrumentNames() {
return instrumentNames;
}
// --------------------------------------------------------------------------
// Benchmark parameters
// --------------------------------------------------------------------------
private Multimap<String, String> mutableUserParameters = ArrayListMultimap.create();
@Option("-D")
private void addParameterSpec(String nameAndValues) throws InvalidCommandException {
addToMultimap(nameAndValues, mutableUserParameters);
}
@Override public ImmutableSetMultimap<String, String> userParameters() {
// de-dup values, but keep in order
return new ImmutableSetMultimap.Builder<String, String>()
.orderKeysBy(Ordering.natural())
.putAll(mutableUserParameters)
.build();
}
// --------------------------------------------------------------------------
// VM arguments
// --------------------------------------------------------------------------
private Multimap<String, String> mutableVmArguments = ArrayListMultimap.create();
@Option("-J")
private void addVmArgumentsSpec(String nameAndValues) throws InvalidCommandException {
dryRunIncompatible("-J");
addToMultimap(nameAndValues, mutableVmArguments);
}
@Override public ImmutableSetMultimap<String, String> vmArguments() {
// de-dup values, but keep in order
return new ImmutableSetMultimap.Builder<String, String>()
.orderKeysBy(Ordering.natural())
.putAll(mutableVmArguments)
.build();
}
// --------------------------------------------------------------------------
// VM arguments
// --------------------------------------------------------------------------
private final Map<String, String> mutableConfigPropertes = Maps.newHashMap();
@Option("-C")
private void addConfigProperty(String nameAndValue) throws InvalidCommandException {
List<String> tokens = splitProperty(nameAndValue);
mutableConfigPropertes.put(tokens.get(0), tokens.get(1));
}
@Override public ImmutableMap<String, String> configProperties() {
return ImmutableMap.copyOf(mutableConfigPropertes);
}
// --------------------------------------------------------------------------
// Location of .caliper
// --------------------------------------------------------------------------
private File caliperDirectory = new File(System.getProperty("user.home"), ".caliper");
@Option({"--directory"})
private void setCaliperDirectory(String path) {
caliperDirectory = new File(path);
}
@Override public File caliperDirectory() {
return caliperDirectory;
}
// --------------------------------------------------------------------------
// Location of config.properties
// --------------------------------------------------------------------------
private Optional<File> caliperConfigFile = Optional.absent();
@Option({"-c", "--config"})
private void setCaliperConfigFile(String filename) {
caliperConfigFile = Optional.of(new File(filename));
}
@Override public File caliperConfigFile() {
return caliperConfigFile.or(new File(caliperDirectory, "config.properties"));
}
// --------------------------------------------------------------------------
// Leftover - benchmark class name
// --------------------------------------------------------------------------
private String benchmarkClassName;
@Leftovers
private void setLeftovers(ImmutableList<String> leftovers) throws InvalidCommandException {
if (requireBenchmarkClassName) {
if (leftovers.isEmpty()) {
throw new InvalidCommandException("No benchmark class specified");
}
if (leftovers.size() > 1) {
throw new InvalidCommandException("Extra stuff, expected only class name: " + leftovers);
}
this.benchmarkClassName = leftovers.get(0);
} else {
if (!leftovers.isEmpty()) {
throw new InvalidCommandException(
"Extra stuff, did not expect non-option arguments: " + leftovers);
}
}
}
@Override public String benchmarkClassName() {
return benchmarkClassName;
}
// --------------------------------------------------------------------------
// Helper methods
// --------------------------------------------------------------------------
private static List<String> splitProperty(String propertyString) throws InvalidCommandException {
List<String> tokens = ImmutableList.copyOf(Splitter.on('=').limit(2).split(propertyString));
if (tokens.size() != 2) {
throw new InvalidCommandException("no '=' found in: " + propertyString);
}
return tokens;
}
private void addToMultimap(String nameAndValues, Multimap<String, String> multimap)
throws InvalidCommandException {
List<String> tokens = splitProperty(nameAndValues);
String name = tokens.get(0);
String values = tokens.get(1);
if (multimap.containsKey(name)) {
throw new InvalidCommandException("multiple parameter sets for: " + name);
}
multimap.putAll(name, split(values));
}
@Override public String toString() {
return MoreObjects.toStringHelper(this)
.add("benchmarkClassName", this.benchmarkClassName())
.add("benchmarkMethodNames", this.benchmarkMethodNames())
.add("benchmarkParameters", this.userParameters())
.add("dryRun", this.dryRun())
.add("instrumentNames", this.instrumentNames())
.add("vms", this.vmNames())
.add("vmArguments", this.vmArguments())
.add("trials", this.trialsPerScenario())
.add("printConfig", this.printConfiguration())
.add("delimiter", this.delimiter)
.add("caliperConfigFile", this.caliperConfigFile)
.toString();
}
// --------------------------------------------------------------------------
// Usage
// --------------------------------------------------------------------------
// TODO(kevinb): kinda nice if CommandLineParser could autogenerate most of this...
// TODO(kevinb): a test could actually check that we don't exceed 79 columns.
private static final ImmutableList<String> USAGE = ImmutableList.of(
"Usage:",
" java com.google.caliper.runner.CaliperMain <benchmark_class_name> [options...]",
"",
"Options:",
" -h, --help print this message",
" -n, --dry-run instead of measuring, execute a single rep for each scenario",
" in-process",
" -b, --benchmark comma-separated list of benchmark methods to run; 'foo' is",
" an alias for 'timeFoo' (default: all found in class)",
" -m, --vm comma-separated list of VMs to test on; possible values are",
" configured in Caliper's configuration file (default:",
" whichever VM caliper itself is running in, only)",
" -i, --instrument comma-separated list of measuring instruments to use; possible ",
" values are configured in Caliper's configuration file ",
" (default: \"" + Joiner.on(",").join(DEFAULT_INSTRUMENT_NAMES) + "\")",
" -t, --trials number of independent trials to peform per benchmark scenario; ",
" a positive integer (default: 1)",
" -l, --time-limit maximum length of time allowed for a single trial; use 0 to allow ",
" trials to run indefinitely. (default: 30s) ",
" -r, --run-name a user-friendly string used to identify the run",
" -p, --print-config print the effective configuration that will be used by Caliper",
" -d, --delimiter separator used in options that take multiple values (default: ',')",
" -c, --config location of Caliper's configuration file (default:",
" $HOME/.caliper/config.properties)",
" --directory location of Caliper's configuration and data directory ",
" (default: $HOME/.caliper)",
"",
" -Dparam=val1,val2,...",
" Specifies the values to inject into the 'param' field of the benchmark",
" class; if multiple values or parameters are specified in this way, caliper",
" will try all possible combinations.",
"",
// commented out until this flag is fixed
// " -JdisplayName='vm arg list choice 1,vm arg list choice 2,...'",
// " Specifies alternate sets of VM arguments to pass. As with any variable,",
// " caliper will test all possible combinations. Example:",
// " -Jmemory='-Xms32m -Xmx32m,-Xms512m -Xmx512m'",
// "",
" -CconfigProperty=value",
" Specifies a value for any property that could otherwise be specified in ",
" $HOME/.caliper/config.properties. Properties specified on the command line",
" will override those specified in the file.",
"",
"See http://code.google.com/p/caliper/wiki/CommandLineOptions for more details.",
"");
}