[ Team LiB ] Previous Section Next Section

An Example Filter—JSP/Servlet Timings

In a production system, you often need to know how well your application is responding. If the system is running slowly, you should find out about it before you get calls from irate users. Some of the statistics you might find useful include the following:

  • The number of pending requests. If this number continues to grow, there may be an infinite loop somewhere, or at least very slow responses.

  • The total number of requests. Lets you gauge how often people access various parts of your site.

  • The last execution time. Lets you know how long a particular JSP or servlet took to run.

  • A weighted average of execution times. Gives you an idea how long a particular JSP/servlet has been taking to run recently.

  • A full average of execution times. Gives you an idea of how long a particular JSP or servlet usually runs.

Listing 7.1 shows a class that keeps track of the various execution statistics for a single JSP or servlet. The formula for the weighted average uses a simple weighting process to give you an average over the last 20 times.

Listing 7.1 Source Code for Times.java
package examples.filter;

public class Times
{
    public String timerName;
    public long totalTime;
    public long weightedAverage;
    public long lastRunTime;
    public long numAccesses;
    public int numPending;

    public static final long NUM_WEIGHTS = 20;

    public Times(String aTimerName)
    {
        timerName = aTimerName;
    }

    public void addTime(long time)
    {
        numAccesses++;
        lastRunTime = time;
        totalTime += time;
        long weight = NUM_WEIGHTS;

        if (numAccesses < NUM_WEIGHTS) weight = numAccesses;

        if (weight > 0)
        {
            weightedAverage = (weightedAverage * (weight-1) +
                    time) / weight;
        }
    }

    public synchronized void begin()
    {
        numPending++;
    }

    public synchronized void end()
    {
        numPending--;
    }

    public String getTimerName() { return timerName; }
    public long getTotalTime() { return totalTime; }
    public long getWeightedAverage() { return weightedAverage; }
    public long getNumAccesses() { return numAccesses; }
    public long getNumPending() { return numPending; }

    /** Return average time converted into seconds */
    public double getAverageTimeSec() {
        return ((double) totalTime) / (1000.0 * (double) numAccesses); }

    /** Return weighted average in seconds */
    public double getWeightedAverageSec() {
        return ((double) weightedAverage) / 1000.0;
    }
}

Listing 7.2 shows the timing filter that actually gathers the statistics for each JSP and servlet. For each different JSP or servlet, it creates an instance of a Times object. Then, for each execution of that JSP or servlet, it adds the execution time to the Times object. The class also includes a static method that returns the times for all JSPs and servlets, so you can display them in a JSP.

Listing 7.2 Source Code for TimingFilter.java
package examples.filter;

import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;

public class TimingFilter implements Filter
{
    // The table of time values for each JSP/Servlet
    public static HashMap times = new HashMap();

    public TimingFilter()
    {
    }

    public void init(FilterConfig filterConfig)
    {
    }

    public void destroy()
    {
    }

    public void doFilter(ServletRequest request,
            ServletResponse response, FilterChain chain)
        throws java.io.IOException, ServletException
    {
        // If this isn't an HTTP request, don't bother timing it.
        if (!(request instanceof HttpServletRequest))
        {
            chain.doFilter(request,response);
            return;
        }

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // Get the pathname of the requested JSP/servlet.
        String uri = httpRequest.getRequestURI();

        // See whether there is an existing time value for this object.
        Times time = (Times) times.get(uri);

        // If not, create a new one.
        if (time == null)
        {
            time = new Times(uri);
            times.put(uri, time);
        }

        time.begin(); // Tells the time object this is a pending request

        // Note the start time.
        long startTime = System.currentTimeMillis();

        // Invoke the JSP/servlet or other chained filter objects.
        chain.doFilter(request, response);

        // Note the finish time.
        long endTime = System.currentTimeMillis();

        // Tell the time object that the pending request has completed.
        time.end();

        // Add the execution time to the time object.
        time.addTime(endTime-startTime);
    }

    /** Returns an array of all the time values known by this filter */
    public static Times[] getTimes()
    {
        ArrayList timeArray = new ArrayList();

        for (Iterator iter=times.keySet().iterator(); iter.hasNext();)
        {
            timeArray.add(times.get(iter.next()));
        }

        return (Times[]) timeArray.toArray(
                new Times[timeArray.size()]);
    }
}

Examining Listing 7.2, you can see all of the required elements. It implements the interface Filter, by providing methods for init, doFilter, and destroy. All of the work is performed in doFilter, so init and destroy are empty. The filter first checks to make sure that the request is an HttpServletRequest. If the request is not an HttpServletRequest, the filter ignores the request and passes the request undisturbed to the next filter or its target resource. Otherwise, a reference to the request is saved locally and then used to get the URI of the requested resource. Because filters work with all kinds of ServletRequests, it was necessary to downcast to an HttpServletRequest to use the method getRequestURI. The URI is used as a unique key to store the instance of Time used to keep the usage statistics for the resource.

The start time of the invocation is recorded and a call is made to doFilter. Upon the completion of doFilter, the request has been processed by all downstream filters and the JSP or servlet. The filter then performs post-processing by recording the end time and execution duration. The example highlights the opportunity to do work throughout the lifecycle of a request.

Finally, Listing 7.3 shows a JSP that displays the execution statistics gathered by the TimingFilter.

Listing 7.3 Source Code for ShowTimes.jsp
<%@page import="examples.filter.*, java.text.*, java.util.*" %>
<html>
<body>
<h1>Current JSP/Servlet Times</h1>
<table border="4">
<tr>
<th>URL</th><th>Total Requests</th><th># Pending</th>
<th>Weighted Average Time (mSec.)</th><th>Average Time</th>
</tr>
<%
    Times[] times = TimingFilter.getTimes();
    DecimalFormat formatter = new DecimalFormat("00.00");

    for ( int i = 0; i < times.length ; ++i ) {
       out.println("<tr>");
       out.println("<td>" + times[i].timerName + "</td>");
       out.println("<td>" + times[i].numAccesses + "</td>");
       out.println("<td>" + times[i].numPending + "</td>");
       out.println("<td>" + formatter.format(times[i].weightedAverage) + "</td>");
       out.println("<td>" + formatter.format(times[i].totalTime) + "</td>");
       out.println("</tr>");
        }

%>
</table>
</body>
</html>

Listing 7.4 shows the deployment descriptor for the application.

Listing 7.4 Source Code for web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">
    <display-name>Show Times</display-name>
    <description>An application to demonstrate the use of a filter
    </description>

<filter>
   <filter-name>Timing Filter</filter-name>
   <filter-class>examples.filter.TimingFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>Timing Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

</web-app>

This deployment descriptor will apply the timing filter to all resources in the current context. As mentioned earlier, by changing the filter-mapping you can limit filters to specific resources.

Repeating Elements

graphics/didyouknow_icon.gif

You can repeat filter-mapping elements to apply filters to groups of resources.


Figure 7.1 shows the output of ShowTimes.jsp.

Figure 7.1. The ShowTimes JSP shows various execution statistics.

graphics/07fig01.jpg

Figure 7.1 shows the times from several simple JSPs. You don't need to do anything special in a JSP to support ShowTimes. Just include the JSPs in the same Web application as ShowTimes. In this case, the example was packaged with a few JSPs from earlier chapters.

    [ Team LiB ] Previous Section Next Section