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) provideServerSocketService(ServerSocketService impl)64 static Service provideServerSocketService(ServerSocketService impl) { 65 return impl; 66 } 67 68 @Provides(type = Type.SET) provideTrialOutputFactoryService(TrialOutputFactoryService impl)69 static Service provideTrialOutputFactoryService(TrialOutputFactoryService impl) { 70 return impl; 71 } 72 73 @Provides provideTrialOutputFactory(TrialOutputFactoryService impl)74 static TrialOutputFactory provideTrialOutputFactory(TrialOutputFactoryService impl) { 75 return impl; 76 } 77 78 @Provides provideExperimentSelector(FullCartesianExperimentSelector impl)79 static ExperimentSelector provideExperimentSelector(FullCartesianExperimentSelector impl) { 80 return impl; 81 } 82 83 @Provides provideExecutorService(CaliperConfig config)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 providePortNumber(ServerSocketService serverSocketService)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 { value()102 Class<? extends ResultProcessor> value(); 103 } 104 105 @Provides(type = Type.MAP) 106 @ResultProcessorClassKey(OutputFileDumper.class) provideOutputFileDumper(OutputFileDumper impl)107 static ResultProcessor provideOutputFileDumper(OutputFileDumper impl) { 108 return impl; 109 } 110 111 @Provides(type = Type.MAP) 112 @ResultProcessorClassKey(HttpUploader.class) provideHttpUploader(HttpUploader impl)113 static ResultProcessor provideHttpUploader(HttpUploader impl) { 114 return impl; 115 } 116 provideResultProcessors( CaliperConfig config, Map<Class<? extends ResultProcessor>, Provider<ResultProcessor>> availableProcessors)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 provideUuid()131 @Provides static UUID provideUuid() { 132 return UUID.randomUUID(); 133 } 134 135 @Provides @BenchmarkParameters provideBenchmarkParameters( BenchmarkClass benchmarkClass, CaliperOptions options)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 provideHost(EnvironmentGetter environmentGetter)142 static Host provideHost(EnvironmentGetter environmentGetter) { 143 return environmentGetter.getHost(); 144 } 145 146 @Provides @Singleton provideEnvironmentGetter()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 { value()157 Class<? extends Instrument> value(); 158 } 159 160 @Provides(type = Type.MAP) 161 @InstrumentClassKey(ArbitraryMeasurementInstrument.class) provideArbitraryMeasurementInstrument()162 static Instrument provideArbitraryMeasurementInstrument() { 163 return new ArbitraryMeasurementInstrument(); 164 } 165 166 @Provides(type = Type.MAP) 167 @InstrumentClassKey(AllocationInstrument.class) provideAllocationInstrument()168 static Instrument provideAllocationInstrument() { 169 return new AllocationInstrument(); 170 } 171 172 @Provides(type = Type.MAP) 173 @InstrumentClassKey(RuntimeInstrument.class) provideRuntimeInstrument( @anoTimeGranularity ShortDuration nanoTimeGranularity)174 static Instrument provideRuntimeInstrument( 175 @NanoTimeGranularity ShortDuration nanoTimeGranularity) { 176 return new RuntimeInstrument(nanoTimeGranularity); 177 } 178 179 @Provides provideInstruments( CaliperOptions options, final CaliperConfig config, Map<Class<? extends Instrument>, Provider<Instrument>> availableInstruments, Platform platform, @Stderr PrintWriter stderr)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 provideNanoTimeGranularityTester()227 @Provides @Singleton static NanoTimeGranularityTester provideNanoTimeGranularityTester() { 228 return new NanoTimeGranularityTester(); 229 } 230 provideNanoTimeGranularity( NanoTimeGranularityTester tester)231 @Provides @Singleton @NanoTimeGranularity static ShortDuration provideNanoTimeGranularity( 232 NanoTimeGranularityTester tester) { 233 return tester.testNanoTimeGranularity(); 234 } 235 provideInstrumentations(CaliperOptions options, BenchmarkClass benchmarkClass, ImmutableSet<Instrument> instruments)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 findAllBenchmarkMethods(Class<?> benchmarkClass, Instrument instrument)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