Commits

Anonymous committed b6b3de8

Adding profiling library from Atlassian
XW-419

git-svn-id: http://svn.opensymphony.com/svn/xwork/trunk@1143e221344d-f017-0410-9bd5-d282ab1896d7

Comments (0)

Files changed (4)

src/java/com/opensymphony/xwork2/DefaultActionInvocation.java

 import com.opensymphony.xwork2.util.ValueStack;
 import com.opensymphony.xwork2.util.ValueStackFactory;
 import com.opensymphony.xwork2.util.XWorkContinuationConfig;
+import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
 import com.uwyn.rife.continuations.ContinuableObject;
 import com.uwyn.rife.continuations.ContinuationConfig;
 import com.uwyn.rife.continuations.ContinuationContext;
 
     protected void createAction(Map contextMap) {
         // load action
+        String timerKey = "actionCreate: "+proxy.getActionName();
         try {
+            UtilTimerStack.push(timerKey);
             action = ObjectFactory.getObjectFactory().buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
         } catch (InstantiationException e) {
             throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig());
 
             gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
             throw new XWorkException(gripe, e, proxy.getConfig());
+        } finally {
+            UtilTimerStack.pop(timerKey);
         }
 
         if (continuationHandler != null) {
     private void executeResult() throws Exception {
         result = createResult();
 
-        if (result != null) {
-            result.execute(this);
-        } else if (!Action.NONE.equals(resultCode)) {
-            LOG.warn("No result defined for action " + getAction().getClass().getName() + " and result " + getResultCode());
+        String timerKey = "executeResult: "+getResultCode();
+        try {
+            UtilTimerStack.push(timerKey);
+            if (result != null) {
+                result.execute(this);
+            } else if (!Action.NONE.equals(resultCode)) {
+                LOG.warn("No result defined for action " + getAction().getClass().getName() + " and result " + getResultCode());
+            }
+        } finally {
+            UtilTimerStack.pop(timerKey);
         }
     }
 
             LOG.debug("Executing action method = " + actionConfig.getMethodName());
         }
 
+        String timerKey = "invokeAction: "+proxy.getActionName();
         try {
+            UtilTimerStack.push(timerKey);
             Method method = getAction().getClass().getMethod(methodName, new Class[0]);
 
             if (action instanceof Proxy) {
             } else {
                 throw e;
             }
+        } finally {
+            UtilTimerStack.pop(timerKey);
         }
     }
     

src/java/com/opensymphony/xwork2/util/profiling/ObjectProfiler.java

