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.List;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23
24 /***
25 * Main entry point for working with {@link Timer Timers} and {@link TimerStatistics}.
26 *
27 * @todo refactor the callstack threadlocal to be static so we don't need the timerStopped callback anymore and timerdatas can update the callstack themselves
28 * @todo refactor the call stack to no longer use a shared dummy parent for root timers
29 * @todo add support for mapping ids (by regular expression ?) to (sets of) labels declaratively
30 * @todo add spring context awareness to a special version/wrapper of this class so it can lookup label mappers, etc.
31 *
32 * @author Age Mooy
33 */
34 public class TimerRuntime {
35
36 /***
37 *
38 */
39 public static final String DEFAULT_ROOT_TIMER_LABEL = "Timers";
40
41
42
43
44
45
46 /***
47 * The root of the TimerData tree. All timing data is stored using this instance.
48 */
49 private final TimerData rootTimerData;
50
51 /***
52 * The thread-local call stack manager that keeps track of the execution order
53 * of timers.
54 */
55 private final TimerDataCallStack timerDataCallStack;
56
57
58
59
60
61
62 /***
63 * todo get rid of this constructor
64 */
65 public TimerRuntime() {
66 rootTimerData = new TimerData(this, DEFAULT_ROOT_TIMER_LABEL);
67 timerDataCallStack = new TimerDataCallStack(rootTimerData);
68 }
69
70
71
72
73
74
75 /***
76 * Creates a new {@link Timer} instance for the specified id and starts it.
77 * @param id an id to uniquely identify the {@link Timer}.
78 * @return the started {@link Timer} instance.
79 */
80 public Timer getTimer(String id) {
81
82
83
84
85 return timerDataCallStack.push(id).createTimer();
86 }
87
88 /***
89 * Creates a new {@link Timer} instance for the specified id and starts it.
90 * @param id an id to uniquely identify the {@link Timer}.
91 * @param label a label that will be associated with the {@link Timer}.
92 * @return the started {@link Timer} instance.
93 */
94 public Timer getTimer(String id, String label) {
95 Timer timer = getTimer(id);
96
97 timer.addLabel(label);
98
99 return timer;
100 }
101
102 /***
103 * Resets all data. This clears the entire set of data collected until
104 * now. Use with caution !!
105 */
106 public void reset() {
107 rootTimerData.reset();
108 }
109
110
111
112
113
114
115 /***
116 * Returns a List<TimerStatistics> representing the statistics for all
117 * currently registered timers.
118 * @return a List<TimerStatistics> representing the statistics for all
119 * currently registered timers.
120 */
121 public List getTimerStatistics() {
122 return rootTimerData.getStatistics().getChildren();
123 }
124
125
126
127
128
129
130 /***
131 * This callback method will be called from the {@link TimerData#updateFromTimer(long)}
132 * method to indicate that a {@link Timer} has stopped. This information is
133 * needed to keep the call stack up to date.
134 */
135 void timerStopped() {
136 timerDataCallStack.pop();
137 }
138
139
140
141
142
143
144 /***
145 * {@link ThreadLocal} extension that keeps track of the current call stack
146 * for each thread.
147 *
148 * @author Age Mooy
149 */
150 static final class TimerDataCallStack extends ThreadLocal {
151 /*** The local logger. */
152 private static Log log = LogFactory.getLog(TimerDataCallStack.class);
153
154 /*** The root node of our little tree. */
155 private final TimerData rootInstance;
156
157 /***
158 * Constructs a new instance and sets the root {@link TimerData}
159 * instance.
160 * @param rootInstance the {@link TimerData} to use as the root node of the tree.
161 */
162 public TimerDataCallStack(TimerData rootInstance) {
163 this.rootInstance = rootInstance;
164 }
165
166 /***
167 * Returns the registered root instance.
168 * @see java.lang.ThreadLocal#initialValue()
169 */
170 protected Object initialValue() {
171 return rootInstance;
172 }
173
174 /***
175 * Replaces the old value (a {@link TimerData} instance) on the call
176 * stack for the current thread with a new {@link TimerData} instance
177 * that is registered as a child of the old value. If the child instance
178 * does not exist yet, it will be created.
179 *
180 * @param label the label of the new {@link TimerData} instance we want
181 * to push on top of the call stack
182 * @return the {@link TimerData} instance that is now on top of the call
183 * stack.
184 */
185 public TimerData push(String label) {
186 TimerData oldValue = (TimerData) get();
187 TimerData newValue = oldValue.getOrCreateChild(label);
188
189 set(newValue);
190
191 if (log.isDebugEnabled()) {
192 log.debug("New call stack for thread '" + Thread.currentThread().getName() + "': " + newValue.getTreePath());
193 }
194
195 return newValue;
196 }
197
198 /***
199 * Replaces the {@link TimerData} instance currently on top of the call
200 * stack with its parent instance if it has a parent.
201 *
202 * @todo how to handle timers that are stopped in the wrong order (compared to their starting order) ???
203 */
204 public void pop() {
205 TimerData oldValue = (TimerData) get();
206
207 if (log.isDebugEnabled()) {
208 log.debug("Call stack popped for thread '" + Thread.currentThread().getName() + "'. Old value was: " + oldValue);
209 }
210
211 if (oldValue.hasParent()) {
212 set(oldValue.getParent());
213
214 if (log.isDebugEnabled()) {
215 log.debug("New call stack for thread '" + Thread.currentThread().getName() + "': " + oldValue.getParent().getTreePath());
216 }
217 }
218 }
219 }
220
221 }