1/*
2 * Copyright (C) 2011 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
17package com.google.caliper.runner;
18
19import static com.google.caliper.runner.CommonInstrumentOptions.GC_BEFORE_EACH_OPTION;
20import static com.google.common.base.Throwables.propagateIfInstanceOf;
21
22import com.google.caliper.api.SkipThisScenarioException;
23import com.google.caliper.bridge.AbstractLogMessageVisitor;
24import com.google.caliper.bridge.StopMeasurementLogMessage;
25import com.google.caliper.model.ArbitraryMeasurement;
26import com.google.caliper.model.Measurement;
27import com.google.caliper.platform.Platform;
28import com.google.caliper.platform.SupportedPlatform;
29import com.google.caliper.util.Util;
30import com.google.caliper.worker.ArbitraryMeasurementWorker;
31import com.google.caliper.worker.Worker;
32import com.google.common.base.Optional;
33import com.google.common.collect.ImmutableList;
34import com.google.common.collect.ImmutableMap;
35import com.google.common.collect.ImmutableSet;
36import com.google.common.collect.Iterables;
37
38import java.lang.reflect.InvocationTargetException;
39import java.lang.reflect.Method;
40
41/**
42 * Instrument for taking an arbitrary measurement. When using this instrument, the benchmark code
43 * itself returns the value. See {@link ArbitraryMeasurement}.
44 */
45@SupportedPlatform(Platform.Type.JVM)
46public final class ArbitraryMeasurementInstrument extends Instrument {
47  @Override public boolean isBenchmarkMethod(Method method) {
48    return method.isAnnotationPresent(ArbitraryMeasurement.class);
49  }
50
51  @Override
52  public Instrumentation createInstrumentation(Method benchmarkMethod)
53      throws InvalidBenchmarkException {
54    if (benchmarkMethod.getParameterTypes().length != 0) {
55      throw new InvalidBenchmarkException(
56          "Arbitrary measurement methods should take no parameters: " + benchmarkMethod.getName());
57    }
58
59    if (benchmarkMethod.getReturnType() != double.class) {
60      throw new InvalidBenchmarkException(
61          "Arbitrary measurement methods must have a return type of double: "
62              + benchmarkMethod.getName());
63    }
64
65    // Static technically doesn't hurt anything, but it's just the completely wrong idea
66    if (Util.isStatic(benchmarkMethod)) {
67      throw new InvalidBenchmarkException(
68          "Arbitrary measurement methods must not be static: " + benchmarkMethod.getName());
69    }
70
71    if (!Util.isPublic(benchmarkMethod)) {
72      throw new InvalidBenchmarkException(
73          "Arbitrary measurement methods must be public: " + benchmarkMethod.getName());
74    }
75
76    return new ArbitraryMeasurementInstrumentation(benchmarkMethod);
77  }
78
79  @Override public TrialSchedulingPolicy schedulingPolicy() {
80    // We could allow it here but in general it would depend on the particular measurement so it
81    // should probably be configured by the user.  For now we just disable it.
82    return TrialSchedulingPolicy.SERIAL;
83  }
84
85  private final class ArbitraryMeasurementInstrumentation extends Instrumentation {
86    protected ArbitraryMeasurementInstrumentation(Method benchmarkMethod) {
87      super(benchmarkMethod);
88    }
89
90    @Override
91    public void dryRun(Object benchmark) throws InvalidBenchmarkException {
92      try {
93        benchmarkMethod.invoke(benchmark);
94      } catch (IllegalAccessException impossible) {
95        throw new AssertionError(impossible);
96      } catch (InvocationTargetException e) {
97        Throwable userException = e.getCause();
98        propagateIfInstanceOf(userException, SkipThisScenarioException.class);
99        throw new UserCodeException(userException);
100      }
101    }
102
103    @Override
104    public Class<? extends Worker> workerClass() {
105      return ArbitraryMeasurementWorker.class;
106    }
107
108    @Override public ImmutableMap<String, String> workerOptions() {
109      return ImmutableMap.of(GC_BEFORE_EACH_OPTION, options.get(GC_BEFORE_EACH_OPTION));
110    }
111
112    @Override
113    MeasurementCollectingVisitor getMeasurementCollectingVisitor() {
114      return new SingleMeasurementCollectingVisitor();
115    }
116  }
117
118  @Override
119  public ImmutableSet<String> instrumentOptions() {
120    return ImmutableSet.of(GC_BEFORE_EACH_OPTION);
121  }
122
123  private static final class SingleMeasurementCollectingVisitor extends AbstractLogMessageVisitor
124      implements MeasurementCollectingVisitor {
125    Optional<Measurement> measurement = Optional.absent();
126
127    @Override
128    public boolean isDoneCollecting() {
129      return measurement.isPresent();
130    }
131
132    @Override
133    public boolean isWarmupComplete() {
134      return true;
135    }
136
137    @Override
138    public ImmutableList<Measurement> getMeasurements() {
139      return ImmutableList.copyOf(measurement.asSet());
140    }
141
142    @Override
143    public void visit(StopMeasurementLogMessage logMessage) {
144      this.measurement = Optional.of(Iterables.getOnlyElement(logMessage.measurements()));
145    }
146
147    @Override
148    public ImmutableList<String> getMessages() {
149      return ImmutableList.of();
150    }
151  }
152}
153