001: /*
002: * $Id: Stopwatch.java,v 1.2 2006/03/06 11:30:53 azzazzel Exp $
003: *
004: * Copyright 2006 Commsen International
005: *
006: * Licensed under the Common Public License, Version 1.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.opensource.org/licenses/cpl1.0.txt
011: *
012: * THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013: * EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS
014: * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
015: *
016: */
017: package com.commsen.stopwatch;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.util.Arrays;
022: import java.util.Calendar;
023: import java.util.Date;
024: import java.util.GregorianCalendar;
025: import java.util.Properties;
026:
027: import org.apache.log4j.Logger;
028:
029: import com.commsen.stopwatch.engines.DefaultStopwatchEngine;
030: import com.commsen.stopwatch.jmx.StopwatchAgent;
031: import com.commsen.stopwatch.storages.DefaultHSQLInMemoryStorage;
032: import com.commsen.stopwatch.storages.StorageManager;
033:
034: /**
035: * Stopwatch allows you to measure performance of any given piece of code. It's basic usage is as follows:
036: * <code>
037: * <pre>
038: * .....
039: * long swId = Stopwatch.start("group", "label");
040: * // some code to be measured
041: * Stopwatch.stop(swId);
042: *....
043: *</pre>
044: *</code>
045: *
046: * To skip already started mensuration (for example if an Exception is thrown) something similar to following code may be used:
047: * <code>
048: * <pre>
049: * .....
050: * long swId = Stopwatch.start("group", "label");
051: * try {
052: * // some code to be measured
053: * } catch (Exception) {
054: * Stopwatch.skip(swId);
055: * } finally {
056: * Stopwatch.stop(swId);
057: * }
058: *....
059: *</pre>
060: *</code>
061: *
062: * <p>
063: * <b>By default Stopwatch is not active!</b> It means all calls to {@link #start(String, String)},
064: * {@link #skip(long)} and {@link #stop(long)} methods are simply ignored.
065: * To activate Stopwatch do one of the following:
066: * <ul>
067: * <li>explicitly call Stopwatch.setActive(true)</li>
068: * <li>pass <code>-Dcom.commsen.stopwatch.activeOnStart=true</code> JVM parameter</li>
069: * <li>create "stopwatch.properties" file on classpath and set <code>activeOnStart=true</code></li>
070: * </ul>
071: * Stopwatch can also be activated/deactivated at runtime via JMX, RMI, etc.
072: *</p>
073: *
074: * @author Milen Dyankov
075: *
076: */
077: public class Stopwatch {
078:
079: /**
080: * Logger for this class
081: */
082: private static final Logger log = Logger.getLogger(Stopwatch.class);
083:
084: public static final String SYSTEM_PROPERTIES_PREFIX = "com.commsen.stopwatch.";
085: public static final String PROPERTY_ACTIVE = "activeOnStart";
086: public static final String PROPERTY_DEBUG = "debugEnabled";
087: public static final String PROPERTY_ENGINE = "engine";
088: public static final String PROPERTY_STORAGE = "storage";
089: public static final String PROPERTY_MODE = "persistenceMode";
090: public static final String PROPERTY_JMX_MANAGED = "jmxManaged";
091: public static final String PROPERTY_MBEAN_SERVER_NAME = "MBeanServer";
092:
093: public static final String DEFAULT_ENGINE = DefaultStopwatchEngine.class
094: .getName();
095: public static final String DEFAULT_STORAGE = DefaultHSQLInMemoryStorage.class
096: .getName();
097: public static final int DEFAULT_MODE = StorageManager.DELAYED_MODE;
098:
099: /**
100: * Indicates if stopwatch is active.
101: * When <code>false</code> all calls to {@link #start(String, String)} and {@link #stop(long)} are ignored.
102: */
103: private static boolean active;
104:
105: /**
106: * Indicates if stopwatch was properly initialised.
107: * If <code>false</code> then Stopwatch can not be activated
108: */
109: private static boolean initialised;
110:
111: /**
112: * Indicates if stopwatch should produce debug information
113: */
114: private static boolean debugEnabled;
115:
116: /**
117: * Indicates if stopwatch should be managed via JMX
118: */
119: private static boolean jmxManaged;
120:
121: /**
122: * The name of the MBean server to register manager with
123: */
124: private static String mBeanServerName;
125:
126: /**
127: * Holds Stopwatch's properties (if any)
128: */
129: private static Properties stopwatchProperties = new Properties();
130:
131: /**
132: * Holds reference to current stopwatch engine
133: */
134: private static StopwatchEngine engine;
135:
136: /**
137: * Holds reference to current stopwatch JMX agent
138: */
139: private static StopwatchAgent agent;
140:
141: /**
142: * Current persistance mode
143: */
144: private static String persistenceMode;
145:
146: /*
147: * Static initialization
148: */
149: static {
150: init();
151: }
152:
153: /**
154: * Start new mensuration.
155: * This method will do nothing and simply return a negative long if Stopwatch is not active.
156: *
157: * @param group the name of the group this mensuration should be placed in
158: * @param label how this mensuration should be labeled
159: * @return Unique ID representing current mensuration.
160: */
161: public static long start(String group, String label) {
162: if (!isActive()) {
163: if (isDebug()) {
164: log.debug("Stopwatch.start(" + group + "," + label
165: + ") ignored. Stopwatch is not active!");
166: }
167: return -1;
168: }
169: return engine.begin(group, label);
170: }
171:
172: /**
173: * Stop mensuration with id <code>id</code>.
174: * This method will do nothing if Stopwatch is not active or mensuration has been skipped already.
175: *
176: * @param id which mensuration to stop
177: */
178: public static void stop(long id) {
179: if (!isActive()) {
180: if (isDebug()) {
181: log.debug("Stopwatch.stop(" + id
182: + ") ignored. Stopwatch is not active!");
183: }
184: return;
185: }
186: engine.end(id);
187: };
188:
189: /**
190: * Skip mensuration with id <code>id</code>.
191: * Method should be called if for some reason current mensuration should be skipped.
192: * Default stopwatch engine will delete any information related to this <code>id</code>
193: * but other engines may behave differently (for example to provide a "skipped mensurations" report).
194: *
195: * This method will do nothing if Stopwatch is not active or mensuration has been stopped already.
196: *
197: * @param id which mensuration to stop
198: */
199: public static void skip(long id) {
200:
201: if (!isActive()) {
202: if (isDebug()) {
203: log.debug("Stopwatch.skip(" + id
204: + ") ignored. Stopwatch is not active!");
205: }
206: return;
207: }
208: engine.skip(id);
209: };
210:
211: /**
212: * Generates an array of reports which contains exectly 1 element for each combination of <b>group</b> and <b>label</b>
213: * If there is no enough data to produce reports, this method returns <code>null</code>
214: *
215: * @return array of reports of <code>null</code>.
216: */
217: public static Report[] getAllReports() {
218:
219: if (!Stopwatch.initialised) {
220: log
221: .warn("Stopwatch not propery initialised! Call to getAllReports() was ignored! Try to reset Stopwatch.");
222: return new Report[0];
223: }
224:
225: return engine.getStorage().getReports();
226: }
227:
228: /**
229: *
230: * @return
231: */
232: public static Report[] getAllByGroupReports() {
233:
234: if (!Stopwatch.initialised) {
235: log
236: .warn("Stopwatch not propery initialised! Call to getAllByGroupReports() was ignored! Try to reset Stopwatch.");
237: return new Report[0];
238: }
239:
240: return engine.getStorage().getAllByGroupReports();
241: }
242:
243: /**
244: *
245: * @return
246: */
247: public static Report[] getAllByLabelReports() {
248:
249: if (!Stopwatch.initialised) {
250: log
251: .warn("Stopwatch not propery initialised! Call to getAllByLabelReports() was ignored! Try to reset Stopwatch.");
252: return new Report[0];
253: }
254:
255: return engine.getStorage().getAllByLabelReports();
256: }
257:
258: /**
259: * Generates an array of reports which contains exectly 1 element for each <b>group</b>
260: * If there is no enough data to produce the report, this method returns <code>null</code>
261: *
262: * @param group the group for which report should be generated
263: * @return array of reports of <code>null</code>.
264: */
265: public static Report[] getGroupReports(String group) {
266: if (!Stopwatch.initialised) {
267: log
268: .warn("Stopwatch not propery initialised! Call to getGroupReports() was ignored! Try to reset Stopwatch.");
269: return new Report[0];
270: }
271: return engine.getStorage().getGroupReports(group);
272: }
273:
274: /**
275: * Generates an array of reports which contains exectly 1 element for each <b>label</b>
276: * If there is no enough data to produce the report, this method returns <code>null</code>
277: *
278: * @param label the label for which report should be generated
279: * @return array of reports or <code>null</code>.
280: */
281: public static Report[] getLabelReports(String label) {
282: if (!Stopwatch.initialised) {
283: log
284: .warn("Stopwatch not propery initialised! Call to getLabelReports() was ignored! Try to reset Stopwatch.");
285: return new Report[0];
286: }
287: return engine.getStorage().getLabelReports(label);
288: }
289:
290: /**
291: * Generates a single report for provided <b>group</b> and <b>label</b>
292: * If there is no enough data to produce the report, this method returns <code>null</code>
293: *
294: * @param group the group for which report should be generated
295: * @param label the label for which report should be generated
296: * @return single report for provided <b>group</b> and <b>label</b> of <code>null</code>.
297: */
298: public static Report getSingleReport(String group, String label) {
299: if (!Stopwatch.initialised) {
300: log
301: .warn("Stopwatch not propery initialised! Call to getSingleReport() was ignored! Try to reset Stopwatch.");
302: return null;
303: }
304: return engine.getStorage().getReport(group, label);
305: }
306:
307: /**
308: * Returns information of how many instances of any masured code ware running for the last <code>numberOfPeriods</code> periods.
309: * Period length is defined by <code>periodField</code> which can be one of {@link java.util.Calendar#FIELD_NAME}
310: * <p>
311: * For example to see how many peaces of masured code were running per minute for the last 30 minutes, one could use:
312: * <pre>
313: * long[] load = Stopwatch.getLoad({@link java.util.Calendar#MINUTE}, 30);
314: * </pre>
315: * In this case <code>load[0]</code> will contain the number of code instances running 30 minutes ago and
316: * <code>load[29]</code> number of code instances running in the last minute.
317: *
318: * @param periodField can be one of {@link java.util.Calendar#FIELD_NAME}
319: * @param numberOfPeriods number of periods
320: * @return array of length <code>numberOfPeriods</code> where every element represents the load for given pariod.
321: */
322: public static long[] getLoad(int periodField, int numberOfPeriods) {
323: if (!Stopwatch.initialised) {
324: log
325: .warn("Stopwatch not propery initialised! Call to getLoad() was ignored! Try to reset Stopwatch.");
326: return new long[0];
327: }
328: return engine.getStorage().getLoad(null, null, periodField,
329: numberOfPeriods);
330: }
331:
332: /**
333: * Returns information of how many instances of any masured code in group <code>group</code> ware running for the last <code>numberOfPeriods</code> periods.
334: * Period length is defined by <code>periodField</code> which can be one of {@link java.util.Calendar#FIELD_NAME}
335: * <p>
336: * For example to see how many peaces of masured code in group <code>g1</code> were running per day for the last 10 days, one could use:
337: * <pre>
338: * long[] load = Stopwatch.getLoad({@link java.util.Calendar#DAY_OF_MONTH}, 10);
339: * </pre>
340: * In this case <code>load[0]</code> will contain the number of code instances running 10 days ago and
341: * <code>load[9]</code> number of code instances running today.
342:
343: * @param group the group for which report should be generated
344: * @param periodField can be one of {@link java.util.Calendar#FIELD_NAME}
345: * @param numberOfPeriods number of periods
346: * @return array of length <code>numberOfPeriods</code> where every element represents the load for given pariod.
347: */
348: public static long[] getGroupLoad(String group, int periodField,
349: int numberOfPeriods) {
350: if (!Stopwatch.initialised) {
351: log
352: .warn("Stopwatch not propery initialised! Call to getGroupLoad() was ignored! Try to reset Stopwatch.");
353: return new long[0];
354: }
355: return engine.getStorage().getLoad(group, null, periodField,
356: numberOfPeriods);
357: }
358:
359: /**
360: * Returns information of how many instances of any masured code labeled <code>label</code> ware running for the last <code>numberOfPeriods</code> periods.
361: * Period length is defined by <code>periodField</code> which can be one of {@link java.util.Calendar#FIELD_NAME}
362: * <p>
363: * For example to see how many peaces of masured code labeled <code>l1</code> were running per second for the last 15 seconds, one could use:
364: * <pre>
365: * long[] load = Stopwatch.getLoad({@link java.util.Calendar#SECOND}, 15);
366: * </pre>
367: * In this case <code>load[0]</code> will contain the number of code instances running 15 seconds ago and
368: * <code>load[14]</code> number of code instances running in the last second.
369:
370: * @param label the label for which report should be generated
371: * @param periodField can be one of {@link java.util.Calendar#FIELD_NAME}
372: * @param numberOfPeriods number of periods
373: * @return array of length <code>numberOfPeriods</code> where every element represents the load for given pariod.
374: */
375: public static long[] getLabelLoad(String label, int periodField,
376: int numberOfPeriods) {
377: if (!Stopwatch.initialised) {
378: log
379: .warn("Stopwatch not propery initialised! Call to getLoad() was ignored! Try to reset Stopwatch.");
380: return new long[0];
381: }
382: return engine.getStorage().getLoad(null, label, periodField,
383: numberOfPeriods);
384: }
385:
386: /**
387: * Returns information of how many instances of any masured code in group <code>gropup</code> labeled <code>label</code> ware running for the last <code>numberOfPeriods</code> periods.
388: * Period length is defined by <code>periodField</code> which can be one of {@link java.util.Calendar#FIELD_NAME}
389: * <p>
390: * For example to see how many peaces of masured code in group <code>g1</code> labeled <code>l1</code> were running per second for the last 3 weeks, one could use:
391: * <pre>
392: * long[] load = Stopwatch.getLoad({@link java.util.Calendar#WEEK_OF_YEAR}, 3);
393: * </pre>
394: * In this case <code>load[0]</code> will contain the number of code instances running 3 weeks ago and
395: * <code>load[2]</code> number of code instances running in the last week.
396: *
397: * @param group the group for which report should be generated
398: * @param label the label for which report should be generated
399: * @param periodField can be one of {@link java.util.Calendar#FIELD_NAME}
400: * @param numberOfPeriods number of periods
401: * @return array of length <code>numberOfPeriods</code> where every element represents the load for given pariod.
402: */
403: public static long[] getLoad(String group, String label,
404: int periodField, int numberOfPeriods) {
405: if (!Stopwatch.initialised) {
406: log
407: .warn("Stopwatch not propery initialised! Call to getLoad() was ignored! Try to reset Stopwatch.");
408: return new long[0];
409: }
410: return engine.getStorage().getLoad(group, label, periodField,
411: numberOfPeriods);
412: }
413:
414: /**
415: *
416: *
417: */
418: public static void reset() {
419: engine.stop();
420: init();
421: }
422:
423: /**
424: * Called to check if Stopwatch is active.
425: * When this method returns <code>false</code> all calls to {@link #start(String, String)} and {@link #stop(long)} are ignored.
426: * @return Returns the Stopwatch's status.
427: */
428: public static boolean isActive() {
429: return active;
430: }
431:
432: /**
433: * This method changes stopwatch's status
434: * Should be used to activate/inactivate Stopwatch at runtime.
435: * @param active the Stopwatch's status.
436: */
437: public static void setActive(boolean active) {
438:
439: //skip if not changed
440: if (Stopwatch.active == active)
441: return;
442:
443: if (!Stopwatch.initialised) {
444: log
445: .warn("Stopwatch not propery initialised! Call to setActive() was ignored! Try to reset Stopwatch.");
446: return;
447: }
448:
449: // inform engine;
450: if (active == false) {
451: engine.pause();
452: } else {
453: engine.resume();
454: }
455:
456: Stopwatch.active = active;
457: }
458:
459: /**
460: * Called to check if Stopwatch should produce debug information.
461: *
462: * @see #setDebugEnabled(boolean)
463: * @return Returns the <code>true</code> or <code>false</code>
464: */
465: public static boolean isDebugEnabled() {
466: return debugEnabled;
467: }
468:
469: /**
470: * Used to disable/enable Stopwatch's debug information.
471: * The reason for this method to exist is to be able to minimize the performance impact
472: * Stopwatch may have on the measured application. Generating debug info consumes additional
473: * CPU units, which may become a problem if Stopwatch is heavily used.
474: *
475: * Setting this to false (is is false by default) will cause no debug info being generated by Stopwatch
476: * even when log4j's level is set to DEBUG.
477: *
478: * @param debugEnabled should debug information be generated
479: */
480: public static void setDebugEnabled(boolean debugEnabled) {
481: Stopwatch.debugEnabled = debugEnabled;
482: }
483:
484: /**
485: * @return true if debug is enabled
486: */
487: private static boolean isDebug() {
488: return isDebugEnabled() && log.isDebugEnabled();
489: }
490:
491: /**
492: * Tries to get the value of property <b>key</b>
493: * @param key
494: * @param defaultValue
495: * @return the value of property <b>key</b> of <code>defaultValue</code> if property not found.
496: */
497: public static String getProperty(String key, String defaultValue) {
498: // first try properties from file
499: String result = stopwatchProperties.getProperty(key,
500: defaultValue);
501: // then try system properties
502: result = System.getProperty(SYSTEM_PROPERTIES_PREFIX + key,
503: result);
504:
505: if (result != null && result.trim().length() > 0)
506: return result.trim();
507: return defaultValue;
508: }
509:
510: /**
511: *
512: */
513: private static void init() {
514: initialised = true;
515: InputStream propertiesInputStream = Thread.currentThread()
516: .getContextClassLoader().getResourceAsStream(
517: "stopwatch.properties");
518: if (propertiesInputStream != null) {
519: try {
520: stopwatchProperties.load(propertiesInputStream);
521: } catch (IOException e) {
522: log.warn(
523: "Problem loading 'stopwatch.properties' file!",
524: e);
525: }
526: }
527:
528: active = Boolean.valueOf(
529: getProperty(PROPERTY_ACTIVE, Boolean.toString(false)))
530: .booleanValue();
531: debugEnabled = Boolean.valueOf(
532: getProperty(PROPERTY_DEBUG, Boolean.toString(false)))
533: .booleanValue();
534: jmxManaged = Boolean.valueOf(
535: getProperty(PROPERTY_JMX_MANAGED, Boolean
536: .toString(false))).booleanValue();
537: mBeanServerName = getProperty(PROPERTY_MBEAN_SERVER_NAME, null);
538: String engineClass = getProperty(PROPERTY_ENGINE,
539: DEFAULT_ENGINE);
540: String storageClass = getProperty(PROPERTY_STORAGE, null);
541: persistenceMode = getProperty(PROPERTY_MODE, null);
542: try {
543: engine = (StopwatchEngine) Class.forName(engineClass)
544: .newInstance();
545: engine.setDebugEnabled(debugEnabled);
546:
547: // if storage is configured then pass it to the engine
548: if (storageClass != null
549: && storageClass.trim().length() > 0) {
550: StopwatchStorage storage = (StopwatchStorage) Class
551: .forName(storageClass).newInstance();
552: storage.setDebugEnabled(debugEnabled);
553: engine.setStorage(storage);
554: }
555:
556: // if persistence mode is configured then pass it to the engine
557: if ("NORMAL".equals(persistenceMode)) {
558: engine.setPersistenceMode(StorageManager.NORMAL_MODE);
559: } else if ("THREAD".equals(persistenceMode)) {
560: engine.setPersistenceMode(StorageManager.THREAD_MODE);
561: } else if ("DELAYED".equals(persistenceMode)) {
562: engine.setPersistenceMode(StorageManager.DELAYED_MODE);
563: } else {
564: if (persistenceMode != null)
565: log.warn("Unknown persistence mode: "
566: + persistenceMode + "! Using default !");
567: engine.setPersistenceMode(DEFAULT_MODE);
568: persistenceMode = "DELAYED";
569: }
570:
571: engine.start();
572:
573: if (jmxManaged) {
574: agent = new StopwatchAgent(mBeanServerName);
575: agent.start();
576: }
577:
578: } catch (InstantiationException e) {
579: active = false;
580: initialised = false;
581: log
582: .warn(
583: "Stopwatch was deactivated because an error(s) occurred during initialization!",
584: e);
585: } catch (IllegalAccessException e) {
586: active = false;
587: initialised = false;
588: log
589: .warn(
590: "Stopwatch was deactivated because an error(s) occurred during initialization!",
591: e);
592: } catch (ClassNotFoundException e) {
593: active = false;
594: initialised = false;
595: log
596: .warn(
597: "Stopwatch was deactivated because an error(s) occurred during initialization!",
598: e);
599: }
600: }
601:
602: public static String getEngineClass() {
603: return engine.getClass().getName();
604: }
605:
606: public static String getStorageClass() {
607: return engine.getStorageClass();
608: }
609:
610: public static String getPersistenceMode() {
611: return persistenceMode;
612: }
613:
614: }
|