In the process of optimizing AccessLogValve, I came to the conclusion that
I needed a tester that would allow me to benchmark (and otherwise test) the
valve without needing to run all of Catalina -- I needed to isolate just the
valve so that I could get clean benchmark times.  So, I thought about 
writing
just a class that would let me instantiate the valve, and use it.  But, 
after
some looking at the code, I realized some things:

1) That it may not be easy to make the valve act the same as it would act
in a regular Catalina Container without making something that acts much
like a Container anyway.

2) Even if I could make something that acts like a Container, it
wouldn't really be a Container, so my custom class would only be
"faking it".
3) Maintaining a fake container to continue to be like the Catalina
Containers wouldn't be easy, and it would certainly be pointless.
4) Making a new class that really *was* a Container didn't look
that tough.
5) If I needed one of these, certainly someone else must have also
wanted one.  :)

So, I set out to write a generic Valve tester.

I wrote it, got it running, and used it for optimizing the
AccessLogValve.  Here's what I wrote:

org.apache.catalina.valves.ValveTesterBase.java
  This is the base abstract class that is a Container that uses the
  Valve you wish to test.
org.apache.catalina.valves.NullValve.java
  I needed this valve to be the basic functionality of the
  ValveTesterBase Container subclasses.  It does absolutely nothing
  other than return control to the caller.  This is good because it
  takes up basically no cpu time, and completes the Container's
  request pipeline.

Then, I wrote one subclass of ValveTesterBase:

org.apache.catalina.connector.http.AccessLogValveTester.java
  This class tests the AccessLogValve in one particular way, and
  has control over the environment in which the AccessLogValve
  operates.  I had to place this class in the http connector
  package only because the HttpRequestImpl and HttpResponseImpl
  classes are not declared public!  Should they be?

With the first two classes, anyone should be able to easily make
a valve tester that tests a valve in exactly the way they want to
test it.  The ValveTesterBase class is intentionally simple, and
small.  The AccessLogValveTester is a good example of a subclass
that uses it.

One idea I had about how ValveTesterBase could be improved is
by writing and integrating a request traffic generator of some
kind (maybe also abstract?) so that the request traffic can be
more like the characteristics of real web traffic, and thus be
a more real-world test of the valves.  I have not begun coding
this yet.  It will need to be multithreaded, though.

Also, in which package should this tester code reside, really?
I'm aprehensive about mixing the tester code in with the regular
Catalina code..

All feedback about this code is welcome!

Cheers.

-- 
Jason Brittain
Software Engineer, Olliance Inc.        http://www.Olliance.com
Current Maintainer, Locomotive Project  http://www.Locomotive.org


/*
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
 * ITS 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */ 


package org.apache.catalina.valves;


import org.apache.catalina.Valve;
import org.apache.catalina.core.ContainerBase;


/**
 * Base abstract class for Valve testers.  Subclass this class with your
 * own implementation of the kind of test you'd like to do with a valve.
 * <p>
 * This class takes care of instantiating and starting up the valve.  The
 * tester is a Container so that the valve you're testing will work the
 * same way during the test as it does when it's being used with Catalina.
 * <p>
 * Note that the main(String args[]) method isn't in this class, since
 * Java doesn't allow static abstract methods.  Subclasses may wish to
 * implement a main method so that the test can be run from the command
 * line.
 *
 * @author Jason Brittain
 * @version $Revision$ $Date$
 */

public abstract class ValveTesterBase extends ContainerBase {


    // ----------------------------------------------------------- Constructors


    /**
     * Construct a new instance of this class with default property values.
     */
    public ValveTesterBase() {

    }


    // ----------------------------------------------------- Instance Variables


    /**
     * The descriptive information string for this implementation.
     */
    private String info = "org.apache.catalina.valves.ValveTesterBase/1.0";


    /**
     * The fully-qualified class name of the valve we're testing.
     */
    protected String valveClassName = null;


    /**
     * The valve instance we are testing.
     */
    protected Valve valve = null;


    /**
     * We store the timer's start time (in milliseconds) so we can compare
     * with the timer's end time to calculate the elapsed time of a test.
     */
    protected long timerStartTime = 0L;


    // ------------------------------------------------------------- Properties


    /**
     * Return the name of the Valve class that we're testing.
     */
    public String getValveClassName() {

        return (valveClassName);

    }


    /**
     * Set the fully-qualified name of the Valve class that we're testing.
     *
     * @param valveClassName the name of the Valve's class
     */
    public void setValveClassName(String valveClassName) {

        this.valveClassName = valveClassName;

    }


