Stopwatch.java :  » Profiler » stopwatch » com » commsen » stopwatch » Java Open Source

Java Open Source » Profiler » stopwatch 
stopwatch » com » commsen » stopwatch » Stopwatch.java
/*
 * $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;
  }
  
}

java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.