Friday, December 31, 2010

Developing custom MBeans for JMX monitoring

Developing custom MBeans for JMX monitoring

In many cases, it might be useful to monitor the metrics of a Software Component. Some cases, we may not find suitable tools to get the required statistics and data. Those situations demand to you write own custom implementing to collect the metrics / statistics. So wrapping the custom implementation as an MBean, is simplest possible way to get the job done. Because any standard JMX based monitoring tool can monitor an MBean running the JVM. Let us examine the required steps to develop a custom MBean to accomplish the below use case.

Use case:
A business component, A Service Manager is responsible to process all the service requests generated in the application. This component invokes the external system's service call as part of business computation. As it is a critical component in the application, we need to monitor the performance statistics of the component described below.
• Number of total requests served.
• Number of failed requests.
• Percentage of success rate.
• Average Response Time
• Maximum Response Time
• Minimum Response Time

Implementation steps:
1. Define a monitor bean interface
2. Implement the monitor bean interface
3. Register the MBean with JVM's MBean Server
4. Expose methods for business component (to feed the data in)

Define a monitor bean interface:
• This interface exposes the MBean implementation to JVM's MBean Server.
• It lists the available methods, can be invoked by the MBean Server. Create AServiceMBean.java as below.
• If any of the attributes are allowed to be set by the monitoring tool, we need to provide the setter method.
• If we want to maintain them as read only values only getter would help, no setters.

package com.sample.mbean;

public interface AServiceMBean {

// read only methods..
public int getTotalServcieCalls();
public long getAverageResponseTime();
public long getMaxResponseTime();
public long getMinResponseTime();
public int getFailedServiceCalls();
public int getSuccessServiceCalls();

// a read-write attribute
public String getName();
public String setName(String name);

}

Implement the monitor bean interface:
• It is a class, implements the the MBean interface.
• This class implements the logic to compute the required statistics on raw data (fed by business component) and makes the computed metrics available for JMX server.
• This class should extends one of the MBean class (StandarMBean, StandardEmitterMBean) to convert the class as an eligible MBean.
• You can even implement DynamicMBean, MBeanRegistration interfaces to override the default behavior (for a specific behavior) of a standard MBean. - will discuss further as we progress....

package com.sample.mbean;

import java.lang.management.ManagementFactory;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.StandardMBean;

public class AServiceMBeanImpl extends StandardMBean implements
AServiceMBean {

private static int totalCalls;
private static long totalResponseTime;
private static int totalFailedCalls;
private static long maxResponseTime;
private static long minResponseTime;
private static long avgResponseTime;

public AServiceMBeanImpl() {
super(AServiceMBean.class, true);
}

public AServiceMBeanImpl(Class mbeanInterface)
throws NotCompliantMBeanException {
super(mbeanInterface);
}

public AServiceMBeanImpl(Class mbeanInterface, boolean isMXBean) {
super(mbeanInterface, isMXBean);
}

public AServiceMBeanImpl(T implementation, Class mbeanInterface,
boolean isMXBean) {
super(implementation, mbeanInterface, isMXBean);
}

public AServiceMBeanImpl(T implementation, Class mbeanInterface)
throws NotCompliantMBeanException {
super(implementation, mbeanInterface);
}

@Override
public int getFailedServiceCalls() {
return totalFailedCalls;
}

@Override
public long getMaxResponseTime() {
return maxResponseTime;
}

@Override
public long getMinResponseTime() {
return minResponseTime;
}

@Override
public String getName() {
return "AService-Component";
}


@Override
public String setName(String name) {
//name = name;
}
@Override
public int getSuccessServiceCalls() {
return totalCalls - totalFailedCalls;
}

@Override
public int getTotalServcieCalls() {
return totalCalls;
}

@Override
public long getAverageResponseTime() {
// return totalResponseTime/(totalCalls-totalFailedCalls);
return avgResponseTime;
}

/**
* methods exposed to business component, via which they can feed the data to MBean.
*
**/
public static void addAServiceCall(boolean result, long responseTime) {
totalCalls ++;

if(result) {

if(responseTime < minResponseTime) minResponseTime = responseTime; if(responseTime > maxResponseTime) maxResponseTime = responseTime;

totalResponseTime = ++ responseTime;

} else {
totalFailedCalls++;
}
}


public static void setMaxTime(long maxResultTimeMilli) {
maxResponseTime = maxResultTimeMilli;

}

public static void setAvgTime(long averageResultTimeMilli) {
avgResponseTime = averageResultTimeMilli;

}

public static void setTotalCount(int thread_count) {
totalCalls = thread_count;

}

public static void setMinTime(long minResultTimeMilli) {
minResponseTime = minResultTimeMilli;

}
}

Register the MBean with JVM's MBean Server
Once an MBean is implemented, it needs to be registered with platform MBean, so the MBean is visible to the internal MBean Server. For simplicity you can have the registering the logic as part of the custom MBean implementation. But I would prefer to isolate the registration logic from business computation logic, into another class solely responsible to register and retrieve the custom MBeans.
• Get instance of MBeanServer using ManagementFactory.getPlatformMBeanServer()
• Create an instance of custom MBean with a name
• Register with the MBeanServer
try {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

AServiceMBeanImpl mbean = new AServiceMBeanImpl();
ObjectName name = new ObjectName(
"com.sample.mbean:type=AServiceMBean");
mbs.registerMBean(mbean, name);

} catch (MalformedObjectNameException e) {
e.printStackTrace();
System.exit(0);
} catch (InstanceAlreadyExistsException e) {
e.printStackTrace();
System.exit(0);
} catch (MBeanRegistrationException e) {
e.printStackTrace();
System.exit(0);
} catch (NotCompliantMBeanException e) {
e.printStackTrace();
}
Expose methods for business component
It is a method exposed to business component. A business component invokes this method and feeds the component's raw data in. It the custom bean applies the logic on and prepares the metrics ready
public static void addAServiceCall(boolean result, long responseTime) {
totalCalls ++;

if(result) {

if(responseTime < minResponseTime) minResponseTime = responseTime; if(responseTime > maxResponseTime) maxResponseTime = responseTime;

totalResponseTime = ++ responseTime;

} else {
totalFailedCalls++;
}
}

Executing the MBean
Once all the above steps are complete, make sure invoke the register method during application star up. It can be invoked however you want but need to make sure the MBean registered once before trying to access from the JMX monitoring tool.
As and when the business component wants feed the data, invoke the exposed method as below.* It can use any of the exposed to methods.
//Feed the MBean...
AServiceMBeanImpl. addAServiceCall(true, timeinmilli);
AServiceMBeanImpl.setMaxTime(maxResultTimeMilli);
AServiceMBeanImpl.setMinTime(minResultTimeMilli);
AServiceMBeanImpl.setAvgTime(averageResultTimeMilli);
AServiceMBeanImpl.setTotalCount(THREAD_COUNT);

Monitoring the MBean
Using the regular approach connect any JMX based monitoring tool (eg. Visual VM), navigate to MBean section, the custom AServiceMBean Service will be shown with the available data.