blob: e960c03eb661d2218ea8499a04e9b0fd0f60d8f7 [file] [log] [blame]
Paul Duffine2363012015-11-30 16:20:41 +00001/*
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
17package com.google.caliper.runner;
18
19import static java.util.concurrent.TimeUnit.NANOSECONDS;
20import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertFalse;
22import static org.junit.Assert.assertTrue;
23import static org.junit.Assert.fail;
24
25import com.google.caliper.Benchmark;
26import com.google.caliper.api.BeforeRep;
27import com.google.caliper.api.Macrobenchmark;
28import com.google.caliper.runner.Instrument.Instrumentation;
29import com.google.caliper.util.ShortDuration;
30import com.google.caliper.worker.MacrobenchmarkWorker;
31import com.google.caliper.worker.RuntimeWorker;
32import com.google.common.base.Function;
33import com.google.common.base.Predicate;
34import com.google.common.collect.FluentIterable;
35import com.google.common.collect.ImmutableSet;
36import com.google.common.util.concurrent.Uninterruptibles;
37
38import org.junit.Before;
39import org.junit.Rule;
40import org.junit.Test;
41import org.junit.runner.RunWith;
42import org.junit.runners.JUnit4;
43
44import java.lang.reflect.Method;
45import java.util.Arrays;
46import java.util.concurrent.TimeUnit;
47
48/**
49 * Tests {@link RuntimeInstrument}.
50 */
51@RunWith(JUnit4.class)
52public 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}