1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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<String> 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
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
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
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<String> 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
180 for (Iterator i = children.values().iterator(); i.hasNext();) {
181 ((TimerData) i.next()).reset();
182 }
183 }
184
185
186
187
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<TimerData> of all registered child instances.
208 * @return a Collection<TimerData> 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
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
262 runtime.timerStopped();
263
264
265 numberOfHits++;
266 totalTime = totalTime + timeTaken;
267 averageTime = totalTime / numberOfHits;
268 sumOfSquaresOfTimes = sumOfSquaresOfTimes + (timeTaken * timeTaken);
269
270
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
288 long nMinus1 = (numberOfHits <= 1) ? 1 : numberOfHits - 1;
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
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<TimerStatistics> 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
351
352
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
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 }