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