Looks good. Can I propose an (close) alternative?

Can the status servlet produce an XML document? Then the XML document can either parsed by some third party app or it can be transformed in place to anyone's liking via XSLT.

Attached are examples of a (very) quick example of what I mean. There should be 2 attachments, StatusManagerServlet2 and xform.xsl. The servlet can take an additional parameter xsltFileName which will transform the output. If not specified, then the raw xml will be produced. So to change the look and feel - all you need to do is change the init parameter.

If anyone wishes to suggest what the xml dtd should look like, I have no attachments to the code above. It was a quick proof of concept.

For those writing monitoring applications and wish not to use JMX to pull their measurements, an XML doc could be a convenient alternative.

-Tim

Remy Maucherat wrote:
Remy Maucherat wrote:

Hi,

I proposed that to Costin a few days ago, but got not so enthusiastic comments.
The idea would be to add a new monitor servlet to the manager webapp. It would generate data similar to http://www.apache.org/server-status. It would mostly (exclusively ?) use JMX to retrieve the components statistics.


That's not a high priority task for me, but something I'd like to get done eventually, and I'm looking for some feedback. I understand that there are existing agents for JMX that can be used to provide more powerful remote access to the statistics (HTTP, RMI, etc), but these tools do not have the ability to give a user a quick and comprehensive look at the Tomcat status (although they allow much more complex operations, and it's not my objective to replace them).


I've committed a rough version of the monitoring servlet. It will display status information for all Coyote connectors, using JMX exclusively. I don't think there's any statistic missing (the amount of meaningful status information available to a Java program is definitely much lower than for a native Unix program, hence the "simpler" look when compared to the Apache status).

The thing is very rough, and could use contributions (hint, hint) :)

As for using it, the monitor servlet is linked from the default Tomcat welcome page. It currently requires the same credentials as the rest of the manager webapp to access.

Remy

<?xml version="1.0"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  version="1.0">

  <!-- Output method -->
  <xsl:output method="xhtml"
            encoding="iso-8859-1"
              indent="no"/>

  <xsl:template match="status">
    <html>
    <head>
    	<TITLE>Tomcat Status</TITLE>
		<STYLE type="text/css">
			body, table, tr, td, a, div, span {
				vertical-align : top;
			}
		</STYLE>
    </head>
    <body>
      <div style='font-size:20px;'>Tomcat Status</div>

      <xsl:apply-templates select="jvm"/>
      <xsl:apply-templates select="connector"/>
     </body>
    </html>
  </xsl:template>

  <xsl:template match="jvm">
   <xsl:apply-templates select="memory"/>
  </xsl:template>

  <xsl:template match="memory">
    <table><tr>
    		 <td><b>JVM:</b></td>
    		 <td><b>free:</b> <xsl:value-of select="@free"/></td>
    		 <td><b>total:</b> <xsl:value-of select="@total"/></td>
    		 <td><b>max:</b> <xsl:value-of select="@max"/></td>
    	   </tr>
    </table><hr />
  </xsl:template>

  <xsl:template match="connector">
	 <b>Connector -- </b> <xsl:value-of select="@name"/><br />

  	<xsl:apply-templates select="threadInfo"/>
  	<xsl:apply-templates select="requestInfo"/>
  	<xsl:apply-templates select="workers"/>
  </xsl:template>

  <xsl:template match="threadInfo">
    <table><tr>
    		 <td><b>threadInfo </b></td>
    		 <td><b>maxThreads:</b> <xsl:value-of select="@maxThreads"/></td>
    		 <td><b>minSpareThreads:</b> <xsl:value-of select="@minSpareThreads"/></td>
    		 <td><b>maxSpareThreads:</b> <xsl:value-of select="@maxSpareThreads"/></td>
    		 <td><b>currentThreadCount:</b> <xsl:value-of select="@currentThreadCount"/></td>
    		 <td><b>currentThreadsBusy:</b> <xsl:value-of select="@currentThreadsBusy"/></td>
    	   </tr>
    </table><hr />
  </xsl:template>

  <xsl:template match="requestInfo">
    <table><tr>
    		 <td><b>requestInfo </b></td>
    		 <td><b>maxTime:</b> <xsl:value-of select="@maxTime"/></td>
    		 <td><b>processingTime:</b> <xsl:value-of select="@processingTime"/></td>
    		 <td><b>requestCount:</b> <xsl:value-of select="@requestCount"/></td>
    		 <td><b>errorCount:</b> <xsl:value-of select="@errorCount"/></td>
    		 <td><b>bytesReceived:</b> <xsl:value-of select="@bytesReceived"/></td>
    		 <td><b>bytesSent:</b> <xsl:value-of select="@bytesSent"/></td>
    	   </tr>
    </table><hr />
  </xsl:template>

  <xsl:template match="workers">
   <table>
    <tr><th>Stage</th><th>Time</th><th>B Sent</th><th>B Recv</th><th>Client</th><th>VHost</th><th>Request</th></tr>
  	<xsl:apply-templates select="worker"/>

   </table>
  </xsl:template>

  <xsl:template match="worker">
   <tr>
    <td><xsl:apply-templates select="stage"/></td>
    <td><xsl:apply-templates select="requestProcessingTime"/></td>
    <td><xsl:apply-templates select="requestBytesSent"/></td>
    <td><xsl:apply-templates select="requestBytesReceived"/></td>
    <td><xsl:apply-templates select="remoteAddr"/></td>
    <td><xsl:apply-templates select="remoteAddr"/></td>
    <td><xsl:apply-templates select="virtualHost"/></td>
    <td><xsl:apply-templates select="method"/></td>
    <td><xsl:apply-templates select="currentUri"/></td>
    <td><xsl:apply-templates select="currentQueryString"/></td>
   </tr>
  </xsl:template>

