Save This Page
Home » xwork-2.1.5 » com.opensymphony » xwork2 » util » profiling » [javadoc | source]
    1   /*
    2    * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved.
    3    *
    4    * Redistribution and use in source and binary forms, with or without modification,
    5    * are permitted provided that the following conditions are met:
    6    * 
    7    *     * Redistributions of source code must retain the above copyright notice,
    8    * this list of conditions and the following disclaimer.
    9    *     * Redistributions in binary form must reproduce the above copyright notice,
   10    * this list of conditions and the following disclaimer in the documentation and/or
   11    * other materials provided with the distribution.
   12    *     * Neither the name of Atlassian Software Systems Pty Ltd nor the names of
   13    * its contributors may be used to endorse or promote products derived from this
   14    * software without specific prior written permission.
   15    * 
   16    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   17    * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   18    * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   19    * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
   20    * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   21    * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   22    * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
   23    * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   24    * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   25    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   26    */
   27   package com.opensymphony.xwork2.util.profiling;
   28   
   29   import com.opensymphony.xwork2.util.logging.Logger;
   30   import com.opensymphony.xwork2.util.logging.LoggerFactory;
   31   
   32   
   33   /**
   34    * A timer stack.
   35    *
   36    * <p />
   37    * 
   38    * <!-- START SNIPPET: profilingAspect_struts2 -->
   39    * 
   40    * Struts2 profiling aspects involves the following :-
   41    * <ul>
   42    *   <li>ActionContextCleanUp</li>
   43    *   <li>FreemarkerPageFilter</li>
   44    *   <li>DispatcherFilter</li>
   45    *   <ul>
   46    *      <li>Dispatcher</li>
   47    *      <ul>
   48    *          <li>creation of DefaultActionProxy</li>
   49    *          <ul>
   50    *              <li>creation of DefaultActionInvocation</li>
   51    *              <ul>
   52    *   	          <li>creation of Action</li>
   53    *              </ul>
   54    *          </ul>
   55    *          <li>execution of DefaultActionProxy</li>
   56    *          <ul>
   57    *              <li>invocation of DefaultActionInvocation</li>
   58    *              <ul>
   59    *                  <li>invocation of Interceptors</li>
   60    *                  <li>invocation of Action</li>
   61    *                  <li>invocation of PreResultListener</li>
   62    *                  <li>invocation of Result</li>
   63    *              </ul>
   64    *          </ul>
   65    *      </ul>
   66    *   </ul>
   67    * </ul>
   68    * 
   69    * <!-- END SNIPPET: profilingAspect_struts2 -->
   70    *
   71    *
   72    * <!-- START SNIPPET: profilingAspect_xwork -->
   73    * 
   74    * XWork2 profiling aspects involves the following :-
   75    * <ul>
   76    *   <ul>
   77    *      <li>creation of DefaultActionProxy</li>
   78    *      <ul>
   79    *         <li>creation of DefaultActionInvocation</li>
   80    *         <ul>
   81    *   	      <li>creation of Action</li>
   82    *        </ul>
   83    *      </ul>
   84    *      <li>execution of DefaultActionProxy</li>
   85    *      <ul>
   86    *         <li>invocation of DefaultActionInvocation</li>
   87    *         <ul>
   88    *           <li>invocation of Interceptors</li>
   89    *           <li>invocation of Action</li>
   90    *           <li>invocation of PreResultListener</li>
   91    *           <li>invocation of Result</li>
   92    *        </ul>
   93    *     </ul>
   94    *   </ul>
   95    * </ul>
   96    * 
   97    * <!-- END SNIPPET: profilingAspect_xwork -->
   98    * 
   99    * 
  100    * <!-- START SNIPPET: activationDescription -->
  101    * 
  102    * Activating / Deactivating of the profiling feature could be done through:- 
  103    * 
  104    * <!-- END SNIPPET: activationDescription -->
  105    * 
  106    * <p/>
  107    * 
  108    * System properties:- <p/>
  109    * <pre>
  110    * <!-- START SNIPPET: activationThroughSystemProperty -->
  111    * 
  112    *  -Dxwork.profile.activate=true
  113    *  
  114    * <!-- END SNIPPET: activationThroughSystemProperty --> 
  115    * </pre>
  116    * 
  117    * <!-- START SNIPPET: activationThroughSystemPropertyDescription -->
  118    * 
  119    * This could be done in the container startup script eg. CATALINA_OPTS in catalina.sh 
  120    * (tomcat) or using "java -Dxwork.profile.activate=true -jar start.jar" (jetty) 
  121    * 
  122    * <!-- END SNIPPET: activationThroughSystemPropertyDescription -->
  123    * 
  124    * <p/>
  125    * Code :- <p/>
  126    * <pre>
  127    * <!-- START SNIPPET: activationThroughCode -->
  128    *   
  129    *  UtilTimerStack.setActivate(true);
  130    *    
  131    * <!-- END SNIPPET: activationThroughCode --> 
  132    * </pre>
  133    * 
  134    * 
  135    * 
  136    * <!-- START SNIPPET: activationThroughCodeDescription -->
  137    * 
  138    * This could be done in a static block, in a Spring bean with lazy-init="false", 
  139    * in a Servlet with init-on-startup as some numeric value, in a Filter or 
  140    * Listener's init method etc.
  141    * 
  142    * <!-- END SNIPPET: activationThroughCodeDescription -->
  143    * 
  144    * <p/>
  145    * Parameter:- 
  146    * 
  147    * <pre>
  148    * <!-- START SNIPPET: activationThroughParameter -->
  149    * 
  150    * &lt;action ... &gt;  
  151    *  ...
  152    *  &lt;interceptor-ref name="profiling"&gt;
  153    *      &lt;param name="profilingKey"&gt;profiling&lt;/param&gt;
  154    *  &lt;/interceptor-ref&gt;
  155    *  ...
  156    * &lt;/action&gt;
  157    * 
  158    * or 
  159    * 
  160    * &lt;action .... &gt;
  161    * ...
  162    *  &lt;interceptor-ref name="profiling" /&gt;
  163    * ...
  164    * &lt;/action&gt;
  165    * 
  166    * through url
  167    * 
  168    * http://host:port/context/namespace/someAction.action?profiling=true
  169    * 
  170    * through code
  171    * 
  172    * ActionContext.getContext().getParameters().put("profiling", "true);
  173    * 
  174    * <!-- END SNIPPET: activationThroughParameter -->
  175    * </pre>
  176    * 
  177    * 
  178    * <!-- START SNIPPET: activationThroughParameterDescription -->
  179    * 
  180    * To use profiling activation through parameter, one will need to pass in through 
  181    * the 'profiling' parameter (which is the default) and could be changed through 
  182    * the param tag in the interceptor-ref. 
  183    * 
  184    * <!-- END SNIPPET: activationThroughParameterDescription -->
  185    * 
  186    * <p/>
  187    * Warning:<p/>
  188    * <!-- START SNIPPET: activationThroughParameterWarning -->
  189    * 
  190    * Profiling activation through a parameter requires the following:
  191    *
  192    * <ul>
  193    *  <li>Profiling interceptor in interceptor stack</li>
  194    *  <li>dev mode on (struts.devMode=true in struts.properties)
  195    * </ul>
  196    * 
  197    * <!-- END SNIPPET: activationThroughParameterWarning -->
  198    * 
  199    * <p/>
  200    * 
  201    * <!-- START SNIPPET: filteringDescription -->
  202    * 
  203    * One could filter out the profile logging by having a System property as follows. With this
  204    * 'xwork.profile.mintime' property, one could only log profile information when its execution time 
  205    * exceed those specified in 'xwork.profile.mintime' system property. If no such property is specified, 
  206    * it will be assumed to be 0, hence all profile information will be logged.
  207    * 
  208    * <!-- END SNIPPET: filteringDescription -->
  209    * 
  210    * <pre>
  211    * <!-- START SNIPPET: filteringCode -->
  212    * 
  213    *  -Dxwork.profile.mintime=10000
  214    * 
  215    * <!-- END SNIPPET: filteringCode -->
  216    * </pre>
  217    * 
  218    * <!-- START SNIPPET: methodDescription -->
  219    * 
  220    * One could extend the profiling feature provided by Struts2 in their web application as well. 
  221    * 
  222    * <!-- END SNIPPET: methodDescription -->
  223    * 
  224    * <pre>
  225    * <!-- START SNIPPET: method1 -->
  226    * 
  227    *    String logMessage = "Log message";
  228    *    UtilTimerStack.push(logMessage);
  229    *    try {
  230    *        // do some code
  231    *    }
  232    *    finally {
  233    *        UtilTimerStack.pop(logMessage); // this needs to be the same text as above
  234    *    }
  235    *    
  236    * <!-- END SNIPPET: method1 -->   
  237    * </pre>
  238    * 
  239    * or 
  240    * 
  241    * <pre>
  242    * <!-- START SNIPPET: method2 -->
  243    * 
  244    *   String result = UtilTimerStack.profile("purchaseItem: ", 
  245    *       new UtilTimerStack.ProfilingBlock<String>() {
  246    *            public String doProfiling() {
  247    *               // do some code
  248    *               return "Ok";
  249    *            }
  250    *       });
  251    *       
  252    * <!-- END SNIPPET: method2 -->      
  253    * </pre>
  254    * 
  255    * 
  256    * <!-- START SNIPPET: profileLogFile -->
  257    * 
  258    * Profiled result is logged using commons-logging under the logger named 
  259    * 'com.opensymphony.xwork2.util.profiling.UtilTimerStack'. Depending on the underlying logging implementation
  260    * say if it is Log4j, one could direct the log to appear in a different file, being emailed to someone or have 
  261    * it stored in the db.
  262    * 
  263    * <!-- END SNIPPET: profileLogFile -->
  264    * 
  265    * @version $Date$ $Id$
  266    */
  267   public class UtilTimerStack
  268   {
  269   
  270       // A reference to the current ProfilingTimerBean
  271       protected static ThreadLocal<ProfilingTimerBean> current = new ThreadLocal<ProfilingTimerBean>();
  272   
  273       /**
  274        * System property that controls whether this timer should be used or not.  Set to "true" activates
  275        * the timer.  Set to "false" to disactivate.
  276        */
  277       public static final String ACTIVATE_PROPERTY = "xwork.profile.activate";
  278   
  279       /**
  280        * System property that controls the min time, that if exceeded will cause a log (at INFO level) to be
  281        * created.
  282        */
  283       public static final String MIN_TIME = "xwork.profile.mintime";
  284       
  285       private static final Logger LOG = LoggerFactory.getLogger(UtilTimerStack.class);
  286   
  287       /**
  288        * Initialized in a static block, it can be changed at runtime by calling setActive(...)
  289        */
  290       private static boolean active;
  291   
  292       static {
  293           active = "true".equalsIgnoreCase(System.getProperty(ACTIVATE_PROPERTY));
  294       }
  295   
  296       /**
  297        * Create and start a performance profiling with the <code>name</code> given. Deal with 
  298        * profile hierarchy automatically, so caller don't have to be concern about it.
  299        * 
  300        * @param name profile name
  301        */
  302       public static void push(String name)
  303       {
  304           if (!isActive())
  305               return;
  306   
  307           //create a new timer and start it
  308           ProfilingTimerBean newTimer = new ProfilingTimerBean(name);
  309           newTimer.setStartTime();
  310   
  311           //if there is a current timer - add the new timer as a child of it
  312           ProfilingTimerBean currentTimer = (ProfilingTimerBean) current.get();
  313           if (currentTimer != null)
  314           {
  315               currentTimer.addChild(newTimer);
  316           }
  317   
  318           //set the new timer to be the current timer
  319           current.set(newTimer);
  320       }
  321   
  322       /**
  323        * End a preformance profiling with the <code>name</code> given. Deal with
  324        * profile hierarchy automatically, so caller don't have to be concern about it.
  325        * 
  326        * @param name profile name
  327        */
  328       public static void pop(String name)
  329       {
  330           if (!isActive())
  331               return;
  332   
  333           ProfilingTimerBean currentTimer = (ProfilingTimerBean) current.get();
  334   
  335           //if the timers are matched up with each other (ie push("a"); pop("a"));
  336           if (currentTimer != null && name != null && name.equals(currentTimer.getResource()))
  337           {
  338               currentTimer.setEndTime();
  339               ProfilingTimerBean parent = currentTimer.getParent();
  340               //if we are the root timer, then print out the times
  341               if (parent == null)
  342               {
  343                   printTimes(currentTimer);
  344                   current.set(null); //for those servers that use thread pooling
  345               }
  346               else
  347               {
  348                   current.set(parent);
  349               }
  350           }
  351           else
  352           {
  353               //if timers are not matched up, then print what we have, and then print warning.
  354               if (currentTimer != null)
  355               {
  356                   printTimes(currentTimer);
  357                   current.set(null); //prevent printing multiple times
  358                   LOG.warn("Unmatched Timer.  Was expecting " + currentTimer.getResource() + ", instead got " + name);
  359               }
  360           }
  361   
  362   
  363       }
  364   
  365       /**
  366        * Do a log (at INFO level) of the time taken for this particular profiling.
  367        * 
  368        * @param currentTimer profiling timer bean
  369        */
  370       private static void printTimes(ProfilingTimerBean currentTimer)
  371       {
  372           LOG.info(currentTimer.getPrintable(getMinTime()));
  373       }
  374   
  375       /**
  376        * Get the min time for this profiling, it searches for a System property
  377        * 'xwork.profile.mintime' and default to 0.
  378        * 
  379        * @return long
  380        */
  381       private static long getMinTime()
  382       {
  383           try
  384           {
  385               return Long.parseLong(System.getProperty(MIN_TIME, "0"));
  386           }
  387           catch (NumberFormatException e)
  388           {
  389              return -1;
  390           }
  391       }
  392   
  393       /**
  394        * Determine if profiling is being activated, by searching for a system property
  395        * 'xwork.profile.activate', default to false (profiling is off).
  396        * 
  397        * @return <tt>true</tt>, if active, <tt>false</tt> otherwise.
  398        */
  399       public static boolean isActive()
  400       {
  401           return active;
  402       }
  403   
  404       /**
  405        * Turn profiling on or off.
  406        * 
  407        * @param active
  408        */
  409       public static void setActive(boolean active)
  410       {
  411           if (active)
  412               System.setProperty(ACTIVATE_PROPERTY, "true");
  413           else
  414           	System.clearProperty(ACTIVATE_PROPERTY);
  415   
  416           UtilTimerStack.active = active; 
  417       }
  418   
  419   
  420       /**
  421        * A convenience method that allows <code>block</code> of code subjected to profiling to be executed 
  422        * and avoid the need of coding boiler code that does pushing (UtilTimeBean.push(...)) and 
  423        * poping (UtilTimerBean.pop(...)) in a try ... finally ... block.
  424        * 
  425        * <p/>
  426        * 
  427        * Example of usage:
  428        * <pre>
  429        * 	 // we need a returning result
  430        *   String result = UtilTimerStack.profile("purchaseItem: ", 
  431        *       new UtilTimerStack.ProfilingBlock<String>() {
  432        *            public String doProfiling() {
  433        *               getMyService().purchaseItem(....)
  434        *               return "Ok";
  435        *            }
  436        *       });
  437        * </pre>
  438        * or
  439        * <pre>
  440        *   // we don't need a returning result
  441        *   UtilTimerStack.profile("purchaseItem: ", 
  442        *       new UtilTimerStack.ProfilingBlock<String>() {
  443        *            public String doProfiling() {
  444        *               getMyService().purchaseItem(....)
  445        *               return null;
  446        *            }
  447        *       });
  448        * </pre>
  449        * 
  450        * @param <T> any return value if there's one.
  451        * @param name profile name
  452        * @param block code block subjected to profiling
  453        * @return T
  454        * @throws Exception
  455        */
  456       public static <T> T profile(String name, ProfilingBlock<T> block) throws Exception {
  457       	UtilTimerStack.push(name);
  458       	try {
  459       		return block.doProfiling();
  460       	}
  461       	finally {
  462       		UtilTimerStack.pop(name);
  463       	}
  464       }
  465       
  466       /**
  467        * A callback interface where code subjected to profile is to be executed. This eliminates the need
  468        * of coding boiler code that does pushing (UtilTimerBean.push(...)) and poping (UtilTimerBean.pop(...))
  469        * in a try ... finally ... block.
  470        * 
  471        * @version $Date$ $Id$
  472        * 
  473        * @param <T>
  474        */
  475       public static interface ProfilingBlock<T> {
  476       	
  477       	/**
  478       	 * Method that execute the code subjected to profiling.
  479       	 * 
  480       	 * @return  profiles Type
  481       	 * @throws Exception
  482       	 */
  483       	T doProfiling() throws Exception;
  484       }
  485   }

Save This Page
Home » xwork-2.1.5 » com.opensymphony » xwork2 » util » profiling » [javadoc | source]