    // --------------------------------------------------------- Public Methods


    /**
     * Return descriptive information about this Container implementation and
     * the corresponding version number, in the format
     * <code>&lt;description&gt;/&lt;version&gt;</code>.
     */
    public String getInfo() {

        return info;

    }


    // ------------------------------------------------------ Protected Methods


    /**
     * This method creates an instance of the valveClassName, sets any valve
     * properties by calling initValveProperties(), and then starts the valve
     * by adding it to this Container.  The valve should then be ready for
     * use.  When you subclass this tester, override the initValveProperties()
     * method if you want to set properties of the valve that you're testing.
     */
    protected void init() {

        //System.out.println("valveClassName: " + this.valveClassName);

        if (valveClassName == null) {
            System.out.println("ValveTesterBase: Subclasses must specify a " +
                               "fully-qualified valveClassName to test!");
            System.exit(-1);
        }
        
        try {
            System.out.println("Creating new instance of " + valveClassName);
            Class valveClass = Class.forName(valveClassName);
            valve = (Valve) valveClass.newInstance();
        } catch (ClassNotFoundException e) {
            System.out.println(e);
            System.exit(-1);
        } catch (IllegalAccessException e) {
            System.out.println(e);
            System.exit(-1);
        } catch (InstantiationException e) {
            System.out.println(e);
            System.exit(-1);
        }

        initValveProperties();

        addValve(valve);

    }


    /**
     * This method sets up all of the configuration properties of the valve.
     * In a subclass, implement this method, and in it call any setXxx()
     * methods of the valve that you would like to call in order to set
     * the default properties the way you need them.
     */
    protected abstract void initValveProperties();


    /**
     * Call this method to start the ValveTester's timer.
     */
    protected void startTimer() {

        timerStartTime = System.currentTimeMillis();

    }


    /**
     * Call this method to end a timed test.
     *
     * @return the elapsed time from the time startTimer() was called until
     *         stopTimer() was called.
     */
    protected long stopTimer() {

        return (System.currentTimeMillis() - timerStartTime);

    }


    /**
     * Return the abbreviated name of this container for logging messsages
     */
    protected String logName() {

        String className = this.getClass().getName();
        int period = className.lastIndexOf(".");
        if (period >= 0)
            className = className.substring(period + 1);
        return className;

    }


    // -------------------------------------------------------- Private Methods

}
/*
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
 * ITS 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */ 


package org.apache.catalina.valves;


import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.util.LifecycleSupport;


/**
 * This valve does nothing other than return control to the caller.  It was
 * designed for testing purposes (benchmarking, for example).
 *
 * @author Jason Brittain
 * @version $Revision$ $Date$
 */

public final class NullValve
    extends ValveBase
    implements Lifecycle {


    // ----------------------------------------------------------- Constructors


    /**
     * Construct a new instance of this class with default property values.
     */
    public NullValve() {

    }


    // ----------------------------------------------------- Instance Variables


    /**
     * The descriptive information about this implementation.
     */
    protected static final String info =
        "org.apache.catalina.valves.NullValve/1.0";


    /**
     * The lifecycle event support for this component.
     */
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);


    /**
     * Has this component been started yet?
     */
    private boolean started = false;


    // ------------------------------------------------------------- Properties


    /**
     * Return descriptive information about this implementation.
     */
    public String getInfo() {

        return (this.info);

    }


    // --------------------------------------------------------- Public Methods


    /**
     * Simply return control right away.
     *
     * @param request Request being processed
     * @param response Response being processed
     *
     * @exception IOException if an input/output error has occurred
     * @exception ServletException if a servlet error has occurred
     */
    public void invoke(Request request, Response response)
        throws IOException, ServletException {

        return;

    }


    // ------------------------------------------------------ Lifecycle Methods


    /**
     * Add a lifecycle event listener to this component.
     *
     * @param listener The listener to add
     */
    public void addLifecycleListener(LifecycleListener listener) {

        lifecycle.addLifecycleListener(listener);

    }


    /**
     * Remove a lifecycle event listener from this component.
     *
     * @param listener The listener to add
     */
    public void removeLifecycleListener(LifecycleListener listener) {

        lifecycle.removeLifecycleListener(listener);

    }


    /**
     * Prepare for the beginning of active use of the public methods of this
     * component.  This method should be called after <code>configure()</code>,
     * and before any of the public methods of the component are utilized.
     *
     * @exception IllegalStateException if this component has already been
     *  started
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    public void start() throws LifecycleException {

        // Validate and update our current component state
        if (started)
            throw new LifecycleException
                (sm.getString("accessLogValve.alreadyStarted"));
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

    }


    /**
     * Gracefully terminate the active use of the public methods of this
     * component.  This method should be the last one called on a given
     * instance of this component.
     *
     * @exception IllegalStateException if this component has not been started
     * @exception LifecycleException if this component detects a fatal error
     *  that needs to be reported
     */
    public void stop() throws LifecycleException {

        // Validate and update our current component state
        if (!started)
            throw new LifecycleException
                (sm.getString("accessLogValve.notStarted"));
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;

    }

}
/*
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
 * ITS 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */ 