</xsl:stylesheet>
/*
 * $Header: Exp $
 * $Revision:   $
 * $Date:   $
 *
 * ====================================================================
 *
 * 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.servlets;


import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.StringReader;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import javax.management.MBeanServer;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;

import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.modeler.Registry;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.Transformer;

/**
 * This servlet will display a complete status of how things are going.
 * By default - this just prints an XML file. If you wish you can transform
 * the xml output via an input parameter.
 *
 * @author Remy Maucherat
 * @author Tim Funk
 * @version $Revision:  $ $Date:   $
 */

public class StatusManagerServlet2
    extends HttpServlet implements NotificationListener {


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


    /**
     * The debugging detail level for this servlet.
     */
    private int debug = 0;


    /**
     * MBean server.
     */
    protected MBeanServer mBeanServer = null;


    /**
     * Vector of protocol handlers object names.
     */
    protected Vector protocolHandlers = new Vector();


    /**
     * Vector of thread pools object names.
     */
    protected Vector threadPools = new Vector();


    /**
     * Vector of request processors object names.
     */
    protected Vector requestProcessors = new Vector();


    /**
     * Vector of global request processors object names.
     */
    protected Vector globalRequestProcessors = new Vector();


    /**
     * The file name to perform the XSLT transformation instead
     * of printing the raw XML.
     */
    protected String  xsltFileName = null;


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


    /**
     * Initialize this servlet.
     */
    public void init() throws ServletException {

        // Retrieve the MBean server
        mBeanServer = Registry.getServer();

        // Set our properties from the initialization parameters
        String value = null;
        try {
            value = getServletConfig().getInitParameter("debug");
            debug = Integer.parseInt(value);
        } catch (Throwable t) {
            ;
        }
        try {
            xsltFileName = getServletConfig().getInitParameter("xsltFileName");
        } catch (Throwable t) {
            ;
        }

        try {

            // Query protocol handlers
            String onStr = "*:type=ProtocolHandler,*";
            ObjectName objectName = new ObjectName(onStr);
            Set set = mBeanServer.queryMBeans(objectName, null);
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                ObjectInstance oi = (ObjectInstance) iterator.next();
                protocolHandlers.addElement(oi.getObjectName());
            }

            // Query Thread Pools
            onStr = "*:type=ThreadPool,*";
            objectName = new ObjectName(onStr);
            set = mBeanServer.queryMBeans(objectName, null);
            iterator = set.iterator();
            while (iterator.hasNext()) {
                ObjectInstance oi = (ObjectInstance) iterator.next();
                threadPools.addElement(oi.getObjectName());
            }

            // Query Global Request Processors
            onStr = "*:type=GlobalRequestProcessor,*";
            objectName = new ObjectName(onStr);
            set = mBeanServer.queryMBeans(objectName, null);
            iterator = set.iterator();
            while (iterator.hasNext()) {
                ObjectInstance oi = (ObjectInstance) iterator.next();
                globalRequestProcessors.addElement(oi.getObjectName());
            }

            // Query Request Processors
            onStr = "*:type=RequestProcessor,*";
            objectName = new ObjectName(onStr);
            set = mBeanServer.queryMBeans(objectName, null);
            iterator = set.iterator();
            while (iterator.hasNext()) {
                ObjectInstance oi = (ObjectInstance) iterator.next();
                requestProcessors.addElement(oi.getObjectName());
            }

            // Register with MBean server
            onStr = "JMImplementation:type=MBeanServerDelegate";
            objectName = new ObjectName(onStr);
            mBeanServer.addNotificationListener(objectName, this, null, null);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * Finalize this servlet.
     */
    public void destroy() {

        ;       // No actions necessary

    }


    /**
     * Process a GET request for the specified resource.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet-specified error occurs
     */
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException {


        StringWriter sw = new StringWriter();
        sw.write("<status>");

        try {

            // Display virtual machine statistics
            writeVMState(sw);

            Enumeration enum = threadPools.elements();
            while (enum.hasMoreElements()) {
                ObjectName objectName = (ObjectName) enum.nextElement();
                String name = objectName.getKeyProperty("name");
                writeConnectorState(sw, objectName, name);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        sw.write("</status>");

        // Now dump it
        if (null==xsltFileName) {
            response.setContentType("text/xml");
            PrintWriter out = response.getWriter();
            out.print("<?xml version=\"1.0\"?>");
            out.print(sw.getBuffer().toString());
        } else {
            response.setContentType("text/html");
            PrintWriter out = response.getWriter();
            out.print("<?xml version=\"1.0\"?>");
            try {
                TransformerFactory tFactory = TransformerFactory.newInstance();
                Source xmlSource = new StreamSource(new 
StringReader(sw.getBuffer().toString()));
                Source xslSource = new 
StreamSource(getServletContext().getResourceAsStream(xsltFileName));
                Transformer transformer = tFactory.newTransformer(xslSource);
                transformer.transform(xmlSource, new StreamResult(out));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }


    /**
     * Write virtual machine state.
     */
    protected void writeVMState(StringWriter writer)
        throws Exception {

        writer.write("<jvm>");

        writer.write("<memory");
        writer.write(" free='" + Runtime.getRuntime().freeMemory() + "'");
        writer.write(" total='" + Runtime.getRuntime().totalMemory() + "'");
        writer.write(" max='" + Runtime.getRuntime().maxMemory() + "'/>");

        writer.write("</jvm>");

    }


    /**
     * Write connector state.
     */
    protected void writeConnectorState(StringWriter writer,
                                       ObjectName tpName, String name)
        throws Exception {

        writer.write("<connector name='" + name + "'>");

        writer.write("<threadInfo ");
        writer.write(" maxThreads=\"" + mBeanServer.getAttribute(tpName, "maxThreads") 
+ "\"");
        writer.write(" minSpareThreads=\"" + mBeanServer.getAttribute(tpName, 
"minSpareThreads") + "\"");
        writer.write(" maxSpareThreads=\"" + mBeanServer.getAttribute(tpName, 
"maxSpareThreads") + "\"");
        writer.write(" currentThreadCount=\"" + mBeanServer.getAttribute(tpName, 
"currentThreadCount") + "\"");
        writer.write(" currentThreadsBusy=\"" + mBeanServer.getAttribute(tpName, 
"currentThreadsBusy") + "\"");
        writer.write(" />");

        ObjectName grpName = null;

        Enumeration enum = globalRequestProcessors.elements();
        while (enum.hasMoreElements()) {
            ObjectName objectName = (ObjectName) enum.nextElement();
            if (name.equals(objectName.getKeyProperty("name"))) {
                grpName = objectName;
            }
        }

        if (grpName != null) {

            writer.write("<requestInfo ");
            writer.write(" maxTime=\"" + mBeanServer.getAttribute(grpName, "maxTime") 
+ "\"");
            writer.write(" processingTime=\"" + mBeanServer.getAttribute(grpName, 
"processingTime") + "\"");
            writer.write(" requestCount=\"" + mBeanServer.getAttribute(grpName, 
"requestCount") + "\"");
            writer.write(" errorCount=\"" + mBeanServer.getAttribute(grpName, 
"errorCount") + "\"");
            writer.write(" bytesReceived=\"" + mBeanServer.getAttribute(grpName, 
"bytesReceived") + "\"");
            writer.write(" bytesSent=\"" + mBeanServer.getAttribute(grpName, 
"bytesSent") + "\"");
            writer.write(" />");


            writer.write("<workers>");
            enum = requestProcessors.elements();
            while (enum.hasMoreElements()) {
                ObjectName objectName = (ObjectName) enum.nextElement();
                if (name.equals(objectName.getKeyProperty("worker"))) {
                    writer.write("<worker>");
                    writeProcessorState(writer, objectName);
                    writer.write("</worker>");
                }
            }
            writer.write("</workers>");
        }

        writer.write("</connector>");

    }


    /**
     * Write processor state.
     */
    protected void writeProcessorState(StringWriter writer, ObjectName pName)
        throws Exception {

        Integer stageValue =
            (Integer) mBeanServer.getAttribute(pName, "stage");
        int stage = stageValue.intValue();
        boolean fullStatus = true;


        writer.write("<stage>");
        switch (stage) {

        case (1/*org.apache.coyote.Constants.STAGE_PARSE*/):
            writer.write("P");
            fullStatus = false;
            break;
        case (2/*org.apache.coyote.Constants.STAGE_PREPARE*/):
            writer.write("P");
            fullStatus = false;
            break;
        case (3/*org.apache.coyote.Constants.STAGE_SERVICE*/):
            writer.write("S");
            break;
        case (4/*org.apache.coyote.Constants.STAGE_ENDINPUT*/):
            writer.write("F");
            break;
        case (5/*org.apache.coyote.Constants.STAGE_ENDOUTPUT*/):
            writer.write("F");
            break;
        case (7/*org.apache.coyote.Constants.STAGE_ENDED*/):
            writer.write("R");
            fullStatus = false;
            break;
        case (6/*org.apache.coyote.Constants.STAGE_KEEPALIVE*/):
            writer.write("K");
            fullStatus = false;
            break;
        case (0/*org.apache.coyote.Constants.STAGE_NEW*/):
            writer.write("R");
            fullStatus = false;
            break;
        default:
            writer.write("?");
            fullStatus = false;

        }

        writer.write("</stage>");

        if (fullStatus) {
            writer.write("<requestProcessingTime>");
            writer.write("" + mBeanServer.getAttribute
                         (pName, "requestProcessingTime"));
            writer.write("</requestProcessingTime>");
            writer.write("<requestBytesSent>");
            writer.write("" + mBeanServer.getAttribute
                         (pName, "requestBytesSent"));
            writer.write("</requestBytesSent>");
            writer.write("<requestBytesReceived>");
            writer.write("" + mBeanServer.getAttribute
                         (pName, "requestBytesReceived"));
            writer.write("</requestBytesReceived>");
            writer.write("<remoteAddr>");
            writer.write("" + mBeanServer.getAttribute(pName, "remoteAddr"));
            writer.write("</remoteAddr>");
            writer.write("<virtualHost>");
            writer.write("" + filter(mBeanServer.getAttribute
                                     (pName, "virtualHost").toString()));
            writer.write("</virtualHost>");
            writer.write("<method>");
            writer.write("" + filter(mBeanServer.getAttribute
                                     (pName, "method").toString()));
            writer.write("</method>");
            writer.write("<currentUri>");
            writer.write("" + filter(mBeanServer.getAttribute
                                     (pName, "currentUri").toString()));
            writer.write("</currentUri>");
            writer.write("<currentQueryString>");
            String queryString = (String) mBeanServer.getAttribute
                (pName, "currentQueryString");
            if ((queryString != null) && (!queryString.equals(""))) {
                writer.write("?");
                writer.write(queryString);
            }
            writer.write("</currentQueryString>");
        }

    }


    /**
     * Filter the specified message string for characters that are sensitive
     * in HTML.  This avoids potential attacks caused by including JavaScript
     * codes in the request URL that is often reported in error messages.
     *
     * @param message The message string to be filtered
     */
    public static String filter(String message) {

        if (message == null)
            return (null);

        char content[] = new char[message.length()];
        message.getChars(0, message.length(), content, 0);
        StringBuffer result = new StringBuffer(content.length + 50);
        for (int i = 0; i < content.length; i++) {
            switch (content[i]) {
            case '<':
                result.append("&lt;");
                break;
            case '>':
                result.append("&gt;");
                break;
            case '&':
                result.append("&amp;");
                break;
            case '"':
                result.append("&quot;");
                break;
            default:
                result.append(content[i]);
            }
        }
        return (result.toString());

    }


    // ------------------------------------------- NotificationListener Methods


    public void handleNotification(Notification notification,
                                   java.lang.Object handback) {

        if (notification instanceof MBeanServerNotification) {
            ObjectName objectName =
                ((MBeanServerNotification) notification).getMBeanName();
            if (notification.getType().equals
                (MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
                String type = objectName.getKeyProperty("type");
                if (type != null) {
                    if (type.equals("ProtocolHandler")) {
                        protocolHandlers.addElement(objectName);
                    } else if (type.equals("ThreadPool")) {
                        threadPools.addElement(objectName);
                    } else if (type.equals("GlobalRequestProcessor")) {
                        globalRequestProcessors.addElement(objectName);
                    } else if (type.equals("RequestProcessor")) {
                        requestProcessors.addElement(objectName);
                    }
                }
            } else if (notification.getType().equals
                       (MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
                String type = objectName.getKeyProperty("type");
                if (type != null) {
                    if (type.equals("ProtocolHandler")) {
                        protocolHandlers.removeElement(objectName);
                    } else if (type.equals("ThreadPool")) {
                        threadPools.removeElement(objectName);
                    } else if (type.equals("GlobalRequestProcessor")) {
                        globalRequestProcessors.removeElement(objectName);
                    } else if (type.equals("RequestProcessor")) {
                        requestProcessors.removeElement(objectName);
                    }
                }
            }
        }

    }


}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to