Home
Implementation
Testing
Add-Ons
> disman API
> Smurf
> JAX
> Schedule-MIB
> Tcl Engine
> Policy Mgmt
Documentation
Download
Mailinglist
People



The Jasmin Project
Adding WWW-MIB AgentX Sub-Agent Support to Jigsaw
Sven Mertens <mertens@ibr.cs.tu-bs.de>

July 2000

This is an example on how to use the JAX package. It describes the measures necessary to add an SNMP interface to an existing Java application by talking AgentX to a master SNMP agent. The application used in this example is the Jigsaw web server. It has been extended to export some of the Objects defined in RFC 2594 known as the WWW-MIB. While writing this example the UCD-SNMP served as the master agent, but JAX should work with any other AgentX enabled SNMP agent.

Preliminaries

The WWW-MIB: Brief Overview

The WWW-MIB was designed to monitor WWW-services. There are two compliance statements in the MIB definition. The full compliance contains separate tables for all incoming and outgoing requests and for all incoming and outgoing responses and a bucket mechanism to keep detailed statistics about the biggest and most frequently accessed documents.

An agent with minimal compliance just has to implement a table of the services and a short summary table. Of course, both are also required for full compliance.

In this example we decided to concentrate on the minimal conformance especially to avoid a complex bucket handling. The code is intended to serve as an example and therefore we strive for easy understanding and simplicity, but we also included three additional tables that contain the last documents and the types of requests and responses along with their number, size and timestamps.

In order to support this subset of the WWW-MIB the Agent has to keep the following information:

  1. names of the accessed documents
  2. sizes of requests and replies
  3. timestamps of protocol actions
  4. service names, ports and servers
  5. details about response success

Generating the Java Files

We use smidump to generate 33 Java class files for the MIB objects in the WWW-MIB:

% smidump -f jax WWW-MIB

WwwDocAccessTopNEntry.java WwwRequestInTable.java
WwwDocAccessTopNEntryImpl.javaWwwRequestOutEntry.java
WwwDocAccessTopNTable.java WwwRequestOutEntryImpl.java
WwwDocBucketEntry.java WwwRequestOutTable.java
WwwDocBucketEntryImpl.java WwwResponseInEntry.java
WwwDocBucketTable.java WwwResponseInEntryImpl.java
WwwDocBytesTopNEntry.java WwwResponseInTable.java
WwwDocBytesTopNEntryImpl.java WwwResponseOutEntry.java
WwwDocBytesTopNTable.java WwwResponseOutEntryImpl.java
WwwDocCtrlEntry.java WwwResponseOutTable.java
WwwDocCtrlEntryImpl.java WwwServiceEntry.java
WwwDocCtrlTable.java WwwServiceEntryImpl.java
WwwDocLastNEntry.java WwwServiceTable.java
WwwDocLastNEntryImpl.java WwwSummaryEntry.java
WwwDocLastNTable.java WwwSummaryEntryImpl.java
WwwRequestInEntry.java WwwSummaryTable.java
WwwRequestInEntryImpl.java

As already stated, we do not intend to make a full conforming agent. So we only need fifteen of the files which are used to implement five tables of the WWW-MIB:

WwwDocLastNEntry.java WwwDocLastNEntryImpl.java WwwDocLastNTable.java
WwwServiceEntry.java WwwServiceEntryImpl.java WwwServiceTable.java
WwwSummaryEntry.java WwwSummaryEntryImpl.java WwwSummaryTable.java
WwwRequestsInEntry.java WwwRequestsInEntryImpl.java WwwRequestsInTable.java
WwwResponseOutEntry.java WwwResponseOutEntryImpl.java WwwResponseOutTable.java

The classes that represent an entire table are called *Table.class. The classes that represent rows of a table are derived form *Entry.class. All classes can be compiled as they are, though this would not affect any data of the instrumented application. The *EntryImpl.class files need some instrumentation to achieve this, so that SNMP operations on their MIB objects can result in appropriate actions.

Interfacing with the Jigsaw Code Base

Now lets take a look at the Jigsaw code base to find out where to start the AgentX-threads and gather the information we need.

In Jigsaw the class httpd represents a web service. Whenever a request is handled the method updateStatistics() is called. As the WWW-MIB from the viewpoint of this object serves a similar purpose, this is the appropriate place to add a call to the new code.

The AgentX main object runs in a separate thread and the httpd objects call its updateMIB() method to keep the MIB information up to date.

