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.common.base.Preconditions.checkArgument;
20import static com.google.common.base.Preconditions.checkNotNull;
21
22import com.google.caliper.bridge.AbstractLogMessageVisitor;
23import com.google.caliper.bridge.LogMessageVisitor;
24import com.google.caliper.bridge.StopMeasurementLogMessage;
25import com.google.caliper.config.VmConfig;
26import com.google.caliper.model.InstrumentSpec;
27import com.google.caliper.model.Measurement;
28import com.google.caliper.worker.Worker;
29import com.google.common.base.MoreObjects;
30import com.google.common.base.Objects;
31import com.google.common.base.Predicates;
32import com.google.common.collect.ArrayListMultimap;
33import com.google.common.collect.ImmutableList;
34import com.google.common.collect.ImmutableMap;
35import com.google.common.collect.ImmutableSet;
36import com.google.common.collect.ListMultimap;
37import com.google.common.collect.Maps;
38
39import java.lang.reflect.Method;
40
41import javax.inject.Inject;
42
43public abstract class Instrument {
44  protected ImmutableMap<String, String> options;
45  private String name = getClass().getSimpleName();
46
47  @Inject void setOptions(@InstrumentOptions ImmutableMap<String, String> options) {
48    this.options = ImmutableMap.copyOf(
49        Maps.filterKeys(options, Predicates.in(instrumentOptions())));
50  }
51
52  @Inject void setInstrumentName(@InstrumentName String name) {
53    this.name = name;
54  }
55
56  String name() {
57    return name;
58  }
59
60  @Override public String toString() {
61    return name();
62  }
63
64  public abstract boolean isBenchmarkMethod(Method method);
65
66  public abstract Instrumentation createInstrumentation(Method benchmarkMethod)
67      throws InvalidBenchmarkException;
68
69  /**
70   * Indicates that trials using this instrument can be run in parallel with other trials.
71   */
72  public abstract TrialSchedulingPolicy schedulingPolicy();
73
74  /**
75   * The application of an instrument to a particular benchmark method.
76   */
77  // TODO(gak): consider passing in Instrument explicitly for DI
78  public abstract class Instrumentation {
79    protected Method benchmarkMethod;
80
81    protected Instrumentation(Method benchmarkMethod) {
82      this.benchmarkMethod = checkNotNull(benchmarkMethod);
83    }
84
85    Instrument instrument() {
86      return Instrument.this;
87    }
88
89    Method benchmarkMethod() {
90      return benchmarkMethod;
91    }
92
93    @Override
94    public final boolean equals(Object obj) {
95      if (obj == this) {
96        return true;
97      } else if (obj instanceof Instrumentation) {
98        Instrumentation that = (Instrumentation) obj;
99        return Instrument.this.equals(that.instrument())
100            && this.benchmarkMethod.equals(that.benchmarkMethod);
101      }
102      return super.equals(obj);
103    }
104
105    @Override
106    public final int hashCode() {
107      return Objects.hashCode(Instrument.this, benchmarkMethod);
108    }
109
110    @Override
111    public String toString() {
112      return MoreObjects.toStringHelper(Instrumentation.class)
113          .add("instrument", Instrument.this)
114          .add("benchmarkMethod", benchmarkMethod)
115          .toString();
116    }
117
118    public abstract void dryRun(Object benchmark) throws InvalidBenchmarkException;
119
120    public abstract Class<? extends Worker> workerClass();
121
122    /**
123     * Return the subset of options (and possibly a transformation thereof) to be used in the
124     * worker. Returns all instrument options by default.
125     */
126    public ImmutableMap<String, String> workerOptions() {
127      return options;
128    }
129
130    abstract MeasurementCollectingVisitor getMeasurementCollectingVisitor();
131  }
132
133  public final ImmutableMap<String, String> options() {
134    return options;
135  }
136
137  final InstrumentSpec getSpec() {
138    return new InstrumentSpec.Builder()
139        .instrumentClass(getClass())
140        .addAllOptions(options())
141        .build();
142  }
143
144  /**
145   * Defines the list of options applicable to this instrument. Implementations that use options
146   * will need to override this method.
147   */
148  protected ImmutableSet<String> instrumentOptions() {
149    return ImmutableSet.of();
150  }
151
152  /**
153   * Returns some arguments that should be added to the command line when invoking
154   * this instrument's worker.
155   *
156   * @param vmConfig the configuration for the VM on which this is running.
157   */
158  ImmutableSet<String> getExtraCommandLineArgs(VmConfig vmConfig) {
159    return vmConfig.commonInstrumentVmArgs();
160  }
161
162  interface MeasurementCollectingVisitor extends LogMessageVisitor {
163    boolean isDoneCollecting();
164    boolean isWarmupComplete();
165    ImmutableList<Measurement> getMeasurements();
166    /**
167     * Returns all the messages created while collecting measurments.
168     *
169     * <p>A message is some piece of user visible data that should be displayed to the user along
170     * with the trial results.
171     *
172     * <p>TODO(lukes): should we model these as anything more than strings.  These messages already
173     * have a concept of 'level' based on the prefix.
174     */
175    ImmutableList<String> getMessages();
176  }
177
178  /**
179   * A default implementation of {@link MeasurementCollectingVisitor} that collects measurements for
180   * pre-specified descriptions.
181   */
182  protected static final class DefaultMeasurementCollectingVisitor
183      extends AbstractLogMessageVisitor implements MeasurementCollectingVisitor {
184    static final int DEFAULT_NUMBER_OF_MEASUREMENTS = 9;
185    final ImmutableSet<String> requiredDescriptions;
186    final ListMultimap<String, Measurement> measurementsByDescription;
187    final int requiredMeasurements;
188
189    DefaultMeasurementCollectingVisitor(ImmutableSet<String> requiredDescriptions) {
190      this(requiredDescriptions, DEFAULT_NUMBER_OF_MEASUREMENTS);
191    }
192
193    DefaultMeasurementCollectingVisitor(ImmutableSet<String> requiredDescriptions,
194        int requiredMeasurements) {
195      this.requiredDescriptions = requiredDescriptions;
196      checkArgument(!requiredDescriptions.isEmpty());
197      this.requiredMeasurements = requiredMeasurements;
198      checkArgument(requiredMeasurements > 0);
199      this.measurementsByDescription =
200          ArrayListMultimap.create(requiredDescriptions.size(), requiredMeasurements);
201    }
202
203    @Override public void visit(StopMeasurementLogMessage logMessage) {
204      for (Measurement measurement : logMessage.measurements()) {
205        measurementsByDescription.put(measurement.description(), measurement);
206      }
207    }
208
209    @Override public boolean isDoneCollecting() {
210      for (String description : requiredDescriptions) {
211        if (measurementsByDescription.get(description).size() < requiredMeasurements) {
212          return false;
213        }
214      }
215      return true;
216    }
217
218    @Override
219    public boolean isWarmupComplete() {
220      return true;
221    }
222
223    @Override public ImmutableList<Measurement> getMeasurements() {
224      return ImmutableList.copyOf(measurementsByDescription.values());
225    }
226
227    @Override public ImmutableList<String> getMessages() {
228      return ImmutableList.of();
229    }
230  }
231}
232