+/*
+ * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * 
+ *     * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *     * Neither the name of Atlassian Software Systems Pty Ltd nor the names of
+ * its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.opensymphony.xwork2.util.profiling;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
+ */
+public class ObjectProfiler
+{
+
+    /**
+     * Given a class, and an interface that it implements, return a proxied version of the class that implements
+     * the interface.
+     * <p>
+     * The usual use of this is to profile methods from Factory objects:
+     * <pre>
+     * public PersistenceManager getPersistenceManager()
+     * {
+     *   return new DefaultPersistenceManager();
+     * }
+     *
+     * instead write:
+     * public PersistenceManager getPersistenceManager()
+     * {
+     *   return ObjectProfiler.getProfiledObject(PersistenceManager.class, new DefaultPersistenceManager());
+     * }
+     * </pre>
+     * <p>
+     * A side effect of this is that you will no longer be able to downcast to DefaultPersistenceManager.  This is probably a *good* thing.
+     *
+     * @param interfaceClazz    The interface to implement.
+     * @param o                 The object to proxy
+     * @return                  A proxied object, or the input object if the interfaceClazz wasn't an interface.
+     */
+    public static Object getProfiledObject(Class interfaceClazz, Object o)
+    {
+        //if we are not active - then do nothing
+        if (!UtilTimerStack.isActive())
+            return o;
+
+        //this should always be true - you shouldn't be passing something that isn't an interface
+        if (interfaceClazz.isInterface())
+        {
+            InvocationHandler timerHandler = new TimerInvocationHandler(o);
+            Object proxy = Proxy.newProxyInstance(interfaceClazz.getClassLoader(),
+                    new Class[]{interfaceClazz}, timerHandler);
+            return proxy;
+        }
+        else
+        {
+            return o;
+        }
+    }
+
+    /**
+     * A profiled call {@link Method#invoke(java.lang.Object, java.lang.Object[]). If {@link UtilTimerStack#isActive() }
+     * returns false, then no profiling is performed.
+     */
+    public static Object profiledInvoke(Method target, Object value, Object[] args) throws IllegalAccessException, InvocationTargetException
+    {
+        //if we are not active - then do nothing
+        if (!UtilTimerStack.isActive())
+            return target.invoke(value, args);
+
+        String logLine = new String(getTrimmedClassName(target) + "." + target.getName() + "()");
+
+        UtilTimerStack.push(logLine);
+        try
+        {
+            Object returnValue = target.invoke(value, args);
+
+            //if the return value is an interface then we should also proxy it!
+            if (returnValue != null && target.getReturnType().isInterface())
+            {
+//                System.out.println("Return type " + returnValue.getClass().getName() + " is being proxied " + target.getReturnType().getName() + " " + logLine);
+                InvocationHandler timerHandler = new TimerInvocationHandler(returnValue);
+                Object objectProxy = Proxy.newProxyInstance(returnValue.getClass().getClassLoader(),
+                        new Class[]{target.getReturnType()}, timerHandler);
+                return objectProxy;
+            }
+            else
+            {
+                return returnValue;
+            }
+        }
+        finally
+        {
+            UtilTimerStack.pop(logLine);
+        }
+    }
+
+    /**
+     * Given a method, get the Method name, with no package information.
+     */
+    public static String getTrimmedClassName(Method method)
+    {
+        String classname = method.getDeclaringClass().getName();
+        return classname.substring(classname.lastIndexOf('.') + 1);
+    }
+
+}
+
+class TimerInvocationHandler implements InvocationHandler
+{
+    protected Object target;
+
+    public TimerInvocationHandler(Object target)
+    {
+        if (target == null)
+            throw new IllegalArgumentException("Target Object passed to timer cannot be null");
+        this.target = target;
+    }
+
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+    {
+        return ObjectProfiler.profiledInvoke(method, target, args);
+    }
+
+}

src/java/com/opensymphony/xwork2/util/profiling/ProfilingTimerBean.java

+/*
+ * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * 
+ *     * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *     * Neither the name of Atlassian Software Systems Pty Ltd nor the names of
+ * its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.opensymphony.xwork2.util.profiling;
+
+import java.util.*;
+
+/**
+ * Bean to contain information about the pages profiled
+ *
+ * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
+ * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
+ */
+public class ProfilingTimerBean implements java.io.Serializable
+{
+    List children = new ArrayList();
+    ProfilingTimerBean parent = null;
+
+    String resource;
+
+    long startTime;
+    long totalTime;
+
+    public ProfilingTimerBean(String resource)
+    {
+        this.resource = resource;
+    }
+
+    protected void addParent(ProfilingTimerBean parent)
+    {
+        this.parent = parent;
+    }
+
+    public ProfilingTimerBean getParent()
+    {
+        return parent;
+    }
+
+
+    public void addChild(ProfilingTimerBean child)
+    {
+        children.add(child);
+        child.addParent(this);
+    }
+
+
+    public void setStartTime()
+    {
+        this.startTime = System.currentTimeMillis();
+    }
+
+    public void setEndTime()
+    {
+        this.totalTime = System.currentTimeMillis() - startTime;
+    }
+
+    public String getResource()
+    {
+        return resource;
+    }
+
+    /**
+     * Get a formatted string representing all the methods that took longer than a specified time.
+     */
+
+    public String getPrintable(long minTime)
+    {
+        return getPrintable("", minTime);
+    }
+
+    protected String getPrintable(String indent, long minTime)
+    {
+        //only print the value if we are larger or equal to the min time.
+        if (totalTime >= minTime)
+        {
+            StringBuffer buffer = new StringBuffer();
+            buffer.append(indent);
+            buffer.append("[" + totalTime + "ms] - " + resource);
+            buffer.append("\n");
+
+            Iterator childrenIt = children.iterator();
+            while (childrenIt.hasNext())
+            {
+                buffer.append(((ProfilingTimerBean) childrenIt.next()).getPrintable(indent + "  ", minTime));
+            }
+
+            return buffer.toString();
+        }
+        else
+            return "";
+    }
+}
+