Of course, before calling the updateMIB() method we have to create the object that implements it. In the start() method of the httpd class the AgentX main object is created.

Note that there are two instances of the httpd object. The first instance is for web based configuration of Jigsaw (jigadmin). It is a separate service and therefore has its own entry in the WwwServiceTable.

    public void start ()
        throws ServerHandlerInitException
    {
        if (!isAClone) {
            if (jaxMain == null) {
                jaxMain = new JigAgentX();
                jaxMain.start();
            }
            // ...
        }
        serviceId = jaxMain.addService(port);
    }

To distinguish between normal requests and those for the jigadmin service every httpd object first registers with JigAgentX and gets a unique identifier (serviceId). This identifier is used when updateMIB() is called from a httpd object.

    public void log (Client client
                     , Request request, Reply reply
                     , int nbytes
                     , long duration) {
        if ( logger != null )
            logger.log (request, reply, nbytes, duration);
        synchronized (jaxMain) {
            jaxMain.updateMIB(serviceId, client, request, reply,
                              nbytes, duration);
        }
        statistics.updateStatistics(client, request, reply,
                                    nbytes, duration);
    }
Implementing the WWW-MIB Objects

The AgentX main class JigAgentX creates the table objects of the WWW-MIB and adds, removes or modifies entries whenever updateMIB() is called.

The table objects of the classes WwwServiceTable, WwwSummaryTable and WwwDocLastNTable

public class JigAgentX extends Thread
{
    // ...
    public synchronized void updateMIB (int service,
                                        Client client,
                                        Request request, Reply reply,
                                        int nbytes,
                                        long duration)
    // ...

    public int addService(long port)
    // ...

    
    public void run ()
    {
        // ...
        // create the AgentX session
        connection = new AgentXConnection(host, port);
        session = new AgentXSession();
        connection.openSession(session);

        // create the MIB tables
        wstab = new WwwServiceTable();
        sumTab = new WwwSummaryTable();
        lastTab = new WwwDocLastNTable();
        reqTab = new WwwRequestInTable();
        replyTab = new WwwResponseOutTable();
        
        // register tables with session object
        session.addGroup(wstab);
        session.addGroup(sumTab);
        session.addGroup(lastTab);
        session.addGroup(reqTab);
        session.addGroup(replyTab);

        // register subtree with master agent
        registration = new AgentXRegistration(new AgentXOID(value));
        session.register(registration);

        // run until we get interrupted
        try {
            while(true) Thread.sleep(90000);
        } catch (InterruptedException e) {}

        // ...
    }
}

The WwwSummaryTable and WwwServiceTable each have one row per service. Jigsaw has a single HTTP service therefore both entries are created on startup. The WwwDocLastNTable is created without any entry. These entries will be created later by the updateMIB() method.

The Table of Services

In the WwwServiceTable there are some attributes containing information Jigsaw does not manage on its own, such as how to contact the operator and a brief description of the services. This rather static information is stored in a Jigsaw configuration file. We added jax.jigadminDesc, jax.jigsawDesc, jax.serviceContact to Jigsaw/config/server.props. Jigsaw loads these properties by default and they can be read using System.getProperty() in the constructor of WwwServiceEntryImpl.

    public WwwServiceEntryImpl(long wwwServiceIndex, long port)
    {
        super(wwwServiceIndex);
        long tcpOid[] = {1,3,6,1,2,1,27,4,port};
        wwwServiceDescription = ((wwwServiceIndex == 1 ) ?
                                 System.getProperty("jax.jigadminDesc") :
                                 System.getProperty("jax.jigsawDesc")
            ).getBytes();
        wwwServiceContact = (System.getProperty("jax.serviceContact")
                             ).getBytes();
        wwwServiceProtocol = new AgentXOID(tcpOid);
        try {
            wwwServiceName = ("" + InetAddress.getLocalHost()).getBytes();
        }
        catch (UnknownHostException e) {
             wwwServiceName = ("Unknown Host").getBytes();
        }
        wwwServiceType = 2;
        byte timeStamp[] = Util.getTimeStamp();
        wwwServiceStartTime = timeStamp;
        wwwServiceOperStatus = 2;
        wwwServiceLastChange = timeStamp;
    }

Strings are passed to the JAX-methods as byte[]. This is a little annoyance but necessary. This mechanism is flexible enough to deal with all cases of octet strings used in SNMP.

