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.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    // Fields
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    // Construction
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    // Timer API
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      // Implementation:
82      // - push the new TimerData instance on top of the call stack
83      // - get a Timer instance linked to the TimerData
84      // - start the Timer and return it
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   // TimerStatistics API
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   // Packge private callbacks
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   // Implementation utilities
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 }