blob: 01fc3a86e7bf7be745836837fb87a8445c805a72 [file] [log] [blame]
Paul Duffin7fc0b452015-11-10 17:45:15 +00001/*
2 * Copyright (C) 2009 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;
18
19import com.google.caliper.UserException.ExceptionFromUserCodeException;
20import com.google.common.collect.ImmutableMap;
21import com.google.common.collect.ImmutableSet;
22
23import java.lang.reflect.InvocationTargetException;
24import java.lang.reflect.Method;
25import java.lang.reflect.Modifier;
26import java.util.Arrays;
27import java.util.LinkedHashMap;
28import java.util.Map;
29import java.util.Set;
30
31/**
32 * A convenience class for implementing benchmarks in plain code.
33 * Implementing classes must have a no-arguments constructor.
34 *
35 * <h3>Benchmarks</h3>
36 * The benchmarks of a suite are defined by . They may be
37 * static. They are not permitted to take parameters . . ..
38 *
39 * <h3>Parameters</h3>
40 * See the {@link Param} documentation to learn about parameters.
41 */
42public abstract class SimpleBenchmark
43 implements Benchmark {
44 private static final Class<?>[] ARGUMENT_TYPES = { int.class };
45
46 private final Map<String, Parameter<?>> parameters;
47 private final Map<String, Method> methods;
48
49 protected SimpleBenchmark() {
50 parameters = Parameter.forClass(getClass());
51 methods = createTimedMethods();
52
53 if (methods.isEmpty()) {
54 throw new ConfigurationException(
55 "No benchmarks defined in " + getClass().getName());
56 }
57 }
58
59 protected void setUp() throws Exception {}
60
61 protected void tearDown() throws Exception {}
62
63 @Override public Set<String> parameterNames() {
64 return ImmutableSet.<String>builder()
65 .add("benchmark")
66 .addAll(parameters.keySet())
67 .build();
68 }
69
70 @Override public Set<String> parameterValues(String parameterName) {
71 if ("benchmark".equals(parameterName)) {
72 return methods.keySet();
73 }
74
75 Parameter<?> parameter = parameters.get(parameterName);
76 if (parameter == null) {
77 throw new IllegalArgumentException();
78 }
79 try {
80 Iterable<?> values = parameter.values();
81
82 ImmutableSet.Builder<String> result = ImmutableSet.builder();
83 for (Object value : values) {
84 result.add(String.valueOf(value));
85 }
86 return result.build();
87 } catch (Exception e) {
88 throw new ExceptionFromUserCodeException(e);
89 }
90 }
91
92 @Override public ConfiguredBenchmark createBenchmark(Map<String, String> parameterValues) {
93 if (!parameterNames().equals(parameterValues.keySet())) {
94 throw new IllegalArgumentException("Invalid parameters specified. Expected "
95 + parameterNames() + " but was " + parameterValues.keySet());
96 }
97
98 String methodName = parameterValues.get("benchmark");
99 final Method method = methods.get(methodName);
100 if (method == null) {
101 throw new IllegalArgumentException("Invalid parameters specified. \"time" + methodName + "\" "
102 + "is not a method of this benchmark.");
103 }
104
105 try {
106 @SuppressWarnings({"ClassNewInstance"}) // can throw any Exception, so we catch all Exceptions
107 final SimpleBenchmark copyOfSelf = getClass().newInstance();
108
109 for (Map.Entry<String, String> entry : parameterValues.entrySet()) {
110 String parameterName = entry.getKey();
111 if ("benchmark".equals(parameterName)) {
112 continue;
113 }
114
115 Parameter<?> parameter = parameters.get(parameterName);
116 Object value = TypeConverter.fromString(entry.getValue(), parameter.getType());
117 parameter.set(copyOfSelf, value);
118 }
119 copyOfSelf.setUp();
120
121 return new ConfiguredBenchmark(copyOfSelf) {
122 @Override public Object run(int reps) throws Exception {
123 try {
124 return method.invoke(copyOfSelf, reps);
125 } catch (InvocationTargetException e) {
126 Throwable cause = e.getCause();
127 if (cause instanceof Exception) {
128 throw (Exception) cause;
129 } else if (cause instanceof Error) {
130 throw (Error) cause;
131 } else {
132 throw e;
133 }
134 }
135 }
136
137 @Override public void close() throws Exception {
138 copyOfSelf.tearDown();
139 }
140 };
141 } catch (Exception e) {
142 throw new ExceptionFromUserCodeException(e);
143 }
144 }
145
146 public Scenario normalizeScenario(Scenario scenario) {
147 Map<String, String> variables =
148 new LinkedHashMap<String, String>(scenario.getVariables());
149 // Make sure the scenario contains method names without the prefixed "time". If
150 // it has "time" prefixed, then remove it. Also check whether the user has
151 // accidentally put a lower cased letter first, and fix it if necessary.
152 String benchmark = variables.get("benchmark");
153 Map<String, Method> timedMethods = createTimedMethods();
154 if (timedMethods.get(benchmark) == null) {
155 // try to upper case first character
156 char[] benchmarkChars = benchmark.toCharArray();
157 benchmarkChars[0] = Character.toUpperCase(benchmarkChars[0]);
158 String upperCasedBenchmark = String.valueOf(benchmarkChars);
159 if (timedMethods.get(upperCasedBenchmark) != null) {
160 variables.put("benchmark", upperCasedBenchmark);
161 } else if (benchmark.startsWith("time")) {
162 variables.put("benchmark", benchmark.substring(4));
163 }
164 }
165 return new Scenario(variables);
166 }
167
168 /**
169 * Returns a spec for each benchmark defined in the specified class. The
170 * returned specs have no parameter values; those must be added separately.
171 */
172 private Map<String, Method> createTimedMethods() {
173 ImmutableMap.Builder<String, Method> result = ImmutableMap.builder();
174 for (Method method : getClass().getDeclaredMethods()) {
175 int modifiers = method.getModifiers();
176 if (!method.getName().startsWith("time")) {
177 continue;
178 }
179
180 if (!Modifier.isPublic(modifiers)
181 || Modifier.isStatic(modifiers)
182 || Modifier.isAbstract(modifiers)
183 || !Arrays.equals(method.getParameterTypes(), ARGUMENT_TYPES)) {
184 throw new ConfigurationException("Timed methods must be public, "
185 + "non-static, non-abstract and take a single int parameter. "
186 + "But " + method + " violates these requirements.");
187 }
188
189 result.put(method.getName().substring(4), method);
190 }
191
192 return result.build();
193 }
194
195 @Override public Map<String, Integer> getTimeUnitNames() {
196 return ImmutableMap.of("ns", 1,
197 "us", 1000,
198 "ms", 1000000,
199 "s", 1000000000);
200 }
201
202 @Override public double nanosToUnits(double nanos) {
203 return nanos;
204 }
205
206 @Override public Map<String, Integer> getInstanceUnitNames() {
207 return ImmutableMap.of(" instances", 1,
208 "K instances", 1000,
209 "M instances", 1000000,
210 "B instances", 1000000000);
211 }
212
213 @Override public double instancesToUnits(long instances) {
214 return instances;
215 }
216
217 @Override public Map<String, Integer> getMemoryUnitNames() {
218 return ImmutableMap.of("B", 1,
219 "KiB", 1024,
220 "MiB", 1048576,
221 "GiB", 1073741824);
222 }
223
224 @Override public double bytesToUnits(long bytes) {
225 return bytes;
226 }
227}