Home | History | Annotate | Download | only in options
      1 /*
      2  * Copyright (C) 2011 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package com.google.caliper.options;
     16 
     17 import static com.google.common.base.Preconditions.checkNotNull;
     18 import static java.util.concurrent.TimeUnit.MINUTES;
     19 
     20 import com.google.caliper.options.CommandLineParser.Leftovers;
     21 import com.google.caliper.options.CommandLineParser.Option;
     22 import com.google.caliper.util.InvalidCommandException;
     23 import com.google.caliper.util.ShortDuration;
     24 import com.google.common.base.Joiner;
     25 import com.google.common.base.MoreObjects;
     26 import com.google.common.base.Optional;
     27 import com.google.common.base.Splitter;
     28 import com.google.common.collect.ArrayListMultimap;
     29 import com.google.common.collect.ImmutableList;
     30 import com.google.common.collect.ImmutableMap;
     31 import com.google.common.collect.ImmutableSet;
     32 import com.google.common.collect.ImmutableSetMultimap;
     33 import com.google.common.collect.Maps;
     34 import com.google.common.collect.Multimap;
     35 import com.google.common.collect.Ordering;
     36 
     37 import java.io.File;
     38 import java.util.List;
     39 import java.util.Map;
     40 
     41 final class ParsedOptions implements CaliperOptions {
     42 
     43   public static ParsedOptions from(String[] args, boolean requireBenchmarkClassName)
     44       throws InvalidCommandException {
     45     ParsedOptions options = new ParsedOptions(requireBenchmarkClassName);
     46 
     47     CommandLineParser<ParsedOptions> parser = CommandLineParser.forClass(ParsedOptions.class);
     48     try {
     49       parser.parseAndInject(args, options);
     50     } catch (InvalidCommandException e) {
     51       e.setUsage(USAGE);
     52       throw e;
     53     }
     54     return options;
     55   }
     56 
     57   /**
     58    * True if the benchmark class name is expected as the last argument, false if it is not allowed.
     59    */
     60   private final boolean requireBenchmarkClassName;
     61 
     62   private ParsedOptions(boolean requireBenchmarkClassName) {
     63     this.requireBenchmarkClassName = requireBenchmarkClassName;
     64   }
     65 
     66   // --------------------------------------------------------------------------
     67   // Dry run -- simple boolean, needs to be checked in some methods
     68   // --------------------------------------------------------------------------
     69 
     70   @Option({"-n", "--dry-run"})
     71   private boolean dryRun;
     72 
     73   @Override public boolean dryRun() {
     74     return dryRun;
     75   }
     76 
     77   private void dryRunIncompatible(String optionName) throws InvalidCommandException {
     78     // This only works because CLP does field injection before method injection
     79     if (dryRun) {
     80       throw new InvalidCommandException("Option not available in dry-run mode: " + optionName);
     81     }
     82   }
     83 
     84   // --------------------------------------------------------------------------
     85   // Delimiter -- injected early so methods can use it
     86   // --------------------------------------------------------------------------
     87 
     88   @Option({"-d", "--delimiter"})
     89   private String delimiter = ",";
     90 
     91   private ImmutableSet<String> split(String string) {
     92     return ImmutableSet.copyOf(Splitter.on(delimiter).split(string));
     93   }
     94 
     95   // --------------------------------------------------------------------------
     96   // Benchmark method names to run
     97   // --------------------------------------------------------------------------
     98 
     99   private ImmutableSet<String> benchmarkNames = ImmutableSet.of();
    100 
    101   @Option({"-b", "--benchmark"})
    102   private void setBenchmarkNames(String benchmarksString) {
    103     benchmarkNames = split(benchmarksString);
    104   }
    105 
    106   @Override public ImmutableSet<String> benchmarkMethodNames() {
    107     return benchmarkNames;
    108   }
    109 
    110   // --------------------------------------------------------------------------
    111   // Print configuration?
    112   // --------------------------------------------------------------------------
    113 
    114   @Option({"-p", "--print-config"})
    115   private boolean printConfiguration = false;
    116 
    117   @Override public boolean printConfiguration() {
    118     return printConfiguration;
    119   }
    120 
    121   // --------------------------------------------------------------------------
    122   // Trials
    123   // --------------------------------------------------------------------------
    124 
    125   private int trials = 1;
    126 
    127   @Option({"-t", "--trials"})
    128   private void setTrials(int trials) throws InvalidCommandException {
    129     dryRunIncompatible("trials");
    130     if (trials < 1) {
    131       throw new InvalidCommandException("trials must be at least 1: " + trials);
    132     }
    133     this.trials = trials;
    134   }
    135 
    136   @Override public int trialsPerScenario() {
    137     return trials;
    138   }
    139 
    140   // --------------------------------------------------------------------------
    141   // Time limit
    142   // --------------------------------------------------------------------------
    143 
    144   private ShortDuration runTime = ShortDuration.of(5, MINUTES);
    145 
    146   @Option({"-l", "--time-limit"})
    147   private void setTimeLimit(String timeLimitString) throws InvalidCommandException {
    148     try {
    149       this.runTime = ShortDuration.valueOf(timeLimitString);
    150     } catch (IllegalArgumentException e) {
    151       throw new InvalidCommandException("Invalid time limit: " + timeLimitString);
    152     }
    153   }
    154 
    155   @Override public ShortDuration timeLimit() {
    156     return runTime;
    157   }
    158 
    159   // --------------------------------------------------------------------------
    160   // Run name
    161   // --------------------------------------------------------------------------
    162 
    163   private String runName = "";
    164 
    165   @Option({"-r", "--run-name"})
    166   private void setRunName(String runName) {
    167     this.runName = checkNotNull(runName);
    168   }
    169 
    170   @Override public String runName() {
    171     return runName;
    172   }
    173 
    174   // --------------------------------------------------------------------------
    175   // VM specifications
    176   // --------------------------------------------------------------------------
    177 
    178   private ImmutableSet<String> vmNames = ImmutableSet.of();
    179 
    180   @Option({"-m", "--vm"})
    181   private void setVms(String vmsString) throws InvalidCommandException {
    182     dryRunIncompatible("vm");
    183     vmNames = split(vmsString);
    184   }
    185 
    186   @Override public ImmutableSet<String> vmNames() {
    187     return vmNames;
    188   }
    189 
    190   // --------------------------------------------------------------------------
    191   // Measuring instruments to use
    192   // --------------------------------------------------------------------------
    193 
    194   private static final ImmutableSet<String> DEFAULT_INSTRUMENT_NAMES =
    195       new ImmutableSet.Builder<String>()
    196       .add("allocation")
    197       .add("runtime")
    198       .build();
    199 
    200   private ImmutableSet<String> instrumentNames = DEFAULT_INSTRUMENT_NAMES;
    201 
    202   @Option({"-i", "--instrument"})
    203   private void setInstruments(String instrumentsString) {
    204     instrumentNames = split(instrumentsString);
    205   }
    206 
    207   @Override public ImmutableSet<String> instrumentNames() {
    208     return instrumentNames;
    209   }
    210 
    211 // --------------------------------------------------------------------------
    212   // Benchmark parameters
    213   // --------------------------------------------------------------------------
    214 
    215   private Multimap<String, String> mutableUserParameters = ArrayListMultimap.create();
    216 
    217   @Option("-D")
    218   private void addParameterSpec(String nameAndValues) throws InvalidCommandException {
    219     addToMultimap(nameAndValues, mutableUserParameters);
    220   }
    221 
    222   @Override public ImmutableSetMultimap<String, String> userParameters() {
    223     // de-dup values, but keep in order
    224     return new ImmutableSetMultimap.Builder<String, String>()
    225         .orderKeysBy(Ordering.natural())
    226         .putAll(mutableUserParameters)
    227         .build();
    228   }
    229 
    230   // --------------------------------------------------------------------------
    231   // VM arguments
    232   // --------------------------------------------------------------------------
    233 
    234   private Multimap<String, String> mutableVmArguments = ArrayListMultimap.create();
    235 
    236   @Option("-J")
    237   private void addVmArgumentsSpec(String nameAndValues) throws InvalidCommandException {
    238     dryRunIncompatible("-J");
    239     addToMultimap(nameAndValues, mutableVmArguments);
    240   }
    241 
    242   @Override public ImmutableSetMultimap<String, String> vmArguments() {
    243     // de-dup values, but keep in order
    244     return new ImmutableSetMultimap.Builder<String, String>()
    245         .orderKeysBy(Ordering.natural())
    246         .putAll(mutableVmArguments)
    247         .build();
    248   }
    249 
    250   // --------------------------------------------------------------------------
    251   // VM arguments
    252   // --------------------------------------------------------------------------
    253 
    254   private final Map<String, String> mutableConfigPropertes = Maps.newHashMap();
    255 
    256   @Option("-C")
    257   private void addConfigProperty(String nameAndValue) throws InvalidCommandException {
    258     List<String> tokens = splitProperty(nameAndValue);
    259     mutableConfigPropertes.put(tokens.get(0), tokens.get(1));
    260   }
    261 
    262   @Override public ImmutableMap<String, String> configProperties() {
    263     return ImmutableMap.copyOf(mutableConfigPropertes);
    264   }
    265 
    266   // --------------------------------------------------------------------------
    267   // Location of .caliper
    268   // --------------------------------------------------------------------------
    269 
    270   private File caliperDirectory = new File(System.getProperty("user.home"), ".caliper");
    271 
    272   @Option({"--directory"})
    273   private void setCaliperDirectory(String path) {
    274     caliperDirectory = new File(path);
    275   }
    276 
    277   @Override public File caliperDirectory() {
    278     return caliperDirectory;
    279   }
    280 
    281   // --------------------------------------------------------------------------
    282   // Location of config.properties
    283   // --------------------------------------------------------------------------
    284 
    285   private Optional<File> caliperConfigFile = Optional.absent();
    286 
    287   @Option({"-c", "--config"})
    288   private void setCaliperConfigFile(String filename) {
    289     caliperConfigFile = Optional.of(new File(filename));
    290   }
    291 
    292   @Override public File caliperConfigFile() {
    293     return caliperConfigFile.or(new File(caliperDirectory, "config.properties"));
    294   }
    295 
    296 
    297   // --------------------------------------------------------------------------
    298   // Leftover - benchmark class name
    299   // --------------------------------------------------------------------------
    300 
    301   private String benchmarkClassName;
    302 
    303   @Leftovers
    304   private void setLeftovers(ImmutableList<String> leftovers) throws InvalidCommandException {
    305     if (requireBenchmarkClassName) {
    306       if (leftovers.isEmpty()) {
    307         throw new InvalidCommandException("No benchmark class specified");
    308       }
    309       if (leftovers.size() > 1) {
    310         throw new InvalidCommandException("Extra stuff, expected only class name: " + leftovers);
    311       }
    312       this.benchmarkClassName = leftovers.get(0);
    313     } else {
    314       if (!leftovers.isEmpty()) {
    315         throw new InvalidCommandException(
    316             "Extra stuff, did not expect non-option arguments: " + leftovers);
    317       }
    318     }
    319   }
    320 
    321   @Override public String benchmarkClassName() {
    322     return benchmarkClassName;
    323   }
    324 
    325   // --------------------------------------------------------------------------
    326   // Helper methods
    327   // --------------------------------------------------------------------------
    328 
    329   private static List<String> splitProperty(String propertyString) throws InvalidCommandException {
    330     List<String> tokens = ImmutableList.copyOf(Splitter.on('=').limit(2).split(propertyString));
    331     if (tokens.size() != 2) {
    332       throw new InvalidCommandException("no '=' found in: " + propertyString);
    333     }
    334     return tokens;
    335   }
    336 
    337   private void addToMultimap(String nameAndValues, Multimap<String, String> multimap)
    338       throws InvalidCommandException {
    339     List<String> tokens = splitProperty(nameAndValues);
    340     String name = tokens.get(0);
    341     String values = tokens.get(1);
    342 
    343     if (multimap.containsKey(name)) {
    344       throw new InvalidCommandException("multiple parameter sets for: " + name);
    345     }
    346     multimap.putAll(name, split(values));
    347   }
    348 
    349   @Override public String toString() {
    350     return MoreObjects.toStringHelper(this)
    351         .add("benchmarkClassName", this.benchmarkClassName())
    352         .add("benchmarkMethodNames", this.benchmarkMethodNames())
    353         .add("benchmarkParameters", this.userParameters())
    354         .add("dryRun", this.dryRun())
    355         .add("instrumentNames", this.instrumentNames())
    356         .add("vms", this.vmNames())
    357         .add("vmArguments", this.vmArguments())
    358         .add("trials", this.trialsPerScenario())
    359         .add("printConfig", this.printConfiguration())
    360         .add("delimiter", this.delimiter)
    361         .add("caliperConfigFile", this.caliperConfigFile)
    362         .toString();
    363   }
    364 
    365   // --------------------------------------------------------------------------
    366   // Usage
    367   // --------------------------------------------------------------------------
    368 
    369   // TODO(kevinb): kinda nice if CommandLineParser could autogenerate most of this...
    370   // TODO(kevinb): a test could actually check that we don't exceed 79 columns.
    371   private static final ImmutableList<String> USAGE = ImmutableList.of(
    372       "Usage:",
    373       " java com.google.caliper.runner.CaliperMain <benchmark_class_name> [options...]",
    374       "",
    375       "Options:",
    376       " -h, --help         print this message",
    377       " -n, --dry-run      instead of measuring, execute a single rep for each scenario",
    378       "                    in-process",
    379       " -b, --benchmark    comma-separated list of benchmark methods to run; 'foo' is",
    380       "                    an alias for 'timeFoo' (default: all found in class)",
    381       " -m, --vm           comma-separated list of VMs to test on; possible values are",
    382       "                    configured in Caliper's configuration file (default:",
    383       "                    whichever VM caliper itself is running in, only)",
    384       " -i, --instrument   comma-separated list of measuring instruments to use; possible ",
    385       "                    values are configured in Caliper's configuration file ",
    386       "                    (default: \"" + Joiner.on(",").join(DEFAULT_INSTRUMENT_NAMES) + "\")",
    387       " -t, --trials       number of independent trials to peform per benchmark scenario; ",
    388       "                    a positive integer (default: 1)",
    389       " -l, --time-limit   maximum length of time allowed for a single trial; use 0 to allow ",
    390       "                    trials to run indefinitely. (default: 30s) ",
    391       " -r, --run-name     a user-friendly string used to identify the run",
    392       " -p, --print-config print the effective configuration that will be used by Caliper",
    393       " -d, --delimiter    separator used in options that take multiple values (default: ',')",
    394       " -c, --config       location of Caliper's configuration file (default:",
    395       "                    $HOME/.caliper/config.properties)",
    396       " --directory        location of Caliper's configuration and data directory ",
    397       "                    (default: $HOME/.caliper)",
    398       "",
    399       " -Dparam=val1,val2,...",
    400       "     Specifies the values to inject into the 'param' field of the benchmark",
    401       "     class; if multiple values or parameters are specified in this way, caliper",
    402       "     will try all possible combinations.",
    403       "",
    404       // commented out until this flag is fixed
    405       // " -JdisplayName='vm arg list choice 1,vm arg list choice 2,...'",
    406       // "     Specifies alternate sets of VM arguments to pass. As with any variable,",
    407       // "     caliper will test all possible combinations. Example:",
    408       // "     -Jmemory='-Xms32m -Xmx32m,-Xms512m -Xmx512m'",
    409       // "",
    410       " -CconfigProperty=value",
    411       "     Specifies a value for any property that could otherwise be specified in ",
    412       "     $HOME/.caliper/config.properties. Properties specified on the command line",
    413       "     will override those specified in the file.",
    414       "",
    415       "See http://code.google.com/p/caliper/wiki/CommandLineOptions for more details.",
    416       "");
    417 }
    418