Creating instrumentation with MBeans from scratch can be time-consuming and tedious . The JMX model MBean specification [1] provides generic instrumentation that can be quickly customized for many resources. This chapter describes the model MBean in detail and provides multiple examples of its use in practice. A model MBean is a fully customizable dynamic MBean. The RequiredModelMBean class, which implements the ModelMBean interface, is guaranteed to exist on every JMX agent. A model MBean implements the ModelMBean interface that extends a set of interfaces including DynamicMBean , PersistentMBean , and ModelMBeanNotificationBroadcaster . Like the open MBean, the model MBean has it's own extended MBeanInfo interface called ModelMBeanInfo . ModelMBeanInfo associates new, extensible metadata objects called descriptors with the management interface. Descriptors add additional information about the managed resource and are also used to customize the behavior of a RequiredModelMBean instance. In this chapter, when we say simply "model MBeans" we are referring to implementations of the model MBean or RequiredModelMBean . When we are referring to the ModelMBean interface, we will say "the ModelMBean interface." Model MBeans were added to JMX for several reasons:
4.1.1 The Simplest Model MBean ExampleIn this section we will look at a simple example of how to use a model MBean. This example shows how to make a resource manageable in as little as five lines of code. In this example we expose attributes and operations of the managed resource and send a notification upon an error condition. Recall from Chapter 3, on MBeans, that the Apache server application had its own standard MBean. Let's look at what it would take to create a model MBean for the Apache server. We will have to write a Java utility, ApacheServer , to get the information from the Apache module and return it to the model MBean. In our scenario, the ApacheServer class is similar to the Apache implementation, but it does not implement the ApacheMBean class, and it does not have to support value caching (see Code Block 3 in the text that follows ). The model MBean will provide caching support at a much finer grain. The caching policy will be defined in the attribute descriptors in ModelMBeanInfo . Therefore, we don't support the isCaching() , getCacheLifetime() , setCacheLifetime() , getCaching() , and setCaching() methods in the original standard MBean interface. The ApacheServer class is the managed resource. The first code block that follows shows how to find the MBeanServer, instantiate the model MBean, instantiate ApacheServer , associate it with the model MBean, configure the model MBean, and send a notification if the server is down at this time. The second code block shows how to configure the model MBean through ModelMBeanInfo . Notice that we can selectively cache the data from the Apache server, choosing not to cache some of the more time-sensitive counters. The third code block shows the slightly modified Apache class from Chapter 3. All of the examples in this book (plus some others) are available on the book's Web site: http://www.awprofessional.com/titles/0672324083. Code Block 1 package jnjmx.ch4; import javax.management.*; import javax.management.modelmbean.*; import java.lang.reflect.Constructor; import java.util.ArrayList; /** * @author Heather Kreger * Chapter 4: Java and JMX Building Manageable Systems * ApacheServerManager: Manages the ApacheServer as a managed * resource using a model MBean. * * ModelMBeanInfo is created using ModelMBeanInfo APIs * rather than an XML file. */ public class ApacheServerManager { public static void main(String[] args) { boolean tracing = false; String serverURL = ""; try { if (args.length != 0) { System.out.println(args[0]); if (args[0] != null ) serverURL = serverURL.concat(args[0]); else serverURL = "http://www.apache.org/server-status?auto"; } // This serverURL default mechanism is just for this example // We would not recommend this in a real application if (!serverURL.startsWith("http")) serverURL = "http://www.apache.org/server-status?auto"; /** Find an MBeanServer for our MBean */ MBeanServer myMBS = MBeanServerFactory.createMBeanServer(); /** Instantiate an unconfigured RequiredModelMBean in the MBeanServer */ RequiredModelMBean apacheMMBean = (RequiredModelMBean) myMBS.instantiate( "javax.management.modelmbean.RequiredModelMBean"); /** Create a name for the MBean * domain = apacheManager * one attribute: id = ApacheServer*/ ObjectName apacheMBeanName = new ObjectName("apacheManager: id=ApacheServer"); /** Instantiate the ApacheServer utility to be delegated to */ jnjmx.ch4.ApacheServer myApacheServer = new jnjmx.ch4.ApacheServer(serverURL); /** Set the configuration of the ModelMBean through the ModelMBeanInfo ** This method is below */ ApacheServerManager asmgr = new ApacheServerManager(); ModelMBeanInfo apacheMMBeanInfo = asmgr.createApacheModelMBeanInfo( myApacheServer, apacheMBeanName); apacheMMBean.setModelMBeanInfo(apacheMMBeanInfo); // ** Set the ApacheServer as the managed resource apacheMMBean.setManagedResource(myApacheServer, "ObjectReference"); // ** Register the ModelMBean instance that is now ready to run with the // MBeanServer ObjectInstance registeredApacheMMBean = myMBS.registerMBean((Object) apacheMMBean, apacheMBeanName); // ** Use the ModelMBean to send the notification into JMX where it gets // sent to a manager adapter that is registered for notifications from the // Apache MBeans and sends them to a pager system String apacheStatus = (String) apacheMMBean.invoke("getState", null, null ); if ((apacheStatus.equals("DOWN")) (apacheStatus.equals("NOT_RESPONDING"))) { System.out.println("Apache Server is Down"); apacheMMBean.sendNotification("Apache Server is Down"); /* You could create monitor to notify when up * again so Web traffic can be routed back to it */ } else System.out.println("Apache Server is " + apacheStatus); } catch (Exception e) { System.out.println( "Apache Server Manager failed with " + e.getMessage()); } System.exit(0); } Because model MBeans support the NotificationBroadcaster interface, when the getStatus() method returns Down or Not Responding , we have the model MBean send a notification to the MBeanServer, which in turn sends it to all registered notification listeners. This is fairly simple to do; we merely
Here's how we set the ModelMBeanInfo instance that we used to customize this model MBean. The ModelMBeanInfo object created for the ApacheServer managed application used in the Apache model MBean defines the following:
Code Block 2 illustrates the programmatic way to create a ModelMBeanInfo instance. ModelMBeanInfo can also be initialized from data saved in a file. This is shown in the XML primer examples later in this chapter (see Section 4.7). It is much simpler code, but it requires the use of an XML service that is not a standard service of JMX. This entire method is relatively lengthy, but it is very simple to program and it is easy for tools to generate. The method in its entirety is listed here. We will explain how to set an attribute, an operation, and a notification in the next section. Code Block 2 // Create the ModelMBeanInfo for the Apache server ModelMBeanInfo createApacheModelMBeanInfo( ApacheServer managedApache, ObjectName apacheMBeanName) { // Set the ApacheServer's class name, there are other // more flexible ways to do this Class apacheClass = null; try { apacheClass = Class.forName("jnjmx.ch4.ApacheServer"); } catch (Exception e) { System.out.println("ApacheServer Class not found"); } // Set the MBean's descriptor with default policies // MBean name is apacheMBeanName // logging notifications to jmxmain.log // caching attributes for 10 seconds Descriptor apacheDescription = new DescriptorSupport( new String[] { ("name=" + apacheMBeanName), "descriptorType=mbean", ("displayName=ApacheServerManager"), "type=jnjmx.ch4.ApacheServer", "log=T", "logFile=jmxmain.log", "currencyTimeLimit=10" }); // Define attributes in ModelMBeanAttributeInfo instances ModelMBeanAttributeInfo[] apacheAttributes = new ModelMBeanAttributeInfo[11]; // Declare BusyWorkers attribute // cache BusyWorkers for 3 seconds, // use get method Descriptor busyWorkersDesc = new DescriptorSupport( new String[] { "name=BusyWorkers", "descriptorType=attribute", "displayName=Apache BusyWorkers", "getMethod=getBusyWorkers", "currencyTimeLimit=3" }); apacheAttributes[0] = new ModelMBeanAttributeInfo( "BusyWorkers", "int", "Apache Server Busy Workers", true, false, false, busyWorkersDesc); // Declare BytesPerSec attribute // Cache ByetesperSec for 10 seconds Descriptor bytesPerSecDesc = new DescriptorSupport( new String[] { "name=BytesPerSec", "descriptorType=attribute", "displayName=Apache BytesPerSec", "getMethod=getBytesPerSec", "currencyTimeLimit=10" }); apacheAttributes[1] = new ModelMBeanAttributeInfo( "BytesPerSec", "int", "Apache Server Bytes Per Sec", true, false, false, bytesPerSecDesc); // Declare BytesPerReq attribute Descriptor bytesPerReqDesc = new DescriptorSupport( new String[] { "name=BytesPerReq", "descriptorType=attribute", "displayName=Apache BytesPerReq", "getMethod=getBytesPerReq", "currencyTimeLimit=10" }); apacheAttributes[2] = new ModelMBeanAttributeInfo( "BytesPerReq", "int", "Apache Server Bytes Per Request", true, false, false, bytesPerReqDesc); // Declare IdleWorkers attribute Descriptor idleWorkersDesc = new DescriptorSupport( new String[] { "name=IdleWorkers", "descriptorType=attribute", "displayName=Apache IdleWorkers", "getMethod=getIdleWorkers", "currencyTimeLimit=10" }); apacheAttributes[3] = new ModelMBeanAttributeInfo( "IdleWorkers", "int", "Apache Server Idle Workers", true, false, false, idleWorkersDesc); // Declare ReqPerSec attribute Descriptor reqPerSecDesc = new DescriptorSupport( new String[] { "name=ReqPerSec", "descriptorType=attribute", "displayName=Apache ReqPerSec", "getMethod=getReqPerSec", "currencyTimeLimit=5" }); apacheAttributes[4] = new ModelMBeanAttributeInfo( "ReqPerSec", "int", "Apache Server Requests Per Second", true, false, false, reqPerSecDesc); // Declare Scoreboard attribute Descriptor ScoreboardDesc = new DescriptorSupport( new String[] { "name=Scoreboard", "descriptorType=attribute", "displayName=Apache Scoreboard", "getMethod=getScoreboard", "currencyTimeLimit=10" }); apacheAttributes[5] = new ModelMBeanAttributeInfo( "Scoreboard", "java.lang.String", "Apache Server Scoreboard", true, false, false, ScoreboardDesc); // Declare TotalAccesses attribute // Do not cache TotalAccesses Descriptor totalAccessesDesc = new DescriptorSupport( new String[] { "name=TotalAccesses", "descriptorType=attribute", "displayName=Apache TotalAccesses", "getMethod=getTotalAccesses", "currencyTimeLimit=-1" }); apacheAttributes[6] = new ModelMBeanAttributeInfo( "TotalAccesses", "int", "Apache Server total accesses", true, false, false, totalAccessesDesc); // Declare TotalKBytes attribute // Do not cache TotalKBytes Descriptor totalKBytesDesc = new DescriptorSupport( new String[] { "name=TotalKBytes", "descriptorType=attribute", "displayName=Apache TotalKBytes", "getMethod=getTotalKBytes", "currencyTimeLimit=-1" }); apacheAttributes[7] = new ModelMBeanAttributeInfo( "TotalKBytes", "int", "Apache Server total KiloBytes", true, false, false, totalKBytesDesc); // Declare Uptime attribute Descriptor uptimeDesc = new DescriptorSupport( new String[] { "name=Uptime", "descriptorType=attribute", "displayName=Apache Uptime", "getMethod=getUptime", "currencyTimeLimit=10" }); apacheAttributes[8] = new ModelMBeanAttributeInfo( "Uptime", "java.lang.String", "Apache Server Up Time", true, false, false, uptimeDesc); // Declare State attribute // State has a getMethod Descriptor stateDesc = new DescriptorSupport( new String[] { "name=State", "descriptorType=attribute", "displayName=Apache State", "getMethod=getState", "currencyTimeLimit=10" }); apacheAttributes[9] = new ModelMBeanAttributeInfo( "State", "java.lang.String", "Apache Server state", true, false, false, stateDesc); // Declare Server attribute // Server has a getMethod and a setMethod Descriptor serverDesc = new DescriptorSupport( new String[] { "name=Server", "descriptorType=attribute", "displayName=Apache Server URL", "getMethod=getServer", "setMethod=setServer", "currencyTimeLimit=10" }); apacheAttributes[10] = new ModelMBeanAttributeInfo( "Server", "java.lang.String", "Apache Server Busy Workers", true, true, false, serverDesc); // Declare constructors for the managed resource // one constructor which accepts one parameter, // a String URL Constructor[] myConstructors = apacheClass.getConstructors(); ModelMBeanConstructorInfo[] apacheConstructors = new ModelMBeanConstructorInfo[1]; MBeanParameterInfo[] constructorParms = new MBeanParameterInfo[] { ( new MBeanParameterInfo("serverURL", "java.lang.String", "Apache Server URL"))}; Descriptor apacheBeanDesc = new DescriptorSupport( new String[] { "name=ApacheServer", "descriptorType=operation", "role=constructor" }); apacheConstructors[0] = new ModelMBeanConstructorInfo( "Apache", "ApacheServer(): Constructs an ApacheServer utility class", constructorParms, apacheBeanDesc); // Define operations in ModelMBeanOperationInfo instances /* Operations: getBusyWorkers, getBytesPerSec, getBytesPerReq, * getIdleWorkers, getReqPerSec, getScoreboard, getTotalAccesses, * getTotalKBytes, getUptime are satisfied by getValue, getState, * getServer, setServer(String URL), start, stop have own operations */ // Declare getValue operation String getValue() // Set parameter array ModelMBeanOperationInfo[] apacheOperations = new ModelMBeanOperationInfo[6]; MBeanParameterInfo[] getParms = new MBeanParameterInfo[0]; MBeanParameterInfo[] getValueParms = new MBeanParameterInfo[] { ( new MBeanParameterInfo("FieldName", "java.lang.String", "Apache status field name"))}; Descriptor getValueDesc = new DescriptorSupport( new String[] { "name=getValue", "descriptorType=operation", "class=ApacheServer", "role=operation" }); apacheOperations[0] = new ModelMBeanOperationInfo( "getValue", "getValue(): get an apache status field", getValueParms, "java.lang.String", MBeanOperationInfo.INFO, getValueDesc); Descriptor getStateDesc = new DescriptorSupport( new String[] { "name=getState", "descriptorType=operation", "class=ApacheServer", "role=operation" }); apacheOperations[1] = new ModelMBeanOperationInfo( "getState", "getState(): current status of apache server", getParms, "java.lang.String", MBeanOperationInfo.INFO, getStateDesc); Descriptor getServerDesc = new DescriptorSupport( new String[] {"name=getServer", "descriptorType=operation", "class=ApacheServer", "role=operation" }); apacheOperations[2] = new ModelMBeanOperationInfo("getServer", "getServer(): URL of apache server", getParms, "java.lang.Integer", MBeanOperationInfo.INFO, getServerDesc); MBeanParameterInfo[] setParms = new MBeanParameterInfo[] { ( new MBeanParameterInfo("url", "java.lang.String", "Apache Server URL"))}; Descriptor setServerDesc = new DescriptorSupport( new String[] {"name=getServer", "descriptorType=operation", "class=ApacheServer", "role=operation" }); apacheOperations[3] = new ModelMBeanOperationInfo("setServer", "getServer(): URL of apache server", setParms, "java.lang.String", MBeanOperationInfo.ACTION, setServerDesc); MBeanParameterInfo[] startParms = new MBeanParameterInfo[0]; Descriptor startDesc = new DescriptorSupport( new String[] {"name=start", "descriptorType=operation", "class=ApacheServer", "role=operation" }); apacheOperations[4] = new ModelMBeanOperationInfo("start", "start(): start apache server", startParms, "java.lang.Integer", MBeanOperationInfo.ACTION, startDesc); MBeanParameterInfo[] stopParms = new MBeanParameterInfo[0]; Descriptor stopDesc = new DescriptorSupport( new String[] {"name=stop", "descriptorType=operation", "class=ApacheServer", "role=operation" }); apacheOperations[5] = new ModelMBeanOperationInfo("stop", "stop(): start apache server", stopParms, "java.lang.Integer", MBeanOperationInfo.ACTION, stopDesc); /* getters/setters for operations */ // MBeanParameterInfo[] getParms = new MBeanParameterInfo[0]; Descriptor bytespsDesc = new DescriptorSupport( new String[] {"name=getBytesPerSecond", "descriptorType=operation", "class=ApacheServer", "role=operation" }); apacheOperations[6] = new ModelMBeanOperationInfo("getBytesPerSecond", "number of bytes per second processed", getParms, "int", MBeanOperationInfo.ACTION, bytespsDesc); // Define notifications in ModelMBeanNotificationInfo // declare an "Apache Server Down" notification ModelMBeanNotificationInfo[] apacheNotifications = new ModelMBeanNotificationInfo[1]; Descriptor apacheDownEventDesc = new DescriptorSupport( new String[] { "descriptorType=notification", "name=jmx.ModelMBean.General.Apache.Down", "severity=1", "MessageId=Apache001" }); apacheNotifications[0] = new ModelMBeanNotificationInfo( new String[] {"jmx.ModelMBean.General.Apache.Down" }, "jmx.ModelMBean.General", "Apache Server Down", apacheDownEventDesc); // Create the ModelMBeanInfo ModelMBeanInfo apacheMMBeanInfo = new ModelMBeanInfoSupport( "Apache", "ModelMBean for managing an Apache Web Server", apacheAttributes, apacheConstructors, apacheOperations, apacheNotifications); // Set the MBean's Descriptor for the ModelMBeanInfo try { apacheMMBeanInfo.setMBeanDescriptor(apacheDescription); } catch (Exception e) { System.out.println("CreateMBeanInfo failed with " + e.getMessage()); } return apacheMMBeanInfo; } Finally, just to complete the example, even though this is basically what's in Chapter 3 as the Apache class, here is the ApacheServer class for getting the data for the Apache server resource we are managing: Code Block 3 package jnjmx.ch4; /** * @author Heather Kreger * Chapter 4: Java and JMX: Building Manageable Systems * ApacheServer: interacts with an Apache server through a URL */ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.HashMap; import java.util.StringTokenizer; import javax.management.*; public class ApacheServer { // String constants for each of the Apache-related attributes private static final String BUSY_WORKERS = "BusyWorkers"; private static final String BYTES_PER_REQ = "BytesPerReq"; private static final String BYTES_PER_SEC = "BytesPerSec"; private static final String CPU_LOAD = "CPULoad"; private static final String IDLE_WORKERS = "IdleWorkers"; private static final String REQ_PER_SEC = "ReqPerSec"; private static final String SCOREBOARD = "Scoreboard"; private static final String TOTAL_ACCESSES = "Total Accesses"; private static final String TOTAL_KBYTES = "Total kBytes"; private static final String UPTIME = "Uptime"; private static final int MAX_FAILURES = 3; // state constants for the Apache Server (brought over from ApacheMBean interface public String DOWN = "DOWN"; public String NOT_RESPONDING = "NOT_RESPONDING"; public String RUNNING = "RUNNING"; public String UNKNOWN = "UNKNOWN"; private int failures = 0; private String state = UNKNOWN; private long tzero; private URL url; // constructor that accepts the URL for the Apache server to be managed public ApacheServer(String url) throws MalformedURLException, IllegalArgumentException { setServer(url); // test the ability to communicate using the URL try { getValue(UPTIME); } catch (Exception e) { System.out.println( "Apache server at " + url + " does not respond."); } } // Return the URL of the Apache server being managed // Server is a readable and writable attribute because // it has a getter and setter public String getServer() { return this. url.toString(); } // Set the URL of the Apache server being managed public void setServer(String url) throws MalformedURLException, IllegalArgumentException { this. url = new URL(url); // test to be sure the URL is really an Apache server URL if (isStatusUrl( this. url) == false ) { throw new IllegalArgumentException(url.toString()); } this. state = ApacheMBean.UNKNOWN; } // return the current state of the Apache server public String getState() { return this.state; } // Example operation for start // If this were running in the same JVM, this is // where the apache process initialzation would go public String start() { this. state = RUNNING; return this.state; } // Sample operation for stop // If this were running in the same JVM, this is // where the Apache process initialization would go public String stop() { this. state = DOWN; return this.state; } // Validate that the URL is an Apache server status URL private boolean isStatusUrl(URL url) { return url.toString().endsWith("server-status?auto"); } // Get methods for metrics retrieved via the Apache URL // These are read-only attributes because no "setters" are // defined for them public int getBusyWorkers() throws ApacheMBeanException { return getIntValue(BUSY_WORKERS); } public int getBytesPerSec() throws ApacheMBeanException { return getIntValue(BYTES_PER_SEC); } public float getBytesPerReq() throws ApacheMBeanException { return getFloatValue(BYTES_PER_REQ); } public float getCpuLoad() throws ApacheMBeanException { return getFloatValue(CPU_LOAD); } public int getIdleWorkers() throws ApacheMBeanException { return getIntValue(IDLE_WORKERS); } public float getReqPerSec() throws ApacheMBeanException { return getFloatValue(REQ_PER_SEC); } public String getScoreboard() throws ApacheMBeanException { return getStringValue(SCOREBOARD); } public int getTotalAccesses() throws ApacheMBeanException { return getIntValue(TOTAL_ACCESSES); } public long getTotalKBytes() throws ApacheMBeanException { return getLongValue(TOTAL_KBYTES); } public long getUptime() throws ApacheMBeanException { return getLongValue(UPTIME); } // These methods are used as internal utilities by the getter methods // There is one for each type returning the base Java type private String getValue(String value) throws ApacheMBeanException, NoSuchFieldException, IOException { String result; URLConnection k = establishConnection(); result = (String) readStatus(k, value); if (result == null ) { throw new NoSuchFieldException(value); } return result; } private float getFloatValue(String value) throws ApacheMBeanException { float result; try { result = Float.parseFloat((String) getValue(value)); } catch (IOException x) { throw new ApacheMBeanException(x); } catch (NoSuchFieldException x) { throw new ApacheMBeanException(x); } return result; } private int getIntValue(String value) throws ApacheMBeanException { int result; try { result = Integer.parseInt((String) getValue(value)); } catch (IOException x) { throw new ApacheMBeanException(x); } catch (NoSuchFieldException x) { throw new ApacheMBeanException(x); } return result; } private long getLongValue(String value) throws ApacheMBeanException { long result; try { result = Long.parseLong((String) getValue(value)); } catch (IOException x) { throw new ApacheMBeanException(x); } catch (NoSuchFieldException x) { throw new ApacheMBeanException(x); } return result; } private String getStringValue(String value) throws ApacheMBeanException { String result; try { result = (String) getValue(value); } catch (IOException x) { throw new ApacheMBeanException(x); } catch (NoSuchFieldException x) { throw new ApacheMBeanException(x); } return result; } // Establishes the connection to the URL // If the connection fails, then the state // is changed to DOWN or NOT_RESPONDING private URLConnection establishConnection() throws IOException, ApacheMBeanException { URLConnection k = this. url.openConnection(); try { k.connect(); this. failures = 0; this. state = ApacheMBean.RUNNING; } catch (IOException x) { if (++ this. failures > MAX_FAILURES) { this. state = DOWN; } else { this. state = NOT_RESPONDING; } throw new ApacheMBeanException("state: " + this. state); } return k; } // Parses the data returned from the URL private String readStatus(URLConnection k, String value) throws IOException { BufferedReader r = new BufferedReader( new InputStreamReader(k.getInputStream())); for (String l = r.readLine(); l != null; l = r.readLine()) { StringTokenizer st = new StringTokenizer(l, ":"); if (st.nextToken().trim().equals(value)) { // if it's the right value return (String) (st.nextToken().trim()); // get wanted value } else { st.nextToken(); // past unwanted value } } return ""; } } Now, let's look at the capabilities of model MBeans in detail. |