Home | History | Annotate | Download | only in runner
      1 /*
      2  * Copyright (C) 2012 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.runner;
     18 
     19 import com.google.caliper.api.ResultProcessor;
     20 import com.google.caliper.config.CaliperConfig;
     21 import com.google.caliper.config.InstrumentConfig;
     22 import com.google.caliper.model.Host;
     23 import com.google.caliper.options.CaliperOptions;
     24 import com.google.caliper.platform.Platform;
     25 import com.google.caliper.runner.Instrument.Instrumentation;
     26 import com.google.caliper.util.InvalidCommandException;
     27 import com.google.caliper.util.ShortDuration;
     28 import com.google.caliper.util.Stderr;
     29 import com.google.caliper.util.Util;
     30 import com.google.common.base.Function;
     31 import com.google.common.collect.ImmutableSet;
     32 import com.google.common.collect.ImmutableSetMultimap;
     33 import com.google.common.collect.ImmutableSortedSet;
     34 import com.google.common.collect.Ordering;
     35 import com.google.common.util.concurrent.ListeningExecutorService;
     36 import com.google.common.util.concurrent.MoreExecutors;
     37 import com.google.common.util.concurrent.Service;
     38 
     39 import dagger.MapKey;
     40 import dagger.Module;
     41 import dagger.Provides;
     42 import dagger.Provides.Type;
     43 
     44 import java.io.PrintWriter;
     45 import java.lang.reflect.Method;
     46 import java.util.HashSet;
     47 import java.util.Map;
     48 import java.util.Set;
     49 import java.util.TreeSet;
     50 import java.util.UUID;
     51 import java.util.concurrent.Executors;
     52 
     53 import javax.inject.Provider;
     54 import javax.inject.Singleton;
     55 
     56 /**
     57  * Configures a {@link CaliperRun} that performs experiments.
     58  */
     59 @Module
     60 final class ExperimentingRunnerModule {
     61   private static final String RUNNER_MAX_PARALLELISM_OPTION = "runner.maxParallelism";
     62 
     63   @Provides(type = Type.SET)
     64   static Service provideServerSocketService(ServerSocketService impl) {
     65     return impl;
     66   }
     67 
     68   @Provides(type = Type.SET)
     69   static Service provideTrialOutputFactoryService(TrialOutputFactoryService impl) {
     70     return impl;
     71   }
     72 
     73   @Provides
     74   static TrialOutputFactory provideTrialOutputFactory(TrialOutputFactoryService impl) {
     75     return impl;
     76   }
     77 
     78   @Provides
     79   static ExperimentSelector provideExperimentSelector(FullCartesianExperimentSelector impl) {
     80     return impl;
     81   }
     82 
     83   @Provides
     84   static ListeningExecutorService provideExecutorService(CaliperConfig config) {
     85     int poolSize = Integer.parseInt(config.properties().get(RUNNER_MAX_PARALLELISM_OPTION));
     86     return MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(poolSize));
     87   }
     88 
     89   @LocalPort
     90   @Provides
     91   static int providePortNumber(ServerSocketService serverSocketService) {
     92     return serverSocketService.getPort();
     93   }
     94 
     95   /**
     96    * Specifies the {@link Class} object to use as a key in the map of available
     97    * {@link ResultProcessor result processors} passed to
     98    * {@link #provideResultProcessors(CaliperConfig, Map)}.
     99    */
    100   @MapKey(unwrapValue = true)
    101   public @interface ResultProcessorClassKey {
    102     Class<? extends ResultProcessor> value();
    103   }
    104 
    105   @Provides(type = Type.MAP)
    106   @ResultProcessorClassKey(OutputFileDumper.class)
    107   static ResultProcessor provideOutputFileDumper(OutputFileDumper impl) {
    108     return impl;
    109   }
    110 
    111   @Provides(type = Type.MAP)
    112   @ResultProcessorClassKey(HttpUploader.class)
    113   static ResultProcessor provideHttpUploader(HttpUploader impl) {
    114     return impl;
    115   }
    116 
    117   @Provides static ImmutableSet<ResultProcessor> provideResultProcessors(
    118       CaliperConfig config,
    119       Map<Class<? extends ResultProcessor>, Provider<ResultProcessor>> availableProcessors) {
    120     ImmutableSet.Builder<ResultProcessor> builder = ImmutableSet.builder();
    121     for (Class<? extends ResultProcessor> processorClass : config.getConfiguredResultProcessors()) {
    122       Provider<ResultProcessor> resultProcessorProvider = availableProcessors.get(processorClass);
    123       ResultProcessor resultProcessor = resultProcessorProvider == null
    124           ? ResultProcessorCreator.createResultProcessor(processorClass)
    125           : resultProcessorProvider.get();
    126       builder.add(resultProcessor);
    127     }
    128     return builder.build();
    129   }
    130 
    131   @Provides static UUID provideUuid() {
    132     return UUID.randomUUID();
    133   }
    134 
    135   @Provides @BenchmarkParameters
    136   static ImmutableSetMultimap<String, String> provideBenchmarkParameters(
    137       BenchmarkClass benchmarkClass, CaliperOptions options) throws InvalidBenchmarkException {
    138     return benchmarkClass.userParameters().fillInDefaultsFor(options.userParameters());
    139   }
    140 
    141   @Provides @Singleton
    142   static Host provideHost(EnvironmentGetter environmentGetter) {
    143     return environmentGetter.getHost();
    144   }
    145 
    146   @Provides @Singleton
    147   static EnvironmentGetter provideEnvironmentGetter() {
    148     return new EnvironmentGetter();
    149   }
    150 
    151   /**
    152    * Specifies the {@link Class} object to use as a key in the map of available
    153    * {@link Instrument instruments} passed to {@link #provideInstruments},
    154    */
    155   @MapKey(unwrapValue = true)
    156   public @interface InstrumentClassKey {
    157     Class<? extends Instrument> value();
    158   }
    159 
    160   @Provides(type = Type.MAP)
    161   @InstrumentClassKey(ArbitraryMeasurementInstrument.class)
    162   static Instrument provideArbitraryMeasurementInstrument() {
    163     return new ArbitraryMeasurementInstrument();
    164   }
    165 
    166   @Provides(type = Type.MAP)
    167   @InstrumentClassKey(AllocationInstrument.class)
    168   static Instrument provideAllocationInstrument() {
    169     return new AllocationInstrument();
    170   }
    171 
    172   @Provides(type = Type.MAP)
    173   @InstrumentClassKey(RuntimeInstrument.class)
    174   static Instrument provideRuntimeInstrument(
    175       @NanoTimeGranularity ShortDuration nanoTimeGranularity) {
    176     return new RuntimeInstrument(nanoTimeGranularity);
    177   }
    178 
    179   @Provides
    180   static ImmutableSet<Instrument> provideInstruments(
    181       CaliperOptions options,
    182       final CaliperConfig config,
    183       Map<Class<? extends Instrument>, Provider<Instrument>> availableInstruments,
    184       Platform platform,
    185       @Stderr PrintWriter stderr)
    186       throws InvalidCommandException {
    187 
    188     ImmutableSet.Builder<Instrument> builder = ImmutableSet.builder();
    189     ImmutableSet<String> configuredInstruments = config.getConfiguredInstruments();
    190     for (final String instrumentName : options.instrumentNames()) {
    191       if (!configuredInstruments.contains(instrumentName)) {
    192         throw new InvalidCommandException("%s is not a configured instrument (%s). "
    193             + "use --print-config to see the configured instruments.",
    194             instrumentName, configuredInstruments);
    195       }
    196       final InstrumentConfig instrumentConfig = config.getInstrumentConfig(instrumentName);
    197       String className = instrumentConfig.className();
    198       try {
    199         Class<? extends Instrument> clazz =
    200             Util.lenientClassForName(className).asSubclass(Instrument.class);
    201         Provider<Instrument> instrumentProvider = availableInstruments.get(clazz);
    202         if (instrumentProvider == null) {
    203           throw new InvalidInstrumentException("Instrument %s not supported", className);
    204         }
    205 
    206         // Make sure that the instrument is supported on the platform.
    207         if (platform.supports(clazz)) {
    208           Instrument instrument = instrumentProvider.get();
    209           InstrumentInjectorModule injectorModule =
    210               new InstrumentInjectorModule(instrumentConfig, instrumentName);
    211           InstrumentComponent instrumentComponent = DaggerInstrumentComponent.builder()
    212               .instrumentInjectorModule(injectorModule)
    213               .build();
    214           instrumentComponent.injectInstrument(instrument);
    215           builder.add(instrument);
    216         } else {
    217           stderr.format("Instrument %s not supported on %s, ignoring\n",
    218               className, platform.name());
    219         }
    220       } catch (ClassNotFoundException e) {
    221         throw new InvalidCommandException("Cannot find instrument class '%s'", className);
    222       }
    223     }
    224     return builder.build();
    225   }
    226 
    227   @Provides @Singleton static NanoTimeGranularityTester provideNanoTimeGranularityTester() {
    228     return new NanoTimeGranularityTester();
    229   }
    230 
    231   @Provides @Singleton @NanoTimeGranularity static ShortDuration provideNanoTimeGranularity(
    232       NanoTimeGranularityTester tester) {
    233     return tester.testNanoTimeGranularity();
    234   }
    235 
    236   @Provides static ImmutableSet<Instrumentation> provideInstrumentations(CaliperOptions options,
    237       BenchmarkClass benchmarkClass, ImmutableSet<Instrument> instruments)
    238           throws InvalidBenchmarkException {
    239     ImmutableSet.Builder<Instrumentation> builder = ImmutableSet.builder();
    240     ImmutableSet<String> benchmarkMethodNames = options.benchmarkMethodNames();
    241     Set<String> unusedBenchmarkNames = new HashSet<String>(benchmarkMethodNames);
    242     for (Instrument instrument : instruments) {
    243       for (Method method : findAllBenchmarkMethods(benchmarkClass.benchmarkClass(), instrument)) {
    244         if (benchmarkMethodNames.isEmpty() || benchmarkMethodNames.contains(method.getName())) {
    245           builder.add(instrument.createInstrumentation(method));
    246           unusedBenchmarkNames.remove(method.getName());
    247         }
    248       }
    249     }
    250     if (!unusedBenchmarkNames.isEmpty()) {
    251       throw new InvalidBenchmarkException(
    252           "Invalid benchmark method(s) specified in options: " + unusedBenchmarkNames);
    253     }
    254     return builder.build();
    255   }
    256 
    257   private static ImmutableSortedSet<Method> findAllBenchmarkMethods(Class<?> benchmarkClass,
    258       Instrument instrument) throws InvalidBenchmarkException {
    259     ImmutableSortedSet.Builder<Method> result = ImmutableSortedSet.orderedBy(
    260         Ordering.natural().onResultOf(new Function<Method, String>() {
    261           @Override public String apply(Method method) {
    262             return method.getName();
    263           }
    264         }));
    265     Set<String> benchmarkMethodNames = new HashSet<String>();
    266     Set<String> overloadedMethodNames = new TreeSet<String>();
    267     for (Method method : benchmarkClass.getDeclaredMethods()) {
    268       if (instrument.isBenchmarkMethod(method)) {
    269         method.setAccessible(true);
    270         result.add(method);
    271         if (!benchmarkMethodNames.add(method.getName())) {
    272           overloadedMethodNames.add(method.getName());
    273         }
    274       }
    275     }
    276     if (!overloadedMethodNames.isEmpty()) {
    277       throw new InvalidBenchmarkException(
    278           "Overloads are disallowed for benchmark methods, found overloads of %s in benchmark %s",
    279           overloadedMethodNames,
    280           benchmarkClass);
    281     }
    282     return result.build();
    283   }
    284 }
    285