View Javadoc
1   /*
2    * #%L
3    * Nuiton Utils
4    * %%
5    * Copyright (C) 2004 - 2010 CodeLutin
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  
23  package org.nuiton.util;
24  
25  import org.apache.commons.collections.primitives.ArrayLongList;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.TreeMap;
34  
35  /**
36   * Cette classe permet de faire des analyses sur les appels de methode
37   * En debut de methode on appelle la methode {@link #enter}, et en fin de methode
38   * la methode {@link #exit}.
39   * <p>
40   * Ensuite on peut récuperer les statistiques par Thread ou de tous les threads
41   * <p>
42   * On a comme statistique
43   * <ul>
44   * <li> le temps d'execution
45   * <li> la memore utilisé
46   * <li> le nombre d'appels
47   * </ul>
48   *
49   * @author Benjamin Poussin - poussin@codelutin.com
50   * @see TimeLog
51   * Created: 25 aout 2005 14:09:22 CEST
52   */
53  public class CallAnalyse { // CallAnalyse
54  
55      /**
56       * Logger.
57       */
58      private static final Log log = LogFactory.getLog(CallAnalyse.class);
59  
60      static private List<ThreadStatistics> listThreadStatistics =
61              new ArrayList<ThreadStatistics>();
62  
63      static private ThreadLocal<ThreadStatistics> stats =
64              new ThreadLocal<ThreadStatistics>() {
65                  @Override
66                  protected synchronized ThreadStatistics initialValue() {
67                      ThreadStatistics result = new ThreadStatistics();
68                      listThreadStatistics.add(result);
69                      return result;
70                  }
71              };
72  
73      /**
74       * Permet d'activer les statistiques, pour le thread courant
75       */
76      public static void activate() {
77          stats.get().setActivated(true);
78      }
79  
80      /**
81       * Permet de desactiver les statistiques, pour le thread courant
82       */
83      public static void desactivate() {
84          stats.get().setActivated(false);
85      }
86  
87      /**
88       * Permet de savoir si les statistiques sont activées ou non, pour le
89       * thread courant
90       * @return FIXME
91       */
92      public static boolean isActivate() {
93          return stats.get().getActivated();
94      }
95  
96      /**
97       * @param name le nom de l'appel a monitorer
98       */
99      public static void enter(String name) {
100         ThreadStatistics t = stats.get();
101         if (t.getActivated()) {
102             t.get(name).enter();
103         }
104     }
105 
106     /**
107      * Indique la sortie de l'appel, name doit avoir ete utilisé lors d'un enter
108      *
109      * @param name le nom de l'appel a monitorer, doit etre identique a
110      *             celui utilisé pour la methode enter
111      */
112     public static void exit(String name) {
113         ThreadStatistics t = stats.get();
114         if (t.getActivated()) {
115             t.get(name).exit();
116         }
117     }
118 
119     /**
120      * @return the statistics for the current thread
121      */
122     public static ThreadStatistics getThreadStatistics() {
123         return stats.get();
124     }
125 
126     /**
127      * @return the statistics for all threads
128      */
129     public static List<ThreadStatistics> getAllThreadStatistics() {
130         return listThreadStatistics;
131     }
132 
133     public static class ThreadStatistics extends TreeMap<String, CallStatistics> {
134         /**  */
135         private static final long serialVersionUID = -36051448464013504L;
136 
137         protected boolean activated = false;
138 
139         public boolean getActivated() {
140             return activated;
141         }
142 
143         public void setActivated(boolean activated) {
144             this.activated = activated;
145         }
146 
147         public CallStatistics get(String name) {
148             CallStatistics result = super.get(name);
149             if (result == null) {
150                 put(name, result = new CallStatistics(name));
151             }
152             return result;
153         }
154 
155         public String toString() {
156             return values().toString();
157         }
158     }
159 
160     /**
161      * This method will get all the statistics from all the threads and put it
162      * all together in a {@link Map} which key is the name of the watched
163      * element and the value is its instance of {@link CallStatisticsSummary}
164      *
165      * @return A map with all collected statistics
166      */
167     public static Map<String, CallStatisticsSummary> getSummary() {
168         Map<String, CallStatisticsSummary> results = new HashMap<String, CallStatisticsSummary>();
169         for (ThreadStatistics stats : CallAnalyse.getAllThreadStatistics()) {
170             for (String name : stats.keySet()) {
171                 CallStatisticsSummary stat = results.get(name);
172                 if (stat == null) {
173                     stat = new CallStatisticsSummary(name);
174                     results.put(name, stat);
175                 }
176                 stat.addCallStats(stats.get(name));
177             }
178         }
179         return results;
180     }
181 
182     /**
183      * CallStatistics is the class which handles values on excecution time and
184      * memory usage.
185      * Each CallStatistics object is for one particular name.
186      *
187      * @author bpoussin
188      */
189     public static class CallStatistics implements Cloneable {
190         protected String name = null;
191 
192         protected long calls = 0;
193 
194         protected long minTime = Long.MAX_VALUE;
195 
196         protected long maxTime = Long.MIN_VALUE;
197 
198         protected long sumTime = 0;
199 
200         protected long minMemory = Long.MAX_VALUE;
201 
202         protected long maxMemory = Long.MIN_VALUE;
203 
204         protected long sumMemory = 0;
205 
206         /**
207          * pile contenant le temps de appel, util pour les appels recursifs
208          */
209         protected ArrayLongList times = new ArrayLongList();
210 
211         protected ArrayLongList memories = new ArrayLongList();
212 
213         protected Runtime runtime = Runtime.getRuntime();
214 
215         public CallStatistics(String name) {
216             this.name = name;
217         }
218 
219         public void enter() {
220             times.add(System.nanoTime());
221             memories.add(getMemory());
222         }
223 
224         public void exit() {
225             calls++;
226 
227             if (times.size() == 0) {
228                 log.info("To many exit call for " + name);
229                 return;
230             }
231             long time = times.removeElementAt(times.size() - 1);
232             time = System.nanoTime() - time;
233             if (time < minTime || minTime == Long.MAX_VALUE) {
234                 minTime = time;
235             }
236             if (time > maxTime) {
237                 maxTime = time;
238             }
239             sumTime += time;
240 
241             long memory = memories.removeElementAt(memories.size() - 1);
242             memory = getMemory() - memory;
243             if (memory < minMemory || minMemory == Long.MAX_VALUE) {
244                 minMemory = memory;
245             }
246             if (memory > maxMemory) {
247                 maxMemory = memory;
248             }
249             sumMemory += memory;
250         }
251 
252         public String getName() {
253             return name;
254         }
255 
256         public long getCalls() {
257             return calls;
258         }
259 
260         public long getMinTime() {
261             return minTime;
262         }
263 
264         public long getMaxTime() {
265             return maxTime;
266         }
267 
268         public long getSumTime() {
269             return sumTime;
270         }
271 
272         public long getAvgTime() {
273             if (calls == 0) {
274                 return 0;
275             } else {
276                 return sumTime / calls;
277             }
278         }
279 
280         public long getMinMemory() {
281             return minMemory;
282         }
283 
284         public long getMaxMemory() {
285             return maxMemory;
286         }
287 
288         public long getSumMemory() {
289             return sumMemory;
290         }
291 
292         public long getAvgMemory() {
293             if (calls == 0) {
294                 return 0;
295             } else {
296                 return sumMemory / calls;
297             }
298         }
299 
300         protected long getMemory() {
301             // runtime.gc();
302             return runtime.totalMemory() - runtime.freeMemory();
303         }
304 
305         @Override
306         public String toString() {
307             return getName() + " calls=" + getCalls()
308                     + " time=" + StringUtil.convertTime(getSumTime())
309                     + "(" + StringUtil.convertTime(getMinTime()) + "/" + StringUtil.convertTime(getAvgTime()) + "/" + StringUtil.convertTime(getMaxTime()) + ")"
310                     + " memory=" + StringUtil.convertMemory(getSumMemory())
311                     + "(" + StringUtil.convertMemory(getMinMemory()) + "/" + StringUtil.convertMemory(getAvgMemory()) + "/" + StringUtil.convertMemory(getMaxMemory()) + ")"
312                     ;
313         }
314 
315     } //CallStatistics
316 
317     /**
318      * This class is collecting data from different CallStatistics classes by
319      * using the method {@link #addCallStats(org.nuiton.util.CallAnalyse.CallStatistics)}.
320      *
321      * @author thimel
322      */
323     public static class CallStatisticsSummary extends CallStatistics {
324 
325         public CallStatisticsSummary(String name) {
326             super(name);
327         }
328 
329         /**
330          * This methods read the given {@link CallStatistics} and add values to
331          * its own
332          *
333          * @param other an other CallStatistics object
334          */
335         public void addCallStats(CallStatistics other) {
336             if (other == null || this.equals(other)) {
337                 return;
338             }
339             calls += other.getCalls();
340             if (other.getMinTime() < minTime || minTime == Long.MAX_VALUE) {
341                 minTime = other.getMinTime();
342             }
343             if (other.getMaxTime() > maxTime) {
344                 maxTime = other.getMaxTime();
345             }
346 
347             sumTime += other.getSumTime();
348 
349             if (other.getMinMemory() < minMemory || minMemory == Long.MAX_VALUE) {
350                 minMemory = other.getMinMemory();
351             }
352             if (other.getMaxMemory() > maxMemory) {
353                 maxMemory = other.getMaxMemory();
354             }
355             sumMemory += other.getSumMemory();
356         }
357     } //CallStatisticsSummary
358 
359 } // CallAnalyse
360