src/java/com/opensymphony/xwork2/util/profiling/UtilTimerStack.java

+/*
+ * Copyright (c) 2002-2003, Atlassian Software Systems Pty Ltd All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * 
+ *     * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *     * Neither the name of Atlassian Software Systems Pty Ltd nor the names of
+ * its contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.opensymphony.xwork2.util.profiling;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * A timer stack.
+ * <p>
+ * Usage:
+ * <pre>
+ * String logMessage = "Log message";
+ * UtilTimerStack.push(logMessage);
+ * try
+ * {
+ *   //do some code
+ * }
+ * finally
+ * {
+ *   UtilTimerStack.pop(logMessage); //this needs to be the same text as above
+ * }
+ * </pre>
+ */
+public class UtilTimerStack
+{
+
+    // A reference to the current ProfilingTimerBean
+    private static ThreadLocal current = new ThreadLocal();
+
+    /**
+     * System property that controls whether this timer should be used or not.  Set to "true" activates
+     * the timer.  Set to "false" to disactivate.
+     */
+    public static final String ACTIVATE_PROPERTY = "xwork.profile.activate";
+
+    public static final String MIN_TIME = "atlassian.profile.mintime";
+    
+    private static final Log log = LogFactory.getLog(UtilTimerStack.class);
+
+    public static void push(String name)
+    {
+        if (!isActive())
+            return;
+
+        //create a new timer and start it
+        ProfilingTimerBean newTimer = new ProfilingTimerBean(name);
+        newTimer.setStartTime();
+
+        //if there is a current timer - add the new timer as a child of it
+        ProfilingTimerBean currentTimer = (ProfilingTimerBean) current.get();
+        if (currentTimer != null)
+        {
+            currentTimer.addChild(newTimer);
+        }
+
+        //set the new timer to be the current timer
+        current.set(newTimer);
+    }
+
+    public static void pop(String name)
+    {
+        if (!isActive())
+            return;
+
+        ProfilingTimerBean currentTimer = (ProfilingTimerBean) current.get();
+
+        //if the timers are matched up with each other (ie push("a"); pop("a"));
+        if (currentTimer != null && name != null && name.equals(currentTimer.getResource()))
+        {
+            currentTimer.setEndTime();
+            ProfilingTimerBean parent = currentTimer.getParent();
+            //if we are the root timer, then print out the times
+            if (parent == null)
+            {
+                printTimes(currentTimer);
+                current.set(null); //for those servers that use thread pooling
+            }
+            else
+            {
+                current.set(parent);
+            }
+        }
+        else
+        {
+            //if timers are not matched up, then print what we have, and then print warning.
+            if (currentTimer != null)
+            {
+                printTimes(currentTimer);
+                current.set(null); //prevent printing multiple times
+                log.warn("Unmatched Timer.  Was expecting " + currentTimer.getResource() + ", instead got " + name);
+            }
+        }
+
+
+    }
+
+    private static void printTimes(ProfilingTimerBean currentTimer)
+    {
+        log.info(currentTimer.getPrintable(getMinTime()));
+    }
+
+    private static long getMinTime()
+    {
+        try
+        {
+            return Long.parseLong(System.getProperty(MIN_TIME, "0"));
+        }
+        catch (NumberFormatException e)
+        {
+           return -1;
+        }
+    }
+
+    public static boolean isActive()
+    {
+        return System.getProperty(ACTIVATE_PROPERTY) != null;
+    }
+
+    public static void setActive(boolean active)
+    {
+        if (active)
+            System.setProperty(ACTIVATE_PROPERTY, "true");
+        else
+            System.setProperty(ACTIVATE_PROPERTY, null);
+    }
+
+}