View Javadoc

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.element;
28  
29  import java.lang.annotation.Annotation;
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Method;
32  import java.util.Arrays;
33  import java.util.Hashtable;
34  import java.util.LinkedHashSet;
35  import java.util.Map;
36  import java.util.Set;
37  
38  import org.perfidix.AbstractConfig;
39  import org.perfidix.annotation.AfterEachRun;
40  import org.perfidix.annotation.AfterLastRun;
41  import org.perfidix.annotation.BeforeEachRun;
42  import org.perfidix.annotation.BeforeFirstRun;
43  import org.perfidix.annotation.Bench;
44  import org.perfidix.exceptions.PerfidixMethodCheckException;
45  import org.perfidix.exceptions.PerfidixMethodInvocationException;
46  import org.perfidix.meter.AbstractMeter;
47  import org.perfidix.result.BenchmarkResult;
48  
49  /**
50   * Corresponding to each method, an executor is launched to execute {@link BeforeFirstRun},
51   * {@link BeforeEachRun}, {@link AfterEachRun} and {@link AfterLastRun} classes. To store the data if the
52   * single-execute before
53   * classes have been executed, this class is implemented as a singleton to store
54   * this information related to the method. All the data comes from the {@link BenchmarkMethod} class.
55   * 
56   * @author Sebastian Graf, University of Konstanz
57   */
58  public final class BenchmarkExecutor {
59  
60      /**
61       * Static mapping for all methods to be executed because of the single-runs
62       * before/after methods.
63       */
64      private static final Map<BenchmarkMethod, BenchmarkExecutor> EXECUTOR =
65          new Hashtable<BenchmarkMethod, BenchmarkExecutor>();
66  
67      /** Static Mapping of runs to occur for each BenchmarkMethod.} */
68      private static final Map<BenchmarkMethod, Integer> RUNS = new Hashtable<BenchmarkMethod, Integer>();
69  
70      /** Set with all meters to be benched automatically. */
71      private static final Set<AbstractMeter> METERS_TO_BENCH = new LinkedHashSet<AbstractMeter>();
72  
73      /** Result for all Benchmarks. */
74      private static BenchmarkResult BENCHRES;
75  
76      /** Config for all Benchmarks. */
77      private static AbstractConfig CONFIG;
78  
79      /** Boolean to be sure that the beforeFirstRun was not executed yet. */
80      private transient boolean beforeFirstRun;
81  
82      /**
83       * Corresponding BenchmarkElement of this executor to get the before/after
84       * methods.
85       */
86      private transient final BenchmarkMethod element;
87  
88      /**
89       * Private constructor, just setting the booleans and one element to get the
90       * before/after methods.
91       * 
92       * @param paramElement
93       *            BenchmarkElement to provide easy access to the before/after
94       *            methods.
95       */
96      private BenchmarkExecutor(final BenchmarkMethod paramElement) {
97          beforeFirstRun = false;
98          element = paramElement;
99      }
100 
101     /**
102      * Getting the executor corresponding to a BenchmarkElement.
103      * 
104      * @param meth
105      *            for the executor. If the underlaying {@link Method} was not
106      *            registered, a new mapping-entry will be created.
107      * @return the BenchmarkExecutor corresponding to the Method of the
108      *         BenchmarkElement
109      */
110     public static BenchmarkExecutor getExecutor(final BenchmarkElement meth) {
111         if (BENCHRES == null) {
112             throw new IllegalStateException("Call initialize method first!");
113         }
114 
115         // check if new instance needs to be created
116         if (!EXECUTOR.containsKey(meth.getMeth())) {
117             EXECUTOR.put(meth.getMeth(), new BenchmarkExecutor(meth.getMeth()));
118             int runsOnAnno = BenchmarkMethod.getNumberOfAnnotatedRuns(meth.getMeth().getMethodToBench());
119             if (runsOnAnno < 0) {
120                 runsOnAnno = CONFIG.getRuns();
121             }
122             RUNS.put(meth.getMeth(), runsOnAnno);
123         }
124 
125         // returning the executor
126         return EXECUTOR.get(meth.getMeth());
127 
128     }
129 
130     /**
131      * Initializing the executor.
132      * 
133      * @param config
134      *            to be benched
135      * @param result
136      *            to be stored to
137      */
138     public static void initialize(final AbstractConfig config, final BenchmarkResult result) {
139         METERS_TO_BENCH.clear();
140         METERS_TO_BENCH.addAll(Arrays.asList(config.getMeters()));
141         EXECUTOR.clear();
142         BENCHRES = result;
143         CONFIG = config;
144 
145     }
146 
147     /**
148      * Executing the {@link BeforeFirstRun}-annotated methods (if still wasn't)
149      * and the {@link BeforeEachRun} methods.
150      * 
151      * @param obj
152      *            the object of the class where the bench runs currently in.
153      */
154     public void executeBeforeMethods(final Object obj) {
155 
156         // invoking once the beforeFirstRun-method
157         if (!beforeFirstRun) {
158             beforeFirstRun = true;
159 
160             Method[] beforeFirst = null;
161             try {
162                 beforeFirst = element.findBeforeFirstRun();
163             } catch (final PerfidixMethodCheckException e) {
164                 BENCHRES.addException(e);
165             }
166             if (beforeFirst.length != 0) {
167                 checkAndExectuteBeforeAfters(obj, BeforeFirstRun.class, beforeFirst);
168             }
169         }
170 
171         // invoking the beforeEachRun-method
172         Method[] beforeEach = null;
173         try {
174             beforeEach = element.findBeforeEachRun();
175         } catch (final PerfidixMethodCheckException e) {
176             BENCHRES.addException(e);
177         }
178         if (beforeEach.length != 0) {
179             checkAndExectuteBeforeAfters(obj, BeforeEachRun.class, beforeEach);
180         }
181 
182     }
183 
184     /**
185      * Execution of bench method. All data is stored corresponding to the
186      * meters.
187      * 
188      * @param objToExecute
189      *            the instance of the benchclass where the method should be
190      *            executed with.
191      */
192     public void executeBench(final Object objToExecute) {
193 
194         final double[] meterResults = new double[METERS_TO_BENCH.size()];
195 
196         final Method meth = element.getMethodToBench();
197 
198         int meterIndex1 = 0;
199         int meterIndex2 = 0;
200         for (final AbstractMeter meter : METERS_TO_BENCH) {
201             meterResults[meterIndex1] = meter.getValue();
202             meterIndex1++;
203         }
204 
205         final PerfidixMethodInvocationException res = invokeMethod(objToExecute, Bench.class, meth);
206 
207         for (final AbstractMeter meter : METERS_TO_BENCH) {
208             meterResults[meterIndex2] = meter.getValue() - meterResults[meterIndex2];
209             meterIndex2++;
210         }
211 
212         if (res == null) {
213             meterIndex1 = 0;
214             for (final AbstractMeter meter : METERS_TO_BENCH) {
215                 BENCHRES.addData(element.getMethodToBench(), meter, meterResults[meterIndex1]);
216                 meterIndex1++;
217             }
218         } else {
219             BENCHRES.addException(res);
220         }
221 
222     }
223 
224     /**
225      * Executing the {@link AfterLastRun}-annotated methods (if still wasn't)
226      * and the {@link AfterEachRun} methods.
227      * 
228      * @param obj
229      *            the object of the class where the bench runs currently in.
230      */
231     public void executeAfterMethods(final Object obj) {
232         int runs = RUNS.get(element);
233         runs--;
234         RUNS.put(element, runs);
235 
236         // invoking once the beforeFirstRun-method
237         if (RUNS.get(element) == 0) {
238             Method[] afterLast = null;
239             try {
240                 afterLast = element.findAfterLastRun();
241             } catch (final PerfidixMethodCheckException e) {
242                 BENCHRES.addException(e);
243             }
244             if (afterLast.length != 0) {
245                 checkAndExectuteBeforeAfters(obj, AfterLastRun.class, afterLast);
246             }
247 
248         }
249 
250         // invoking the beforeEachRun-method
251         Method[] afterEach = null;
252         try {
253             afterEach = element.findAfterEachRun();
254         } catch (final PerfidixMethodCheckException e) {
255             BENCHRES.addException(e);
256         }
257         if (afterEach != null) {
258             checkAndExectuteBeforeAfters(obj, AfterEachRun.class, afterEach);
259         }
260 
261     }
262 
263     /**
264      * Checking and executing several before/after methods.
265      * 
266      * @param obj
267      *            on which the execution should take place
268      * @param meth
269      *            to be executed
270      * @param anno
271      *            the related annotation
272      */
273     private void checkAndExectuteBeforeAfters(final Object obj, final Class<? extends Annotation> anno,
274         Method... meths) {
275         final PerfidixMethodCheckException checkExc = checkMethod(obj, anno, meths);
276         if (checkExc == null) {
277             final PerfidixMethodInvocationException invoExc = invokeMethod(obj, anno, meths);
278             if (invoExc != null) {
279                 BENCHRES.addException(invoExc);
280             }
281 
282         } else {
283             BENCHRES.addException(checkExc);
284         }
285     }
286 
287     /**
288      * Method to invoke a reflective invokable method.
289      * 
290      * @param obj
291      *            on which the execution takes place
292      * @param meth
293      *            to be executed
294      * @param relatedAnno
295      *            related annotation for the execution
296      * @return {@link PerfidixMethodInvocationException} if invocation fails,
297      *         null otherwise.
298      */
299     public static PerfidixMethodInvocationException invokeMethod(final Object obj,
300         final Class<? extends Annotation> relatedAnno, Method... meths) {
301         final Object[] args = {};
302         for (Method meth : meths) {
303             try {
304                 meth.invoke(obj, args);
305             } catch (final IllegalArgumentException e) {
306                 return new PerfidixMethodInvocationException(e, meth, relatedAnno);
307             } catch (final IllegalAccessException e) {
308                 return new PerfidixMethodInvocationException(e, meth, relatedAnno);
309             } catch (final InvocationTargetException e) {
310                 return new PerfidixMethodInvocationException(e.getCause(), meth, relatedAnno);
311             }
312         }
313         return null;
314     }
315 
316     /**
317      * Checking a method if it is reflective executable and if the mapping to
318      * the object fits.
319      * 
320      * @param obj
321      *            on which the execution takes place
322      * @param meths
323      *            to be checked
324      * @param anno
325      *            the related annotation for the check
326      * @return {@link PerfidixMethodCheckException} if something is wrong in the
327      *         mapping, null otherwise.
328      */
329     public static PerfidixMethodCheckException checkMethod(final Object obj,
330         final Class<? extends Annotation> anno, Method... meths) {
331 
332         for (Method meth : meths) {
333             // check if the class of the object to be executed has the given method
334             boolean classMethodCorr = false;
335             for (final Method methodOfClass : obj.getClass().getDeclaredMethods()) {
336                 if (methodOfClass.equals(meth)) {
337                     classMethodCorr = true;
338                 }
339             }
340 
341             if (!classMethodCorr) {
342                 return new PerfidixMethodCheckException(new IllegalStateException(new StringBuilder(
343                     "Object to execute ").append(obj).append(" is not having a Method named ").append(meth)
344                     .append(".").toString()), meth, anno);
345             }
346 
347             // check if the method is reflected executable
348             if (!BenchmarkMethod.isReflectedExecutable(meth, anno)) {
349                 return new PerfidixMethodCheckException(new IllegalAccessException(new StringBuilder(
350                     "Method to execute ").append(meth).append(" is not reflected executable.").toString()),
351                     meth, anno);
352             }
353         }
354         return null;
355     }
356 
357 }