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 createInstrument()57 @Before public void createInstrument() { 58 this.instrument = new RuntimeInstrument(ShortDuration.of(100, NANOSECONDS)); 59 } 60 isBenchmarkMethod()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 createInstrumentation_macrobenchmark()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 createInstrumentation_microbenchmark()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 createInstrumentation_picobenchmark()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 createInstrumentation_badParam()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 createInstrumentation_notAMacrobenchmark()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 createInstrumentationnotAMicrobenchmark()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 createInstrumentation_notAPicobenchmark()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 { macrobenchmark()139 @Benchmark void macrobenchmark() {} microbenchmark(int reps)140 @Benchmark void microbenchmark(int reps) {} picobenchmark(long reps)141 @Benchmark void picobenchmark(long reps) {} 142 integerParam(Integer oops)143 @Benchmark void integerParam(Integer oops) {} 144 notAMacrobenchmark()145 void notAMacrobenchmark() {} notAMicrobenchmark(int reps)146 void notAMicrobenchmark(int reps) {} notAPicobenchmark(long reps)147 void notAPicobenchmark(long reps) {} 148 } 149 relativeDifference(double a, double b)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 { pico(long reps)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 micro(int reps)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 macro()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. spin()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 gcBeforeEachOptionIsHonored()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 gcBeforeEachOptionIsReallyNecessary()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 runBenchmarkWithKnownHeap(boolean gcBeforeEach)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 benchmarkMethod()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 maxWarmupWallTimeOptionIsHonored()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 beforeRepMuchLongerThanBenchmark()246 public void beforeRepMuchLongerThanBenchmark() { 247 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); 248 } 249 250 @Benchmark prettyFastMacroBenchmark()251 long prettyFastMacroBenchmark() { 252 return spin(); 253 } 254 } 255 } 256