Home | History | Annotate | Download | only in runner
      1 /*
      2  * Copyright (C) 2013 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 java.util.concurrent.TimeUnit.NANOSECONDS;
     20 import static org.junit.Assert.assertEquals;
     21 import static org.junit.Assert.assertFalse;
     22 import static org.junit.Assert.assertTrue;
     23 import static org.junit.Assert.fail;
     24 
     25 import com.google.caliper.Benchmark;
     26 import com.google.caliper.api.BeforeRep;
     27 import com.google.caliper.api.Macrobenchmark;
     28 import com.google.caliper.runner.Instrument.Instrumentation;
     29 import com.google.caliper.util.ShortDuration;
     30 import com.google.caliper.worker.MacrobenchmarkWorker;
     31 import com.google.caliper.worker.RuntimeWorker;
     32 import com.google.common.base.Function;
     33 import com.google.common.base.Predicate;
     34 import com.google.common.collect.FluentIterable;
     35 import com.google.common.collect.ImmutableSet;
     36 import com.google.common.util.concurrent.Uninterruptibles;
     37 
     38 import org.junit.Before;
     39 import org.junit.Rule;
     40 import org.junit.Test;
     41 import org.junit.runner.RunWith;
     42 import org.junit.runners.JUnit4;
     43 
     44 import java.lang.reflect.Method;
     45 import java.util.Arrays;
     46 import java.util.concurrent.TimeUnit;
     47 
     48 /**
     49  * Tests {@link RuntimeInstrument}.
     50  */
     51 @RunWith(JUnit4.class)
     52 public class RuntimeInstrumentTest {
     53   @Rule public CaliperTestWatcher runner = new CaliperTestWatcher();
     54 
     55   private RuntimeInstrument instrument;
     56 
     57   @Before public void createInstrument() {
     58     this.instrument = new RuntimeInstrument(ShortDuration.of(100, NANOSECONDS));
     59   }
     60 
     61   @Test public void isBenchmarkMethod() {
     62     assertEquals(
     63         ImmutableSet.of("macrobenchmark", "microbenchmark", "picobenchmark", "integerParam"),
     64         FluentIterable.from(Arrays.asList(RuntimeBenchmark.class.getDeclaredMethods()))
     65             .filter(new Predicate<Method>() {
     66               @Override public boolean apply(Method input) {
     67                 return instrument.isBenchmarkMethod(input);
     68               }
     69             })
     70             .transform(new Function<Method, String>() {
     71               @Override public String apply(Method input) {
     72                 return input.getName();
     73               }
     74             })
     75             .toSet());
     76   }
     77 
     78   @Test public void createInstrumentation_macrobenchmark() throws Exception {
     79     Method benchmarkMethod = RuntimeBenchmark.class.getDeclaredMethod("macrobenchmark");
     80     Instrumentation instrumentation = instrument.createInstrumentation(benchmarkMethod);
     81     assertEquals(benchmarkMethod, instrumentation.benchmarkMethod());
     82     assertEquals(instrument, instrumentation.instrument());
     83     assertEquals(MacrobenchmarkWorker.class, instrumentation.workerClass());
     84   }
     85 
     86   @Test public void createInstrumentation_microbenchmark() throws Exception {
     87     Method benchmarkMethod = RuntimeBenchmark.class.getDeclaredMethod("microbenchmark", int.class);
     88     Instrumentation instrumentation = instrument.createInstrumentation(benchmarkMethod);
     89     assertEquals(benchmarkMethod, instrumentation.benchmarkMethod());
     90     assertEquals(instrument, instrumentation.instrument());
     91     assertEquals(RuntimeWorker.Micro.class, instrumentation.workerClass());
     92   }
     93 
     94   @Test public void createInstrumentation_picobenchmark() throws Exception {
     95     Method benchmarkMethod = RuntimeBenchmark.class.getDeclaredMethod("picobenchmark", long.class);
     96     Instrumentation instrumentation = instrument.createInstrumentation(benchmarkMethod);
     97     assertEquals(benchmarkMethod, instrumentation.benchmarkMethod());
     98     assertEquals(instrument, instrumentation.instrument());
     99     assertEquals(RuntimeWorker.Pico.class, instrumentation.workerClass());
    100   }
    101 
    102   @Test public void createInstrumentation_badParam() throws Exception {
    103     Method benchmarkMethod =
    104         RuntimeBenchmark.class.getDeclaredMethod("integerParam", Integer.class);
    105     try {
    106       instrument.createInstrumentation(benchmarkMethod);
    107       fail();
    108     } catch (InvalidBenchmarkException expected) {}
    109   }
    110 
    111   @Test public void createInstrumentation_notAMacrobenchmark() throws Exception {
    112     Method benchmarkMethod = RuntimeBenchmark.class.getDeclaredMethod("notAMacrobenchmark");
    113     try {
    114       instrument.createInstrumentation(benchmarkMethod);
    115       fail();
    116     } catch (IllegalArgumentException expected) {}
    117   }
    118 
    119   @Test public void createInstrumentationnotAMicrobenchmark() throws Exception {
    120     Method benchmarkMethod =
    121         RuntimeBenchmark.class.getDeclaredMethod("notAMicrobenchmark", int.class);
    122     try {
    123       instrument.createInstrumentation(benchmarkMethod);
    124       fail();
    125     } catch (IllegalArgumentException expected) {}
    126   }
    127 
    128   @Test public void createInstrumentation_notAPicobenchmark() throws Exception {
    129     Method benchmarkMethod =
    130         RuntimeBenchmark.class.getDeclaredMethod("notAPicobenchmark", long.class);
    131     try {
    132       instrument.createInstrumentation(benchmarkMethod);
    133       fail();
    134     } catch (IllegalArgumentException expected) {}
    135   }
    136 
    137   @SuppressWarnings("unused")
    138   private static final class RuntimeBenchmark {
    139     @Benchmark void macrobenchmark() {}
    140     @Benchmark void microbenchmark(int reps) {}
    141     @Benchmark void picobenchmark(long reps) {}
    142 
    143     @Benchmark void integerParam(Integer oops) {}
    144 
    145     void notAMacrobenchmark() {}
    146     void notAMicrobenchmark(int reps) {}
    147     void notAPicobenchmark(long reps) {}
    148   }
    149 
    150   private double relativeDifference(double a, double b) {
    151     return Math.abs(a - b) / ((a + b) / 2.0);
    152   }
    153 
    154   static final class TestBenchmark {
    155     @Benchmark long pico(long reps) {
    156       long dummy = 0;
    157       for (long i = 0; i < reps; i++) {
    158         dummy += spin();
    159       }
    160       return dummy;
    161     }
    162 
    163     @Benchmark long micro(int reps) {
    164       long dummy = 0;
    165       for (int i = 0; i < reps; i++) {
    166         dummy += spin();
    167       }
    168       return dummy;
    169     }
    170 
    171     @Macrobenchmark long macro() {
    172       return spin();
    173     }
    174   }
    175 
    176   // busy spin for 10ms and return the elapsed time.  N.B. we busy spin instead of sleeping so
    177   // that we aren't put at the mercy (and variance) of the thread scheduler.
    178   private static long spin() {
    179     long remainingNanos = TimeUnit.MILLISECONDS.toNanos(10);
    180     long start = System.nanoTime();
    181     long elapsed;
    182     while ((elapsed = System.nanoTime() - start) < remainingNanos) {}
    183     return elapsed;
    184   }
    185 
    186   @Test
    187 
    188   public void gcBeforeEachOptionIsHonored() throws Exception {
    189     runBenchmarkWithKnownHeap(true);
    190     // The GC error will only be avoided if gcBeforeEach is true, and
    191     // honored by the MacrobenchmarkWorker.
    192     assertFalse("No GC warning should be printed to stderr",
    193         runner.getStdout().toString().contains("WARNING: GC occurred during timing."));
    194   }
    195 
    196   @Test
    197 
    198   public void gcBeforeEachOptionIsReallyNecessary() throws Exception {
    199     // Verifies that we indeed get a GC warning if gcBeforeEach = false.
    200     runBenchmarkWithKnownHeap(false);
    201     assertTrue("A GC warning should be printed to stderr if gcBeforeEach isn't honored",
    202         runner.getStdout().toString().contains("WARNING: GC occurred during timing."));
    203   }
    204 
    205   private void runBenchmarkWithKnownHeap(boolean gcBeforeEach) throws Exception {
    206     runner.forBenchmark(BenchmarkThatAllocatesALot.class)
    207         .instrument("runtime")
    208         .options(
    209             "-Cvm.args=-Xmx512m",
    210             "-Cinstrument.runtime.options.measurements=10",
    211             "-Cinstrument.runtime.options.gcBeforeEach=" + gcBeforeEach,
    212             "--time-limit=30s")
    213         .run();
    214   }
    215 
    216   static final class BenchmarkThatAllocatesALot {
    217     @Benchmark
    218     int benchmarkMethod() {
    219       // Any larger and the GC doesn't manage to make enough space, resulting in
    220       // OOMErrors in both test cases above.
    221       long[] array = new long[32 * 1024 * 1024];
    222       return array.length;
    223     }
    224   }
    225 
    226   @Test
    227 
    228   public void maxWarmupWallTimeOptionIsHonored() throws Exception {
    229     runner.forBenchmark(MacroBenchmarkWithLongBeforeRep.class)
    230         .instrument("runtime")
    231         .options(
    232             "-Cinstrument.runtime.options.maxWarmupWallTime=100ms",
    233             "--time-limit=10s")
    234         .run();
    235 
    236     assertTrue(
    237         "The maxWarmupWallTime should trigger an interruption of warmup and a warning "
    238             + "should be printed to stderr",
    239         runner.getStdout().toString().contains(
    240             "WARNING: Warmup was interrupted "
    241                 + "because it took longer than 100ms of wall-clock time."));
    242   }
    243 
    244   static final class MacroBenchmarkWithLongBeforeRep {
    245     @BeforeRep
    246     public void beforeRepMuchLongerThanBenchmark() {
    247       Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
    248     }
    249 
    250     @Benchmark
    251     long prettyFastMacroBenchmark() {
    252       return spin();
    253     }
    254   }
    255 }
    256