1   /*
2    * Copyright (c) 2004-2008 QOS.ch
3    * All rights reserved.
4    * 
5    * Permission is hereby granted, free  of charge, to any person obtaining
6    * a  copy  of this  software  and  associated  documentation files  (the
7    * "Software"), to  deal in  the Software without  restriction, including
8    * without limitation  the rights to  use, copy, modify,  merge, publish,
9    * distribute,  sublicense, and/or sell  copies of  the Software,  and to
10   * permit persons to whom the Software  is furnished to do so, subject to
11   * the following conditions:
12   * 
13   * The  above  copyright  notice  and  this permission  notice  shall  be
14   * included in all copies or substantial portions of the Software.
15   * 
16   * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
17   * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
18   * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21   * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
22   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23   */
24  package org.slf4j.profiler;
25  
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.slf4j.Logger;
30  import org.slf4j.Marker;
31  import org.slf4j.MarkerFactory;
32  
33  // +  Profiler [BAS]
34  // |-- elapsed time            [doX]     0 milliseconds.
35  // |-- elapsed time        [doYYYYY]    56 milliseconds.
36  // |--+ Profiler Y
37  //    |-- elapsed time            [doZ]    21 milliseconds.
38  //    |-- elapsed time            [doZ]    21 milliseconds.
39  //    |-- Total elapsed time        [Y]    78 milliseconds.
40  // |-- elapsed time            [doZ]    21 milliseconds.
41  // |-- Total elapsed time      [BAS]    78 milliseconds.
42  
43  /**
44   * A poor man's profiler to measure the time elapsed performing 
45   * some lengthy task.
46   * 
47   * @author Ceki Gülcü
48   */
49  public class Profiler implements TimeInstrument {
50  
51    final static String PROFILER_MARKER_NAME = "PROFILER";
52  
53    final static int MIN_SW_NAME_LENGTH = 24;
54    final static int MIN_SW_ELAPSED_TIME_NUMBER_LENGTH = 9;
55    
56    final String name;
57    final StopWatch globalStopWatch;
58  
59    //List<StopWatch> stopwatchList = new ArrayList<StopWatch>();
60    List<TimeInstrument> childTimeInstrumentList = new ArrayList<TimeInstrument>();
61  
62    // optional field
63    ProfilerRegistry profilerRegistry;
64  //optional field
65    Logger logger;
66  
67    public Profiler(String name) {
68      this.name = name;
69      this.globalStopWatch = new StopWatch(name);
70    }
71  
72    public String getName() {
73      return name;
74    }
75  
76    public ProfilerRegistry getProfilerRegistry() {
77      return profilerRegistry;
78    }
79  
80    public void registerWith(ProfilerRegistry profilerRegistry) {
81      if (profilerRegistry == null) {
82        return;
83      }
84      this.profilerRegistry = profilerRegistry;
85      profilerRegistry.put(this);
86    }
87  
88    public Logger getLogger() {
89      return logger;
90    }
91  
92    public void setLogger(Logger logger) {
93      this.logger = logger;
94    }
95  
96    /**
97     * Starts a child stop watch and stops any previously started time instruments.
98     */
99    public void start(String name) {
100     stopLastTimeInstrument();
101     StopWatch childSW = new StopWatch(name);
102     childTimeInstrumentList.add(childSW);
103   }
104 
105   public Profiler startNested(String name) {
106     stopLastTimeInstrument();
107     Profiler nestedProfiler = new Profiler(name);
108     nestedProfiler.registerWith(profilerRegistry);
109     nestedProfiler.setLogger(logger);
110     childTimeInstrumentList.add(nestedProfiler);
111     return nestedProfiler;
112   }
113 
114   TimeInstrument getLastTimeInstrument() {
115     if (childTimeInstrumentList.size() > 0) {
116       return childTimeInstrumentList.get(childTimeInstrumentList.size() - 1);
117     } else {
118       return null;
119     }
120   }
121 
122   void stopLastTimeInstrument() {
123     TimeInstrument last = getLastTimeInstrument();
124     if (last != null) {
125       last.stop();
126     }
127   }
128 
129 //  void stopNestedProfilers() {
130 //    for (Object child : childTimeInstrumentList) {
131 //      if (child instanceof Profiler)
132 //        ((Profiler) child).stop();
133 //    }
134 //  }
135 
136   public long elapsedTime() {
137     return globalStopWatch.elapsedTime();
138   }
139   
140   public TimeInstrument stop() {
141     stopLastTimeInstrument();
142     globalStopWatch.stop();
143     return this;
144   }
145 
146   public TimeInstrumentStatus getStatus() {
147     return globalStopWatch.status;
148   }
149   
150   /**
151    * This method is used in tests.
152    */
153   void sanityCheck() throws IllegalStateException {
154     if(getStatus() != TimeInstrumentStatus.STOPPED) {
155       throw new IllegalStateException("time instrument ["+getName()+" is not stopped");
156     }
157     
158     long totalElapsed = globalStopWatch.elapsedTime();
159     long childTotal = 0;
160     
161     for(TimeInstrument ti: childTimeInstrumentList) {
162       childTotal += ti.elapsedTime();
163       if(ti.getStatus() != TimeInstrumentStatus.STOPPED) {
164         throw new IllegalStateException("time instrument ["+ti.getName()+" is not stopped");
165       }
166       if(ti instanceof Profiler) {
167         Profiler nestedProfiler = (Profiler) ti;
168         nestedProfiler.sanityCheck();
169       }
170     }
171     if(totalElapsed < childTotal) {
172       throw new IllegalStateException("children have a higher accumulated elapsed time");
173     }
174   }
175 
176   static String TOP_PROFILER_FIRST_PREFIX = "+";
177   static String NESTED_PROFILER_FIRST_PREFIX = "|---+";
178   static String TOTAL_ELAPSED =    " Total        ";
179   static String SUBTOTAL_ELAPSED = " Subtotal     ";
180   static String ELAPSED_TIME     = " elapsed time ";
181   
182 
183   public void print() {
184     System.out.println(toString());
185   }
186   
187   @Override
188   public String toString() {
189     DurationUnit du = Util.selectDurationUnitForDisplay(globalStopWatch);
190     return buildProfilerString(du, TOP_PROFILER_FIRST_PREFIX, TOTAL_ELAPSED, "");
191   }
192   
193   public void log() {
194     Marker profilerMarker = MarkerFactory.getMarker(PROFILER_MARKER_NAME);
195     if(logger == null) {
196       throw new NullPointerException("If you invoke the log() method, then you must associate a logger with this profiler.");
197     }
198     if (logger.isDebugEnabled(profilerMarker)) {
199       DurationUnit du = Util.selectDurationUnitForDisplay(globalStopWatch);
200       String r = buildProfilerString(du, TOP_PROFILER_FIRST_PREFIX, TOTAL_ELAPSED, "");
201       logger.debug(profilerMarker, SpacePadder.LINE_SEP+r);
202     }
203   }
204   
205   private String buildProfilerString(DurationUnit du, String firstPrefix, String label, String indentation) {
206     StringBuffer buf = new StringBuffer();
207 
208     buf.append(firstPrefix);
209     buf.append(" Profiler [");
210     buf.append(name);
211     buf.append("]");
212     buf.append(SpacePadder.LINE_SEP);
213     for (TimeInstrument child : childTimeInstrumentList) {
214       if (child instanceof StopWatch) {
215         buildStopWatchString(buf, du, ELAPSED_TIME, indentation, (StopWatch) child);
216       } else if (child instanceof Profiler) {
217         Profiler profiler = (Profiler) child;
218         String subString = profiler
219             .buildProfilerString(du, NESTED_PROFILER_FIRST_PREFIX, SUBTOTAL_ELAPSED, indentation + "    ");
220         buf.append(subString);
221         buildStopWatchString(buf, du, ELAPSED_TIME, indentation, profiler.globalStopWatch);
222       }
223     }
224     buildStopWatchString(buf, du, label, indentation, globalStopWatch);
225     return buf.toString();
226   }
227 
228   private static void buildStopWatchString(StringBuffer buf, DurationUnit du,
229       String prefix, String indentation, StopWatch sw) {
230 
231     buf.append(indentation);
232     buf.append("|--");
233     buf.append(prefix);
234     SpacePadder.leftPad(buf, "[" + sw.getName() + "]", MIN_SW_NAME_LENGTH);
235     buf.append(" ");
236     String timeStr = Util.durationInDurationUnitsAsStr(sw.elapsedTime(),
237         du);
238     SpacePadder.leftPad(buf, timeStr, MIN_SW_ELAPSED_TIME_NUMBER_LENGTH);
239     buf.append(" ");
240     Util.appendDurationUnitAsStr(buf, du);
241     buf.append(SpacePadder.LINE_SEP);
242   }
243 
244 }