The wwwServiceLastChange and wwwServiceStartTime objects are set to the same value as the JAX main object is constructed after the initialization of the service and the state will not change until termination. The notation used for DateAndTime is a binary form unusual in Java objects. I wrote a special utility object that would export methods to generate timestamps according to the DateAndTime textual convention:

public class Util
{
    public static byte[] getTimeStamp() {
        char sign = '+';
        GregorianCalendar now = new GregorianCalendar();
        int offset = (now.getTimeZone()).getRawOffset() / 1000;
        if (offset < 0) {
            offset = (-1) * offset;
            sign = '-';
        }
        return makeDateAndTime(now.get(Calendar.YEAR),
                               now.get(Calendar.MONTH),
                               now.get(Calendar.DAY_OF_MONTH),
                               now.get(Calendar.HOUR_OF_DAY),
                               now.get(Calendar.MINUTE),
                               now.get(Calendar.SECOND),
                               0, // no deci seconds
                               sign,
                               (offset / 60),
                               (offset % 60)
                               );
    }

    public static byte[] makeDateAndTime(int year,
                                         int month,
                                         int day,
                                         int hour,
                                         int min,
                                         int sec,
                                         int decSec,
                                         char directionFromUTC,
                                         int hoursFromUTC,
                                         int minutesFromUTC) {
            byte out[] = new byte[11];
            out[0] = (byte) (year / 256);
            out[1] = (byte) (year % 256);
            out[2] = (byte) (month + 1);
            out[3] = (byte) day;
            out[4] = (byte) hour;
            out[5] = (byte) min;
            out[6] = (byte) sec;
            out[7] = (byte) decSec;
            out[8] = (byte) directionFromUTC;
            out[9] = (byte) hoursFromUTC;
            out[10] = (byte) minutesFromUTC;
            return out;
        }
}

New entries in the WwwServiceTable and WwwSummaryTable are created and added by the addService() method of the JigAgentX) class.

    public int addService(long port) {
        WwwServiceEntryImpl wsent;
        WwwSummaryEntryImpl sumEnt;
         
        wsent = new WwwServiceEntryImpl(serviceId, port);
        sumEnt = new WwwSummaryEntryImpl(serviceId);

	// add both entries to the global table instances in this object
        wstab.addEntry(wsent);
        sumTab.addEntry(sumEnt);
        return serviceId++;
    }

The Table that Summarizes Service Activity

In the WwwSummaryTable all protocol messages are counted and their sizes are summed up separately for incoming and outgoing requests and responses. Since Jigsaw does not work as a proxy server we just have to account for outgoing responses and incoming requests, so we added two methods to the WwwSummaryEntryImpl class to be able to set those values.

    public void addInRequest(long size)
    {
        wwwSummaryInBytes += size;
        wwwSummaryInRequests++;
    }

    public void addOutResponses(long size)
    {
        wwwSummaryOutBytes += size;
        wwwSummaryOutResponses++;
    }

To avoid updating the values of wwwSummary*LowBytes we compute them on the fly during get requests.

    public long get_wwwSummaryInLowBytes()
    {
        return (wwwSummaryInBytes % (((long) 1) << 32));
    }

Both add*() methods are called from the updateSummaryTable() method of the AgentX main object.

    private void updateSummaryTable(int service, long reqLength,
                                    long replLength) {
        
        // find the summary entry of this service in Vector
        WwwSummaryEntryImpl sumEnt = null;
        Enumeration enum = sumTab.elements();
        for(int k = 0; enum.hasMoreElements(); k++) {
            sumEnt = (WwwSummaryEntryImpl) enum.nextElement();
            if (service == sumEnt.get_wwwServiceIndex())
                break;
        }
        // add summary information
        if (sumEnt != null) {
            sumEnt.addInRequest(reqLength);
            sumEnt.addOutResponses(replLength);
        }
    }

In turn, updateSummaryTable() is called from updateMIB(). The latter also calls methods to handle objects of other tables.

    public synchronized void updateMIB(int service,
                                       Client client,
                                       Request request,
				       Reply reply,
                                       int nbytes,
                                       long duration)
    {
        long requestLength = (request.hasContentLength() ?
                              request.getContentLength() :
                              (request.getURLPath()).length());
        updateDocLastNTable(service, request.getURLPath(),
	                    request.getMethod(), reply.getStatus(),
                            reply.getReason(), nbytes);
        updateSummaryTable(service, requestLength, nbytes);
        updateRequestTable(service, request.getMethod(),
                           requestLength);
        updateResponseTable(service, reply.getStatus(), nbytes);
    }

