Home | History | Annotate | Download | only in runner
      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 
     17 package com.google.caliper.runner;
     18 
     19 import static com.google.common.base.Preconditions.checkArgument;
     20 import static com.google.common.base.Preconditions.checkNotNull;
     21 
     22 import com.google.caliper.bridge.AbstractLogMessageVisitor;
     23 import com.google.caliper.bridge.LogMessageVisitor;
     24 import com.google.caliper.bridge.StopMeasurementLogMessage;
     25 import com.google.caliper.config.VmConfig;
     26 import com.google.caliper.model.InstrumentSpec;
     27 import com.google.caliper.model.Measurement;
     28 import com.google.caliper.worker.Worker;
     29 import com.google.common.base.MoreObjects;
     30 import com.google.common.base.Objects;
     31 import com.google.common.base.Predicates;
     32 import com.google.common.collect.ArrayListMultimap;
     33 import com.google.common.collect.ImmutableList;
     34 import com.google.common.collect.ImmutableMap;
     35 import com.google.common.collect.ImmutableSet;
     36 import com.google.common.collect.ListMultimap;
     37 import com.google.common.collect.Maps;
     38 
     39 import java.lang.reflect.Method;
     40 
     41 import javax.inject.Inject;
     42 
     43 public 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