Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
Benchmark |
|
| 3.909090909090909;3.909 |
1 | /** | |
2 | * Copyright (c) 2012, University of Konstanz, Distributed Systems Group | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions are met: | |
7 | * * Redistributions of source code must retain the above copyright | |
8 | * notice, this list of conditions and the following disclaimer. | |
9 | * * Redistributions in binary form must reproduce the above copyright | |
10 | * notice, this list of conditions and the following disclaimer in the | |
11 | * documentation and/or other materials provided with the distribution. | |
12 | * * Neither the name of the University of Konstanz nor the | |
13 | * names of its contributors may be used to endorse or promote products | |
14 | * derived from this software without specific prior written permission. | |
15 | * | |
16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
19 | * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY | |
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
26 | */ | |
27 | package org.perfidix; | |
28 | ||
29 | import java.lang.reflect.Method; | |
30 | import java.util.ArrayList; | |
31 | import java.util.HashMap; | |
32 | import java.util.Hashtable; | |
33 | import java.util.LinkedHashSet; | |
34 | import java.util.List; | |
35 | import java.util.Map; | |
36 | import java.util.Random; | |
37 | import java.util.Set; | |
38 | ||
39 | import org.perfidix.AbstractConfig.StandardConfig; | |
40 | import org.perfidix.annotation.AfterBenchClass; | |
41 | import org.perfidix.annotation.BeforeBenchClass; | |
42 | import org.perfidix.annotation.Bench; | |
43 | import org.perfidix.element.AbstractMethodArrangement; | |
44 | import org.perfidix.element.BenchmarkElement; | |
45 | import org.perfidix.element.BenchmarkExecutor; | |
46 | import org.perfidix.element.BenchmarkMethod; | |
47 | import org.perfidix.exceptions.PerfidixMethodCheckException; | |
48 | import org.perfidix.exceptions.PerfidixMethodInvocationException; | |
49 | import org.perfidix.meter.AbstractMeter; | |
50 | import org.perfidix.result.BenchmarkResult; | |
51 | ||
52 | /** | |
53 | * Class to hold all classes which want to be benchmarked. | |
54 | * <p> | |
55 | * | |
56 | * <pre> | |
57 | * | |
58 | * | |
59 | * public class MyBenchmark { | |
60 | * | |
61 | * public static void main(final String[] args) { | |
62 | * // Build up own config | |
63 | * final AbstractConfig config = new MyConfig(); | |
64 | * | |
65 | * // Initialise benchmark with config | |
66 | * final Benchmark benchmark = new Benchmark(config); | |
67 | * | |
68 | * // Adding Classes to benchmark | |
69 | * benchmark.add(Class1.class); | |
70 | * benchmark.add(Class2.class); | |
71 | * benchmark.add(Object3); // of Class3, every class is allowed to be inserted only once | |
72 | * | |
73 | * // Running benchmark | |
74 | * final BenchmarkResult result = benchmark.run(); | |
75 | * | |
76 | * // Printing out Result in a Tabular, every AbstractOutput implementing class is allowed. | |
77 | * new TabularSummaryOutput().visitBenchmark(result); | |
78 | * | |
79 | * } | |
80 | * } | |
81 | * | |
82 | * </pre> | |
83 | * | |
84 | * </p> | |
85 | * | |
86 | * @see AbstractConfig | |
87 | * @see BenchmarkResult | |
88 | * @author Sebastian Graf, University of Konstanz | |
89 | */ | |
90 | public final class Benchmark { | |
91 | ||
92 | /** Set with all used classes. */ | |
93 | private transient final Set<Class<?>> clazzes; | |
94 | ||
95 | /** Already instantiated objects */ | |
96 | private transient final Set<Object> objects; | |
97 | ||
98 | /** Simple random for gc-prob */ | |
99 | 5 | private transient static final Random RAN = new Random(); |
100 | ||
101 | /** Configuration of benchmark, holding everything. */ | |
102 | private transient final AbstractConfig conf; | |
103 | ||
104 | /** | |
105 | * Constructor with a fixed set of used meters. | |
106 | * | |
107 | * @param paramConf | |
108 | * Configuration for Benchmark | |
109 | */ | |
110 | 65 | public Benchmark(final AbstractConfig paramConf) { |
111 | 65 | conf = paramConf; |
112 | 65 | this.clazzes = new LinkedHashSet<Class<?>>(); |
113 | 65 | this.objects = new LinkedHashSet<Object>(); |
114 | ||
115 | 65 | } |
116 | ||
117 | /** | |
118 | * Convience constructor using the {@link StandardConfig} | |
119 | */ | |
120 | public Benchmark() { | |
121 | 5 | this(new StandardConfig()); |
122 | 5 | } |
123 | ||
124 | /** | |
125 | * Adding a class to bench to this benchmark. This class should contain | |
126 | * benchmarkable methods, otherwise it will be ignored. | |
127 | * | |
128 | * @param clazz | |
129 | * to be added. | |
130 | */ | |
131 | public void add(final Class<?> clazz) { | |
132 | 75 | if (this.clazzes.contains(clazz)) { |
133 | 10 | throw new IllegalArgumentException("Only one class-instance per benchmark allowed"); |
134 | } else { | |
135 | 65 | this.clazzes.add(clazz); |
136 | } | |
137 | 65 | } |
138 | ||
139 | /** | |
140 | * Adding a already instantiated objects to benchmark. Per benchmark, only | |
141 | * one objects of each class is allowed. | |
142 | * | |
143 | * @param obj | |
144 | * to be added | |
145 | */ | |
146 | public void add(final Object obj) { | |
147 | 25 | final Class<?> clazz = obj.getClass(); |
148 | ||
149 | 25 | if (this.clazzes.contains(clazz)) { |
150 | 5 | throw new IllegalArgumentException("Only one class-instance per benchmark allowed"); |
151 | } else { | |
152 | 20 | this.clazzes.add(clazz); |
153 | 20 | this.objects.add(obj); |
154 | } | |
155 | 20 | } |
156 | ||
157 | /** | |
158 | * Getting the number of all methods and all runs | |
159 | * | |
160 | * @return a map with all methods and the runs. | |
161 | */ | |
162 | public Map<BenchmarkMethod, Integer> getNumberOfMethodsAndRuns() { | |
163 | 15 | final Map<BenchmarkMethod, Integer> returnVal = new HashMap<BenchmarkMethod, Integer>(); |
164 | 15 | final List<BenchmarkMethod> meths = getBenchmarkMethods(); |
165 | 15 | for (final BenchmarkMethod meth : meths) { |
166 | 80 | int numberOfRuns = BenchmarkMethod.getNumberOfAnnotatedRuns(meth.getMethodToBench()); |
167 | 80 | if (numberOfRuns == Bench.NONE_RUN) { |
168 | 5 | numberOfRuns = conf.getRuns(); |
169 | } | |
170 | 80 | returnVal.put(meth, numberOfRuns); |
171 | 80 | } |
172 | 15 | return returnVal; |
173 | } | |
174 | ||
175 | /** | |
176 | * Running this benchmark | |
177 | * | |
178 | * @return {@link BenchmarkResult} the result in an {@link BenchmarkResult} container. | |
179 | */ | |
180 | public BenchmarkResult run() { | |
181 | 40 | final BenchmarkResult res = new BenchmarkResult(conf.getListener()); |
182 | 40 | BenchmarkExecutor.initialize(conf, res); |
183 | ||
184 | // getting Benchmarkables | |
185 | 40 | final List<BenchmarkElement> elements = getBenchmarkElements(); |
186 | ||
187 | // arranging them | |
188 | 40 | final AbstractMethodArrangement arrangement = |
189 | AbstractMethodArrangement.getMethodArrangement(elements, conf.getArrangement()); | |
190 | ||
191 | // instantiate methods | |
192 | 40 | final Map<Class<?>, Object> instantiatedObj = instantiateMethods(res); |
193 | ||
194 | // getting the mapping and executing beforemethod | |
195 | 40 | final Map<Class<?>, Object> objectsToExecute = executeBeforeBenchClass(instantiatedObj, res); |
196 | ||
197 | // executing the bench for the arrangement | |
198 | 40 | for (final BenchmarkElement elem : arrangement) { |
199 | // invoking gc if possible | |
200 | 3975 | if (RAN.nextDouble() < conf.getGcProb()) { |
201 | 3975 | System.gc(); |
202 | } | |
203 | ||
204 | 3975 | final BenchmarkExecutor exec = BenchmarkExecutor.getExecutor(elem); |
205 | ||
206 | 3975 | final Object obj = objectsToExecute.get(elem.getMeth().getMethodToBench().getDeclaringClass()); |
207 | // check needed because of failed initialization of objects | |
208 | 3975 | if (obj != null) { |
209 | 3925 | exec.executeBeforeMethods(obj); |
210 | 3925 | exec.executeBench(obj); |
211 | 3925 | exec.executeAfterMethods(obj); |
212 | } | |
213 | 3975 | } |
214 | ||
215 | // cleaning up methods to benchmark | |
216 | 40 | tearDownObjectsToExecute(objectsToExecute, res); |
217 | 40 | return res; |
218 | } | |
219 | ||
220 | /** | |
221 | * Setting up executable objects for all registered classes and executing {@link BeforeBenchClass} | |
222 | * annotated methods. If an {@link Exception} occurs, this failure will be stored in the | |
223 | * {@link BenchmarkResult} and | |
224 | * the class will not be instantiated | |
225 | * | |
226 | * @param res | |
227 | * {@link BenchmarkResult} for storing possible failures. | |
228 | * @return a mapping with class->objects for all registered classes- | |
229 | */ | |
230 | private Map<Class<?>, Object> instantiateMethods(final BenchmarkResult res) { | |
231 | // datastructure initialization for all objects | |
232 | 40 | final Map<Class<?>, Object> objectsToUse = new Hashtable<Class<?>, Object>(); |
233 | ||
234 | // generating including already instaniated objects | |
235 | 40 | for (final Object obj : this.objects) { |
236 | 5 | final Class<?> clazz = obj.getClass(); |
237 | 5 | objectsToUse.put(clazz, obj); |
238 | 5 | } |
239 | ||
240 | // generating objects for each registered class | |
241 | 40 | for (final Class<?> clazz : clazzes) { |
242 | // generating a new instance on which the benchmark will be | |
243 | // performed if there isn't a user generated one | |
244 | 50 | if (!objectsToUse.containsKey(clazz)) { |
245 | try { | |
246 | 45 | final Object obj = clazz.newInstance(); |
247 | 45 | objectsToUse.put(clazz, obj); |
248 | // otherwise adding an exception to the result | |
249 | 0 | } catch (final InstantiationException e) { |
250 | 0 | res.addException(new PerfidixMethodInvocationException(e, BeforeBenchClass.class)); |
251 | 0 | } catch (final IllegalAccessException e) { |
252 | 0 | res.addException(new PerfidixMethodInvocationException(e, BeforeBenchClass.class)); |
253 | 45 | } |
254 | ||
255 | } | |
256 | 50 | } |
257 | ||
258 | 40 | return objectsToUse; |
259 | } | |
260 | ||
261 | /** | |
262 | * Executing beforeBenchClass if present. | |
263 | * | |
264 | * @param instantiatedObj | |
265 | * with the instantiatedObj; | |
266 | * @param res | |
267 | * where the Exceptions should be stored to | |
268 | * @return valid instances with valid beforeCall | |
269 | */ | |
270 | private Map<Class<?>, Object> executeBeforeBenchClass(final Map<Class<?>, Object> instantiatedObj, | |
271 | final BenchmarkResult res) { | |
272 | ||
273 | 40 | final Map<Class<?>, Object> returnVal = new Hashtable<Class<?>, Object>(); |
274 | ||
275 | // invoking before bench class | |
276 | 40 | for (final Class<?> clazz : instantiatedObj.keySet()) { |
277 | ||
278 | 50 | final Object objectToUse = instantiatedObj.get(clazz); |
279 | // ..the search for the beforeClassMeth begins... | |
280 | 50 | Method beforeClassMeth = null; |
281 | 50 | boolean continueVal = true; |
282 | try { | |
283 | 50 | beforeClassMeth = |
284 | BenchmarkMethod.findAndCheckAnyMethodByAnnotation(clazz, BeforeBenchClass.class); | |
285 | // ... and if this search is throwing an exception, the | |
286 | // exception will be added and a flag is set to break up | |
287 | 0 | } catch (final PerfidixMethodCheckException e) { |
288 | 0 | res.addException(e); |
289 | 0 | continueVal = false; |
290 | 50 | } |
291 | // if everything worked well... | |
292 | 50 | if (continueVal) { |
293 | 50 | if (beforeClassMeth == null) { |
294 | // ...either the objects is directly mapped to the class | |
295 | // for executing the benches | |
296 | 15 | returnVal.put(clazz, objectToUse); |
297 | } else { | |
298 | // ... or the beforeMethod will be executed and a | |
299 | // possible exception stored to the result... | |
300 | 35 | final PerfidixMethodCheckException beforeByCheck = |
301 | BenchmarkExecutor.checkMethod(objectToUse, BeforeBenchClass.class, beforeClassMeth); | |
302 | 35 | if (beforeByCheck == null) { |
303 | 35 | final PerfidixMethodInvocationException beforeByInvok = |
304 | BenchmarkExecutor.invokeMethod(objectToUse, BeforeBenchClass.class, | |
305 | beforeClassMeth); | |
306 | 35 | if (beforeByInvok == null) { |
307 | 30 | returnVal.put(clazz, objectToUse); |
308 | } else { | |
309 | 5 | res.addException(beforeByInvok); |
310 | } | |
311 | 35 | } else { |
312 | 0 | res.addException(beforeByCheck); |
313 | } | |
314 | } | |
315 | } | |
316 | 50 | } |
317 | 40 | return returnVal; |
318 | } | |
319 | ||
320 | /** | |
321 | * Tear down executable objects for all registered classes and executing {@link AfterBenchClass} annotated | |
322 | * methods. | |
323 | * | |
324 | * @param objects | |
325 | * a mapping with class->objects to be teared down | |
326 | * @param res | |
327 | * the {@link BenchmarkResult} for storing possible failures. | |
328 | */ | |
329 | private void tearDownObjectsToExecute(final Map<Class<?>, Object> objects, final BenchmarkResult res) { | |
330 | ||
331 | // executing tearDown for all clazzes registered in given Map | |
332 | 40 | for (final Class<?> clazz : objects.keySet()) { |
333 | 45 | final Object objectToUse = objects.get(clazz); |
334 | 45 | if (objectToUse != null) { |
335 | // executing AfterClass for all objects. | |
336 | 45 | Method afterClassMeth = null; |
337 | try { | |
338 | 45 | afterClassMeth = |
339 | BenchmarkMethod.findAndCheckAnyMethodByAnnotation(clazz, AfterBenchClass.class); | |
340 | 0 | } catch (final PerfidixMethodCheckException e) { |
341 | 0 | res.addException(e); |
342 | 45 | } |
343 | // if afterClassMethod exists, the method will be executed and | |
344 | // possible failures will be stored in the BenchmarkResult | |
345 | 45 | if (afterClassMeth != null) { |
346 | 25 | final PerfidixMethodCheckException afterByCheck = |
347 | BenchmarkExecutor.checkMethod(objectToUse, AfterBenchClass.class, afterClassMeth); | |
348 | 25 | if (afterByCheck == null) { |
349 | 25 | final PerfidixMethodInvocationException afterByInvok = |
350 | BenchmarkExecutor | |
351 | .invokeMethod(objectToUse, AfterBenchClass.class, afterClassMeth); | |
352 | 25 | if (afterByInvok != null) { |
353 | 0 | res.addException(afterByInvok); |
354 | } | |
355 | 25 | } else { |
356 | 0 | res.addException(afterByCheck); |
357 | } | |
358 | } | |
359 | } | |
360 | 45 | } |
361 | 40 | } |
362 | ||
363 | /** | |
364 | * Getting all Benchmarkable methods out of the registered class. | |
365 | * | |
366 | * @return a Set with {@link BenchmarkMethod} | |
367 | */ | |
368 | public List<BenchmarkMethod> getBenchmarkMethods() { | |
369 | // Generating Set for returnVal | |
370 | 55 | final List<BenchmarkMethod> elems = new ArrayList<BenchmarkMethod>(); |
371 | // Getting all Methods and testing if its benchmarkable | |
372 | 55 | for (final Class<?> clazz : clazzes) { |
373 | 765 | for (final Method meth : clazz.getDeclaredMethods()) { |
374 | // Check if benchmarkable, if so, insert to returnVal; | |
375 | 680 | if (BenchmarkMethod.isBenchmarkable(meth)) { |
376 | 170 | final BenchmarkMethod benchmarkMeth = new BenchmarkMethod(meth); |
377 | 170 | elems.add(benchmarkMeth); |
378 | } | |
379 | } | |
380 | 85 | } |
381 | 55 | return elems; |
382 | } | |
383 | ||
384 | /** | |
385 | * Getting all benchmarkable objects out of the registered classes with the | |
386 | * annotated number of runs. | |
387 | * | |
388 | * @return a Set with {@link BenchmarkMethod} | |
389 | */ | |
390 | public List<BenchmarkElement> getBenchmarkElements() { | |
391 | ||
392 | 40 | final List<BenchmarkElement> elems = new ArrayList<BenchmarkElement>(); |
393 | ||
394 | 40 | final List<BenchmarkMethod> meths = getBenchmarkMethods(); |
395 | ||
396 | 40 | for (final BenchmarkMethod meth : meths) { |
397 | 90 | int numberOfRuns = BenchmarkMethod.getNumberOfAnnotatedRuns(meth.getMethodToBench()); |
398 | 90 | if (numberOfRuns == Bench.NONE_RUN) { |
399 | 30 | numberOfRuns = conf.getRuns(); |
400 | } | |
401 | ||
402 | // getting the number of runs and adding this number of | |
403 | // elements to the set to be evaluated. | |
404 | 4065 | for (int i = 0; i < numberOfRuns; i++) { |
405 | 3975 | elems.add(new BenchmarkElement(meth)); |
406 | } | |
407 | 90 | } |
408 | ||
409 | 40 | return elems; |
410 | } | |
411 | } |