/*
* $Id: Stopwatch.java,v 1.2 2006/03/06 11:30:53 azzazzel Exp $
*
* Copyright 2006 Commsen International
*
* Licensed under the Common Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.txt
*
* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS
* OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
*/
package com.commsen.stopwatch;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Properties;
import org.apache.log4j.Logger;
import com.commsen.stopwatch.engines.DefaultStopwatchEngine;
import com.commsen.stopwatch.jmx.StopwatchAgent;
import com.commsen.stopwatch.storages.DefaultHSQLInMemoryStorage;
import com.commsen.stopwatch.storages.StorageManager;
/**
* Stopwatch allows you to measure performance of any given piece of code. It's basic usage is as follows:
* <code>
* <pre>
* .....
* long swId = Stopwatch.start("group", "label");
* // some code to be measured
* Stopwatch.stop(swId);
*....
*</pre>
*</code>
*
* To skip already started mensuration (for example if an Exception is thrown) something similar to following code may be used:
* <code>
* <pre>
* .....
* long swId = Stopwatch.start("group", "label");
* try {
* // some code to be measured
* } catch (Exception) {
* Stopwatch.skip(swId);
* } finally {
* Stopwatch.stop(swId);
* }
*....
*</pre>
*</code>
*
* <p>
* <b>By default Stopwatch is not active!</b> It means all calls to {@link #start(String, String)},
* {@link #skip(long)} and {@link #stop(long)} methods are simply ignored.
* To activate Stopwatch do one of the following:
* <ul>
* <li>explicitly call Stopwatch.setActive(true)</li>
* <li>pass <code>-Dcom.commsen.stopwatch.activeOnStart=true</code> JVM parameter</li>
* <li>create "stopwatch.properties" file on classpath and set <code>activeOnStart=true</code></li>
* </ul>
* Stopwatch can also be activated/deactivated at runtime via JMX, RMI, etc.
*</p>
*
* @author Milen Dyankov
*
*/
public class Stopwatch {
/**
* Logger for this class
*/
private static final Logger log = Logger.getLogger(Stopwatch.class);
public static final String SYSTEM_PROPERTIES_PREFIX = "com.commsen.stopwatch.";
public static final String PROPERTY_ACTIVE = "activeOnStart";
public static final String PROPERTY_DEBUG = "debugEnabled";
public static final String PROPERTY_ENGINE = "engine";
public static final String PROPERTY_STORAGE = "storage";
public static final String PROPERTY_MODE = "persistenceMode";
public static final String PROPERTY_JMX_MANAGED = "jmxManaged";
public static final String PROPERTY_MBEAN_SERVER_NAME = "MBeanServer";
public static final String DEFAULT_ENGINE = DefaultStopwatchEngine.class.getName();
public static final String DEFAULT_STORAGE = DefaultHSQLInMemoryStorage.class.getName();
public static final int DEFAULT_MODE = StorageManager.DELAYED_MODE;
/**
* Indicates if stopwatch is active.
* When <code>false</code> all calls to {@link #start(String, String)} and {@link #stop(long)} are ignored.
*/
private static boolean active;
/**
* Indicates if stopwatch was properly initialised.
* If <code>false</code> then Stopwatch can not be activated
*/
private static boolean initialised;
/**
* Indicates if stopwatch should produce debug information
*/
private static boolean debugEnabled;
/**
* Indicates if stopwatch should be managed via JMX
*/
private static boolean jmxManaged;
/**
* The name of the MBean server to register manager with
*/
private static String mBeanServerName;
/**
* Holds Stopwatch's properties (if any)
*/
private static Properties stopwatchProperties = new Properties();
/**
* Holds reference to current stopwatch engine
*/
private static StopwatchEngine engine;
/**
* Holds reference to current stopwatch JMX agent
*/
private static StopwatchAgent agent;
/**
* Current persistance mode
*/
private static String persistenceMode;
/*
* Static initialization
*/
static {
init();
}
/**
* Start new mensuration.
* This method will do nothing and simply return a negative long if Stopwatch is not active.
*
* @param group the name of the group this mensuration should be placed in
* @param label how this mensuration should be labeled
* @return Unique ID representing current mensuration.
*/
public static long start (String group, String label) {
if (!isActive()) {
if (isDebug()) {
log.debug("Stopwatch.start("+ group +","+ label +") ignored. Stopwatch is not active!");
}
return -1;
}
return engine.begin(group, label);
}
/**
* Stop mensuration with id <code>id</code>.
* This method will do nothing if Stopwatch is not active or mensuration has been skipped already.
*
* @param id which mensuration to stop
*/
public static void stop (long id) {
if (!isActive()) {
if (isDebug()) {
log.debug("Stopwatch.stop("+ id +") ignored. Stopwatch is not active!");
}
return;
}
engine.end(id);
};
/**
* Skip mensuration with id <code>id</code>.
* Method should be called if for some reason current mensuration should be skipped.
* Default stopwatch engine will delete any information related to this <code>id</code>
* but other engines may behave differently (for example to provide a "skipped mensurations" report).
*
* This method will do nothing if Stopwatch is not active or mensuration has been stopped already.
*
* @param id which mensuration to stop
*/
public static void skip (long id) {
if (!isActive()) {
if (isDebug()) {
log.debug("Stopwatch.skip("+ id +") ignored. Stopwatch is not active!");
}
return;
}
engine.skip(id);
};
/**
* Generates an array of reports which contains exectly 1 element for each combination of <b>group</b> and <b>label</b>
* If there is no enough data to produce reports, this method returns <code>null</code>
*
* @return array of reports of <code>null</code>.
*/
public static Report[] getAllReports () {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getAllReports() was ignored! Try to reset Stopwatch.");
return new Report[0];
}
return engine.getStorage().getReports();
}
/**
*
* @return
*/
public static Report[] getAllByGroupReports () {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getAllByGroupReports() was ignored! Try to reset Stopwatch.");
return new Report[0];
}
return engine.getStorage().getAllByGroupReports();
}
/**
*
* @return
*/
public static Report[] getAllByLabelReports () {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getAllByLabelReports() was ignored! Try to reset Stopwatch.");
return new Report[0];
}
return engine.getStorage().getAllByLabelReports();
}
/**
* Generates an array of reports which contains exectly 1 element for each <b>group</b>
* If there is no enough data to produce the report, this method returns <code>null</code>
*
* @param group the group for which report should be generated
* @return array of reports of <code>null</code>.
*/
public static Report[] getGroupReports (String group) {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getGroupReports() was ignored! Try to reset Stopwatch.");
return new Report[0];
}
return engine.getStorage().getGroupReports(group);
}
/**
* Generates an array of reports which contains exectly 1 element for each <b>label</b>
* If there is no enough data to produce the report, this method returns <code>null</code>
*
* @param label the label for which report should be generated
* @return array of reports or <code>null</code>.
*/
public static Report[] getLabelReports (String label) {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getLabelReports() was ignored! Try to reset Stopwatch.");
return new Report[0];
}
return engine.getStorage().getLabelReports(label);
}
/**
* Generates a single report for provided <b>group</b> and <b>label</b>
* If there is no enough data to produce the report, this method returns <code>null</code>
*
* @param group the group for which report should be generated
* @param label the label for which report should be generated
* @return single report for provided <b>group</b> and <b>label</b> of <code>null</code>.
*/
public static Report getSingleReport (String group, String label) {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getSingleReport() was ignored! Try to reset Stopwatch.");
return null;
}
return engine.getStorage().getReport(group, label);
}
/**
* Returns information of how many instances of any masured code ware running for the last <code>numberOfPeriods</code> periods.
* Period length is defined by <code>periodField</code> which can be one of {@link java.util.Calendar#FIELD_NAME}
* <p>
* For example to see how many peaces of masured code were running per minute for the last 30 minutes, one could use:
* <pre>
* long[] load = Stopwatch.getLoad({@link java.util.Calendar#MINUTE}, 30);
* </pre>
* In this case <code>load[0]</code> will contain the number of code instances running 30 minutes ago and
* <code>load[29]</code> number of code instances running in the last minute.
*
* @param periodField can be one of {@link java.util.Calendar#FIELD_NAME}
* @param numberOfPeriods number of periods
* @return array of length <code>numberOfPeriods</code> where every element represents the load for given pariod.
*/
public static long[] getLoad (int periodField, int numberOfPeriods) {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getLoad() was ignored! Try to reset Stopwatch.");
return new long[0];
}
return engine.getStorage().getLoad(null, null, periodField, numberOfPeriods);
}
/**
* Returns information of how many instances of any masured code in group <code>group</code> ware running for the last <code>numberOfPeriods</code> periods.
* Period length is defined by <code>periodField</code> which can be one of {@link java.util.Calendar#FIELD_NAME}
* <p>
* 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:
* <pre>
* long[] load = Stopwatch.getLoad({@link java.util.Calendar#DAY_OF_MONTH}, 10);
* </pre>
* In this case <code>load[0]</code> will contain the number of code instances running 10 days ago and
* <code>load[9]</code> number of code instances running today.
* @param group the group for which report should be generated
* @param periodField can be one of {@link java.util.Calendar#FIELD_NAME}
* @param numberOfPeriods number of periods
* @return array of length <code>numberOfPeriods</code> where every element represents the load for given pariod.
*/
public static long[] getGroupLoad (String group, int periodField, int numberOfPeriods) {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getGroupLoad() was ignored! Try to reset Stopwatch.");
return new long[0];
}
return engine.getStorage().getLoad(group, null, periodField, numberOfPeriods);
}
/**
* Returns information of how many instances of any masured code labeled <code>label</code> ware running for the last <code>numberOfPeriods</code> periods.
* Period length is defined by <code>periodField</code> which can be one of {@link java.util.Calendar#FIELD_NAME}
* <p>
* 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:
* <pre>
* long[] load = Stopwatch.getLoad({@link java.util.Calendar#SECOND}, 15);
* </pre>
* In this case <code>load[0]</code> will contain the number of code instances running 15 seconds ago and
* <code>load[14]</code> number of code instances running in the last second.
* @param label the label for which report should be generated
* @param periodField can be one of {@link java.util.Calendar#FIELD_NAME}
* @param numberOfPeriods number of periods
* @return array of length <code>numberOfPeriods</code> where every element represents the load for given pariod.
*/
public static long[] getLabelLoad (String label, int periodField, int numberOfPeriods) {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getLoad() was ignored! Try to reset Stopwatch.");
return new long[0];
}
return engine.getStorage().getLoad(null, label, periodField, numberOfPeriods);
}
/**
* 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.
* Period length is defined by <code>periodField</code> which can be one of {@link java.util.Calendar#FIELD_NAME}
* <p>
* 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:
* <pre>
* long[] load = Stopwatch.getLoad({@link java.util.Calendar#WEEK_OF_YEAR}, 3);
* </pre>
* In this case <code>load[0]</code> will contain the number of code instances running 3 weeks ago and
* <code>load[2]</code> number of code instances running in the last week.
*
* @param group the group for which report should be generated
* @param label the label for which report should be generated
* @param periodField can be one of {@link java.util.Calendar#FIELD_NAME}
* @param numberOfPeriods number of periods
* @return array of length <code>numberOfPeriods</code> where every element represents the load for given pariod.
*/
public static long[] getLoad (String group, String label, int periodField, int numberOfPeriods) {
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to getLoad() was ignored! Try to reset Stopwatch.");
return new long[0];
}
return engine.getStorage().getLoad(group, label, periodField, numberOfPeriods);
}
/**
*
*
*/
public static void reset () {
engine.stop();
init();
}
/**
* Called to check if Stopwatch is active.
* When this method returns <code>false</code> all calls to {@link #start(String, String)} and {@link #stop(long)} are ignored.
* @return Returns the Stopwatch's status.
*/
public static boolean isActive() {
return active;
}
/**
* This method changes stopwatch's status
* Should be used to activate/inactivate Stopwatch at runtime.
* @param active the Stopwatch's status.
*/
public static void setActive(boolean active) {
//skip if not changed
if (Stopwatch.active == active) return;
if (!Stopwatch.initialised) {
log.warn("Stopwatch not propery initialised! Call to setActive() was ignored! Try to reset Stopwatch.");
return;
}
// inform engine;
if (active == false) {
engine.pause();
} else {
engine.resume();
}
Stopwatch.active = active;
}
/**
* Called to check if Stopwatch should produce debug information.
*
* @see #setDebugEnabled(boolean)
* @return Returns the <code>true</code> or <code>false</code>
*/
public static boolean isDebugEnabled() {
return debugEnabled;
}
/**
* Used to disable/enable Stopwatch's debug information.
* The reason for this method to exist is to be able to minimize the performance impact
* Stopwatch may have on the measured application. Generating debug info consumes additional
* CPU units, which may become a problem if Stopwatch is heavily used.
*
* Setting this to false (is is false by default) will cause no debug info being generated by Stopwatch
* even when log4j's level is set to DEBUG.
*
* @param debugEnabled should debug information be generated
*/
public static void setDebugEnabled(boolean debugEnabled) {
Stopwatch.debugEnabled = debugEnabled;
}
/**
* @return true if debug is enabled
*/
private static boolean isDebug () {
return isDebugEnabled() && log.isDebugEnabled();
}
/**
* Tries to get the value of property <b>key</b>
* @param key
* @param defaultValue
* @return the value of property <b>key</b> of <code>defaultValue</code> if property not found.
*/
public static String getProperty(String key, String defaultValue) {
// first try properties from file
String result = stopwatchProperties.getProperty(key, defaultValue);
// then try system properties
result = System.getProperty(SYSTEM_PROPERTIES_PREFIX + key, result);
if (result != null && result.trim().length() > 0) return result.trim();
return defaultValue;
}
/**
*
*/
private static void init() {
initialised = true;
InputStream propertiesInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("stopwatch.properties");
if (propertiesInputStream != null) {
try {
stopwatchProperties.load(propertiesInputStream);
} catch (IOException e) {
log.warn("Problem loading 'stopwatch.properties' file!", e);
}
}
active = Boolean.valueOf(getProperty(PROPERTY_ACTIVE, Boolean.toString(false))).booleanValue();
debugEnabled = Boolean.valueOf(getProperty(PROPERTY_DEBUG, Boolean.toString(false))).booleanValue();
jmxManaged = Boolean.valueOf(getProperty(PROPERTY_JMX_MANAGED, Boolean.toString(false))).booleanValue();
mBeanServerName = getProperty(PROPERTY_MBEAN_SERVER_NAME, null);
String engineClass = getProperty(PROPERTY_ENGINE, DEFAULT_ENGINE);
String storageClass = getProperty(PROPERTY_STORAGE, null);
persistenceMode = getProperty(PROPERTY_MODE, null);
try {
engine = (StopwatchEngine)Class.forName(engineClass).newInstance();
engine.setDebugEnabled(debugEnabled);
// if storage is configured then pass it to the engine
if (storageClass != null && storageClass.trim().length() > 0) {
StopwatchStorage storage = (StopwatchStorage)Class.forName(storageClass).newInstance();
storage.setDebugEnabled(debugEnabled);
engine.setStorage(storage);
}
// if persistence mode is configured then pass it to the engine
if ("NORMAL".equals(persistenceMode)) {
engine.setPersistenceMode(StorageManager.NORMAL_MODE);
} else if ("THREAD".equals(persistenceMode)) {
engine.setPersistenceMode(StorageManager.THREAD_MODE);
} else if ("DELAYED".equals(persistenceMode)) {
engine.setPersistenceMode(StorageManager.DELAYED_MODE);
} else {
if (persistenceMode != null) log.warn("Unknown persistence mode: " + persistenceMode + "! Using default !");
engine.setPersistenceMode(DEFAULT_MODE);
persistenceMode = "DELAYED";
}
engine.start();
if (jmxManaged) {
agent = new StopwatchAgent(mBeanServerName);
agent.start();
}
} catch (InstantiationException e) {
active = false;
initialised = false;
log.warn("Stopwatch was deactivated because an error(s) occurred during initialization!",e);
} catch (IllegalAccessException e) {
active = false;
initialised = false;
log.warn("Stopwatch was deactivated because an error(s) occurred during initialization!",e);
} catch (ClassNotFoundException e) {
active = false;
initialised = false;
log.warn("Stopwatch was deactivated because an error(s) occurred during initialization!",e);
}
}
public static String getEngineClass() {
return engine.getClass().getName();
}
public static String getStorageClass() {
return engine.getStorageClass();
}
public static String getPersistenceMode() {
return persistenceMode;
}
}
|