package org.apache.catalina.connector.http;


import java.io.IOException;
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Valve;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.valves.NullValve;
import org.apache.catalina.valves.ValveTesterBase;
import org.apache.catalina.connector.HttpRequestWrapper;
import org.apache.catalina.connector.HttpResponseWrapper;

/**
 * Run this class to test the AccessLogValve.  Make sure you set the
 * catalina.home system property by running this tester like:
 * <p>
 * java -Dcatalina.home=/tmp &lt;fully-qualified-class-name&gt;
 * <p>
 * And make sure you have both catalina.jar and servlet.jar on your
 * CLASSPATH.
 * <p>
 * This version of AccessLogValveTester only runs a single-threaded
 * benchmark of the AccessLogValve.  It creates one request object
 * and one response object and invokes the valve with them in a loop.
 * No concurrency testing / multithreaded testing is done yet.
 *
 * @author Jason Brittain
 * @version $Revision$ $Date$
 */

public class AccessLogValveTester extends ValveTesterBase {


    // ----------------------------------------------------------- Constructors


    /**
     * Construct a new instance of this class with default property values.
     */
    public AccessLogValveTester() {

        super();

    }


    // ----------------------------------------------------- Instance Variables



    // --------------------------------------------------------- Public Methods


    /**
     * Start up the AccessLogValveTester.
     *
     * @param args Command line arguments to be processed (none are used yet)
     */
    public static void main(String args[]) {

        AccessLogValveTester tester = new AccessLogValveTester();
        
//      // Make sure there's a "logs" directory.
//      File file = new File("logs");
//      if (!file.exists()) {
//          file.mkdir();
//      }

        tester.setValveClassName("org.apache.catalina.valves.AccessLogValve");
        tester.init();
        tester.runBenchmark();

    }



    // ------------------------------------------------------ Protected Methods


    /**
     * This method sets up all of the configuration properties of the valve.
     * it is called by the init() method of ValveTesterBase.
     */
    protected void initValveProperties() {

        AccessLogValve accessLogValve = (AccessLogValve) valve;

        // We'll set some common defaults..
        accessLogValve.setPrefix("localhost_access_log.");
        accessLogValve.setSuffix(".txt");

        // Use the "common" (optimized) pattern
        accessLogValve.setPattern("common");

        // Use a pattern similar to "common", but different enough to
        // trigger the unoptimized logging (note the extra space on the end)
        //accessLogValve.setPattern("%h %l %u %t \"%r\" %s %b ");

        accessLogValve.setNext(null);
        setBasic(new NullValve());

    }


    // -------------------------------------------------------- Private Methods


    /**
     * 
     */
    private void runBenchmark() {

        log("Sending access log to: " + System.getProperty("catalina.home") +
            "/logs/");

        // Create a Request object
        HttpRequestImpl httpRequest = new HttpRequestImpl();
        try {

            // Set some request state to fake a normal looking HTTP/1.1 request
            httpRequest.setInet(InetAddress.getLocalHost());
            httpRequest.setMethod("GET");
            httpRequest.setRequestURI("/index.html");
            httpRequest.setProtocol("HTTP/1.1");

        } catch (UnknownHostException e) {
        }

        // Create a Response object
        HttpResponseImpl httpResponse = new HttpResponseImpl();

        // Start the Container
        try {
            start();
        } catch (LifecycleException e) {
            log("AccessLogValveTester.runBenchmark: " + e);
        }

        log("Running benchmark on AccessLogValve..");

        startTimer();

        // Start the benchmark loop
        int count = 0;
        for (; count < 100000; count++) {

            // Send one request
            try {
                valve.invoke(httpRequest, httpResponse);
            } catch (IOException e) {

            } catch (ServletException e) {

            }
        }

        long elapsedTime = stopTimer();

        log("Ran " + count + " requests through the valve.");
        log("Elapsed time (ms): " + elapsedTime);
        log("Average time (ms): " + elapsedTime / count);
        log("DONE running benchmark on AccessLogValve!");

    }


}

Reply via email to