View Javadoc
1   /*
2    * Copyright 2005 the original author or authors.
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 net.sf.sensor.timer;
18  
19  import java.util.ArrayList;
20  import java.util.Iterator;
21  import java.util.LinkedHashMap;
22  import java.util.LinkedHashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  
28  /***
29   * Internal representation of a "timer" that manages the statistical data
30   * in a threadsafe manner. This class should never be used directly but
31   * always through the {@link TimerRuntime}. For that reason, it is completely
32   * package private.
33   * <p>
34   * TimerData instances form a tree structure that represents the original
35   * execution call stack. This means that there can be many TimerData instances
36   * with the same id. The {@link TimerRuntime} offers different ways of 
37   * presenting this internal tree structure, including a flattened representation
38   * that involves adding (or merging) the data from all TimerData instances that
39   * match a specific id.
40   * 
41   * @author Age Mooy
42   */
43  class TimerData {
44  
45    // ==========================================================================
46    // Fields
47    // ==========================================================================
48    
49    /*** The associated {@link TimerRuntime}. */
50    private TimerRuntime runtime = null;
51    
52    /*** The id that uniquely identifies an instance of this class. */
53    private final String id;
54    
55    /*** The parent (as in tree node) TimerData instance. */
56    private TimerData parent = null;
57    
58    /*** A collection of id - TimerData instance mappings that represents the children of this tree node. */
59    private Map children = new LinkedHashMap();
60  
61    /*** A Set&lt;String&gt; of labels associated with this instance. */
62    private Set labels = new LinkedHashSet();
63    
64    /*** Lazy-initialized hierarchical (tree) view on this instance. */
65    private TimerStatistics timerStatistics;
66    
67    
68    // ==========================================================================
69    // Statistics Fields
70    // ==========================================================================
71    
72    /*** The number of hits. */
73    private long numberOfHits = 0L;
74    
75    /*** The total time (in ms). */
76    private long totalTime = 0L;
77    
78    /*** The average time (in ms). */
79    private long averageTime = 0L;
80    
81    /*** The minimum time (in ms). */
82    private long minimumTime = 0L;
83    
84    /*** The maximum time (in ms). */
85    private long maximumTime = 0L;
86    
87    /*** The standard deviation (in ms). */
88    private long standardDeviation = 0L;
89    
90    /*** The sum of the squares of all measurements, which is used in calculating the standard deviation. */
91    private long sumOfSquaresOfTimes = 0L;
92    
93    /*** The time (in ms since 01-01-1979 00:00 UTC) of the first update. */
94    private long timeOfFirstUpdate = 0L;
95    
96    /*** The time (in ms since 01-01-1979 00:00 UTC) of the last update. */
97    private long timeOfLastUpdate = 0L;
98    
99    
100   // ==========================================================================
101   // Constructors
102   // ==========================================================================
103   
104   /***
105    * Creates a new TimerData instance uniquely identified by the specified id.
106    * 
107    * @param runtime the associated {@link TimerRuntime}.
108    * @param id the id that uniquely identifies an instance of this class.
109    */
110   TimerData(TimerRuntime runtime, String id) {
111     this(runtime, null, id);
112   }
113 
114   /***
115    * Creates a new TimerData instance uniquely identified by the specified id
116    * and with the specified TimerData instance set as its parent.
117    * This constructor is private because it should only be called from the
118    * {@link #getOrCreateChild(String)} method, which ensures the proper
119    * parent-child relationships are set. The only way to create child instances
120    * is through the {@link #getOrCreateChild(String)} method.
121    * 
122    * @param runtime the associated {@link TimerRuntime}.
123    * @param parent the parent TimerData instance.
124    * @param id the id that uniquely identifies an instance of this class.
125    */
126   private TimerData(TimerRuntime runtime, TimerData parent, String id) {
127     this.runtime = runtime;
128     this.parent  = parent;
129     this.id   = id;
130   }
131 
132   
133   // ==========================================================================
134   // Basic API
135   // ==========================================================================
136   
137   /***
138    * Returns the id.
139    * @return Returns the id.
140    */
141   String getId() {
142     return id;
143   }
144 
145   /***
146    * @return the Set&lt;String&gt; of labels associated with this instance.
147    */
148   Set getLabels() {
149     return labels;
150   }
151   
152   /***
153    * @param label the new label thst should be associated with this instance.
154    */
155   void addLabel(String label) {
156     if (label != null) {
157       labels.add(label);
158     }    
159   }
160   
161   /***
162    * Resets all statistics data and all registered child instances.
163    * 
164    * @todo should we remove the children or reset thier values ???
165    */
166   synchronized void reset() {
167     numberOfHits = 0L;
168     totalTime    = 0L;
169     averageTime  = 0L;
170     minimumTime  = 0L;
171     maximumTime  = 0L;
172     
173     sumOfSquaresOfTimes = 0L;
174     standardDeviation = 0L;
175     
176     timeOfFirstUpdate = 0L;
177     timeOfLastUpdate  = 0L;
178     
179     // reset all children
180     for (Iterator i = children.values().iterator(); i.hasNext();) {
181       ((TimerData) i.next()).reset();
182     }
183   }
184   
185   
186   // ==========================================================================
187   // Tree structure-related methods (package private) 
188   // ==========================================================================
189   
190   /***
191    * Returns the parent.
192    * @return Returns the parent.
193    */
194   TimerData getParent() {
195     return parent;
196   }
197   
198   /***
199    * Returns true if this instance has a (non-null) parent instance.
200    * @return true if this instance has a (non-null) parent instance.
201    */
202   boolean hasParent() {
203     return parent != null;
204   }
205   
206   /***
207    * Returns a Collection&lt;TimerData&gt; of all registered child instances.
208    * @return a Collection&lt;TimerData&gt; of all registered child instances.
209    */
210   Iterator getChildren() {
211     return children.values().iterator();
212   }
213   
214   /***
215    * Returns true if this instance has a (non-null) parent instance.
216    * @return true if this instance has a (non-null) parent instance.
217    */
218   boolean hasChildren() {
219     return children.size() > 0;
220   }
221   
222   /***
223    * Retrieves the child matching the specified id or creates a new
224    * child if it doesn't exist yet. This method is synchronized to prevent
225    * double creates, overwrites, race conditions, etc. There should always
226    * be exactly one child that matches a id.
227    * 
228    * @param id the id that should match a child TimerData.
229    * @return the (possibly newly created) child TimerData that matches the specified id.
230    */
231   synchronized TimerData getOrCreateChild(String id) {
232     TimerData child = (TimerData) children.get(id);
233     
234     if (child == null) {
235       child = new TimerData(runtime, this, id);
236       
237       children.put(id, child);
238     }
239     
240     return child;
241   }
242   
243   
244   // ==========================================================================
245   // Management of Timers and TimerStatistics
246   // ==========================================================================
247   
248   /***
249    * Creates a new Timer instance linked to this instance of TimerData.
250    * @return a new Timer instance linked to this instance of TimerData.
251    */
252   Timer createTimer() {
253     return new Timer(this);
254   }
255   
256   /***
257    * Callback method used by Timer instances to report back a timing measurement.
258    * @param timeTaken the time (in milliseconds) it took to execute the code that was being measured.
259    */
260   synchronized void updateFromTimer(long timeTaken) {
261     // tell the runtime (which keeps track of the current call stack) that this timer was stopped
262     runtime.timerStopped();
263     
264     // update the internal statistics
265     numberOfHits++;
266     totalTime           = totalTime + timeTaken;
267     averageTime         = totalTime / numberOfHits;
268     sumOfSquaresOfTimes = sumOfSquaresOfTimes + (timeTaken * timeTaken);
269     
270     // the time of the last update is always the last call to this method.
271     timeOfLastUpdate = System.currentTimeMillis();
272     
273     if (numberOfHits == 1L) {
274       minimumTime      = timeTaken;
275       maximumTime      = timeTaken;
276       timeOfFirstUpdate = timeOfLastUpdate;
277     } else {
278       if (timeTaken < minimumTime) {
279         minimumTime = timeTaken;
280       }
281       
282       if (timeTaken > maximumTime) {
283         maximumTime = timeTaken;
284       }
285     }
286     
287     // calculate the standard deviation
288     long nMinus1 = (numberOfHits <= 1) ? 1 : numberOfHits - 1; // avoid 0 divides
289     long numerator = sumOfSquaresOfTimes - ((totalTime * totalTime) / numberOfHits);
290     
291     standardDeviation = (long) java.lang.Math.sqrt(numerator / nMinus1);
292   }
293   
294   /***
295    * Returns a hierarchical statistical view of this instance of TimerData.
296    * @return a hierarchical statistical view of this instance of TimerData.
297    */
298   TimerStatistics getStatistics() {
299     if (timerStatistics == null) {
300       timerStatistics = new TimerStatistics(this);
301     }
302     
303     // refresh the statistics
304     refreshStatistics();
305     
306     return timerStatistics;
307   }
308   
309   /***
310    * Callback method used by {@link TimerStatistics#refresh()} to extract
311    * the latest data from this instance.
312    * This method is synchronized to prevent half-updated data, etc.
313    * The reason for this elaborate callback mechanism is to get all internal
314    * values out of this instance in one synchronized call instead of calling
315    * a number of synchronized getters from the statistics class. The latter
316    * could lead to inconsistent data.
317    */
318   synchronized void refreshStatistics() {
319     timerStatistics.refreshFromData(
320         numberOfHits, 
321         totalTime, 
322         averageTime,
323         standardDeviation,
324         minimumTime, 
325         maximumTime,
326         timeOfFirstUpdate,
327         timeOfLastUpdate,
328         sumOfSquaresOfTimes,
329         newChildStatisticsSnapshotList());
330   }
331 
332   /***
333    * @return a new List&lt;TimerStatistics&gt; containing an instance of
334    *         TimerStatistics for each currently registered child.
335    */
336   private synchronized List newChildStatisticsSnapshotList() {
337     List childStatistics = new ArrayList();
338     
339     for (Iterator children = getChildren(); children.hasNext();) {
340       childStatistics.add(((TimerData) children.next()).getStatistics());
341     }
342     
343     return childStatistics;
344   }
345   
346   
347   
348   
349   // ==========================================================================
350   // Internal statistics getters.
351   // FOR TESTING PURPOSES ONLY !! 
352   // THESE METHODS ARE NOT THREAD SAFE !!
353   // ==========================================================================
354   
355   /***
356    * Returns the numberOfHits.
357    * <b>FOR TESTING PURPOSES ONLY !! NOT THREAD SAFE !!</b>
358    * @return Returns the numberOfHits.
359    */
360   long getNumberOfHits() {
361     return numberOfHits;
362   }
363   
364   /***
365    * Returns the totalTime.
366    * <b>FOR TESTING PURPOSES ONLY !! NOT THREAD SAFE !!</b>
367    * @return Returns the totalTime.
368    */
369   long getTotalTime() {
370     return totalTime;
371   }
372   
373   /***
374    * Returns the averageTime.
375    * <b>FOR TESTING PURPOSES ONLY !! NOT THREAD SAFE !!</b>
376    * @return Returns the averageTime.
377    */
378   long getAverageTime() {
379     return averageTime;
380   }
381   
382   /***
383    * Returns the minimumTime.
384    * <b>FOR TESTING PURPOSES ONLY !! NOT THREAD SAFE !!</b>
385    * @return Returns the minimumTime.
386    */
387   long getMinimumTime() {
388     return minimumTime;
389   }
390   
391   /***
392    * Returns the maximumTime.
393    * <b>FOR TESTING PURPOSES ONLY !! NOT THREAD SAFE !!</b>
394    * @return Returns the maximumTime.
395    */
396   long getMaximumTime() {
397     return maximumTime;
398   }
399   
400   /***
401    * Returns the standardDeviation.
402    * @return Returns the standardDeviation.
403    */
404   long getStandardDeviation() {
405     return standardDeviation;
406   }
407 
408   /***
409    * Returns the sumOfSquaresOfTimes.
410    * @return Returns the sumOfSquaresOfTimes.
411    */
412   long getSumOfSquaresOfTimes() {
413     return sumOfSquaresOfTimes;
414   }
415 
416   /***
417    * Returns the timeOfFirstUpdate.
418    * @return Returns the timeOfFirstUpdate.
419    */
420   long getTimeOfFirstUpdate() {
421     return timeOfFirstUpdate;
422   }
423 
424   /***
425    * Returns the timeOfLastUpdate.
426    * @return Returns the timeOfLastUpdate.
427    */
428   long getTimeOfLastUpdate() {
429     return timeOfLastUpdate;
430   }
431   
432   
433   // ==========================================================================
434   // Standard Object method overrides and simple debugging methods
435   // ==========================================================================
436 
437   /***
438    * @see java.lang.Object#toString()
439    */
440   public String toString() {
441     return "Path: "                       + getTreePath()
442          + ", NumberOfHits: "             + numberOfHits
443          + ", Total Time: "               + totalTime
444          + ", Average Time: "             + averageTime
445          + ", Standard deviation: "       + standardDeviation
446          + ", Minimum Time: "             + minimumTime
447          + ", Maximum Time: "             + maximumTime
448          + ", Time of first update: "     + timeOfFirstUpdate
449          + ", Time of last update: "      + timeOfLastUpdate
450          + ", Sum of squares of times: "  + sumOfSquaresOfTimes;
451   }
452   
453   /***
454    * For testing correct tree-structure behaviour. 
455    * @return a String representing the tree path from the root node to this node,
456    *         formatted like this: "/root/path/to/this/instance" where the path
457    *         entries are the labels of the tree nodes.
458    */
459   String getTreePath() {
460     if (hasParent()) {
461       return getParent().getTreePath() + "/" + getId();
462     } else {
463       return "/" + getId();
464     }
465   }
466 
467 }