The Table of Incoming Requests

The WwwRequestInTable stores type, time and size of all requests sent via the service. For this purpose the method updateRequestType() was introduced in the WwwRequestInEntryImpl class.

    public void updateRequestType(long count, long inBytes)
    {
        wwwRequestInBytes    = inBytes;
        wwwRequestInRequests = count;
        wwwRequestInLastTime = Util.getTimeStamp();
    }

New rows are inserted into the table each time a new request is encountered. Again updateMIB() calls a special updateRequestTable() method that will create new rows or update existing ones depending on the type of the new request and the called service.

    private void updateRequestTable(int service, String index, long size)
    {
        WwwRequestInEntryImpl row = null;
        int k = 0;
        Enumeration enum = reqTab.elements();
        // check for an existing entry with same request type
        for (k = 0; enum.hasMoreElements(); k++) {
            row = (WwwRequestInEntryImpl) enum.nextElement();
            if (service == row.get_wwwServiceIndex()
                && index.equals(new String(row.get_wwwRequestInIndex())))
                break;
        }
        if (k == reqTab.size()) {
            row = new WwwRequestInEntryImpl(service, index);
            reqTab.addEntry(row);
        }
        row.updateRequestType(row.get_wwwRequestInRequests() + 1, size);
    }

The Table of Outgoing Responses

Like for the WwwRequestInTable above the updateMIB() method in JigAgentX.java calls updateResponseTable() that will create new rows or update existing ones depending on the type of the new response and the called service.

    private void updateResponseTable(int service, int index, long size)
    {
        WwwResponseOutEntryImpl row = null;
        int k = 0;
        Enumeration enum = replyTab.elements();
        // check for an existing entry with same request type
        for (k = 0; enum.hasMoreElements(); k++) {
            row = (WwwResponseOutEntryImpl) enum.nextElement();
            if (service == row.get_wwwServiceIndex()
                && index == row.get_wwwResponseOutIndex())
                    break;
        }
        if (k == replyTab.size()) {
            row = new WwwResponseOutEntryImpl(service, index);
            replyTab.addEntry(row);
        }
        row.updateResponseType(row.get_wwwResponseOutResponses() + 1,
	                       size);
    }

Similarly updateResponseType() looks just like updateRequestType() in the WwwRequestInEntryImpl class.

    public void updateResponseType(long count, long outBytes)
    {
        wwwResponseOutBytes     = outBytes;
        wwwResponseOutResponses = count;
        wwwResponseOutLastTime  = Util.getTimeStamp();
    }

The Table of the Last Documents that were Requested

The *entryImpl classes do not have methods to store information within the object, so I wrote at first a new method for WwwDocLastNEntryImpl called setDocInfo(), which sets name, type, status and size.

    public void setDocInfo(String name, String reqType,
                           int resType, String state, long size) {
        wwwDocLastNName         = name.getBytes();
        wwwDocLastNBytes        = size;
        wwwDocLastNResponseType = resType;
        wwwDocLastNRequestType  = reqType.getBytes();
        wwwDocLastNStatusMsg    = state.getBytes();
        wwwDocLastNTimeStamp    = Util.getTimeStamp();
    }

setDocInfo() is called from the updateMIB() method via updateDocLastNTable() right after the construction of the new row object.

    private void updateDocLastNTable(int service, String url,
                                     String reqType, int status,
                                     String reason, long size) {
        
        // add a new line to the DocLastNTable of this Service
        WwwDocLastNEntryImpl ent = new WwwDocLastNEntryImpl(service,
	                                                    nextDocIndex++);
        ent.setDocInfo(url, reqType, status, reason, size);
        lastTab.addEntry(ent);

        // remove superfluous entries in DocLastNTable
        if (lastTab.size() > max_n) {
            Enumeration all = lastTab.elements();
            for (int k = lastTab.size() - max_n;
                 k > 0 && all.hasMoreElements(); k--) {
                ent = (WwwDocLastNEntryImpl) all.nextElement();
                lastTab.removeEntry(ent);
            }
        }
    }

The first index of the LastDocNTable is the service id of the httpd object handling the request. The second index is set to nextDocIndex (starting at 1) and nextDocIndex is incremented subsequently. The entry is then added to the table. If the number of entries exceeds max_n the oldest entries are deleted.


© 2000 TU Braunschweig, NEC C&C Europe    -    Wed Sep 5 12:55:04 2001