JMX - Still Not Dead!

JMX

JMX has fallen out of fashion a little recently. Large corporates have started using proprietry byte code instrumentation technolgies for application monitoring (such as Dynatrace etc). To put it mildly I'm not a fan of this approach, but there you are.

Anyway we had cause recently to expose some statistics via JMX for the benefit of support teams, so I had to get back into it. Things have moved on. One of the problems I found was exposing complex data such that it appeared in Java Mission Control in a navigable form, from an OSGi Bundle. Using the usual method of simply creating an interface named SomethingMBean didn't work (for various reasons) so I had to use DynamicMBean This led me to using Open MBeans to expose a collection of statistics where each element of the collection also featured a collection.

This is done using with Open MBeans using the CompositeType and TabularType

Because JMX has fallen a little by the wayside it took me a while to discover how to do this.

Handling complex objects in MBeans


import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.Map;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
import javax.management.openmbean.OpenMBeanConstructorInfoSupport;
import javax.management.openmbean.OpenMBeanInfoSupport;
import javax.management.openmbean.OpenMBeanOperationInfoSupport;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
/**
 * Example DynamicMBean using Open MBean types
 * Run the main method then open JMC (java mission control) or JConsole etc
 * Find this bean and see how it appears
 * Be aware that the main method will run forever, so make sure you run it in a way that you can close it (in a separate console, or in eclipse etc.)
 * @author richard.senior@gmail.com
 */
public class ComplexTypeExampleMBean implements DynamicMBean {
	/** This will hold the data we're actually going to expose to JMX **/
	private Map<String,Map<String,String>>data = new HashMap<String,Map<String,String>>();
	/** these hold the names and descriptions that will be seen in Mission Control, or JConsole etc. **/
	private static String itemsTableName = "items";
	private static String itemsTableDescription = "a table of items";
	private static String[] attributeNames = {"item","subitems"};
	private static String[] attributeDescriptions = {"an item", "a collection of sub-items"};	
	private static TabularType itemsType,subItemsType;
	private static CompositeType itemRowType,subItemRowType;	
	private static String subItemsTableName = "subitems";
	private static String subItemsTableDescription = "a table of sub-items";
	private static String[] subAttributeNames = {"subitem","subitem_value"};
	private static String[] subAttributeDescriptions = {"an sub-item", "the value of this sub-item"};
	private static OpenType<?>[] subAttributeTypes = {SimpleType.STRING,SimpleType.STRING};
	//we have to do this because for some bizarre reason the constructors of these types throw checked exceptions
	static {		
		try {
			subItemRowType = new CompositeType(
					subAttributeNames[0],
					subAttributeDescriptions[0],
					subAttributeNames,
					subAttributeDescriptions,
					subAttributeTypes				
			);
			subItemsType = new TabularType (
					subItemsTableName,
					subItemsTableDescription,
					subItemRowType,
					new String[] {subAttributeNames[0]}				
			);				
			itemRowType = new CompositeType(
					attributeNames[0],
					attributeDescriptions[0],
					attributeNames,
					attributeDescriptions,
					new OpenType[] {SimpleType.STRING,subItemsType}
			);	
			itemsType = new TabularType (
					itemsTableName,
					itemsTableDescription,
					itemRowType,
					new String[] {attributeNames[0]}					
			);			
		} catch (OpenDataException e) {
			throw new RuntimeException("failed to create necessary open jmx types");
		}
	}

	private TabularData getItems() throws OpenDataException {
		TabularDataSupport items = new TabularDataSupport(itemsType);
		for (Map.Entry<String,Map<String,String>>e:data.entrySet()) {
			//Create the sub-tems Table
			TabularDataSupport subItems = new TabularDataSupport(subItemsType);
			for (Map.Entry<String,String>se:e.getValue().entrySet()) {
				CompositeData subItemrow = new CompositeDataSupport(				
						subItemRowType,
						subAttributeNames,
						new Object[] {se.getKey(),se.getValue()}
					);
				subItems.put(subItemrow);				
			}
			//now create the item itself
			CompositeData row = new CompositeDataSupport(				
					itemRowType,
					attributeNames,
					new Object[] {e.getKey(),subItems}
				);
			items.put(row);
		}								
		return items;
	}
	
	@Override
	public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
		if (attribute==null||attribute.length()==0) throw new AttributeNotFoundException("no attribute matching that name");
		if (itemsTableName.equals(attribute)) {
			try {
				return getItems();
			} catch (OpenDataException e) {
				throw new MBeanException(e);
			}
		} else {
			throw new AttributeNotFoundException("no attribute matching the name " + attribute);
		}
	}
	
	/**
	 * {@inheritDoc}
	 * This is boilerplate code, which is frustrating 
	 */
	@Override
	public AttributeList getAttributes(String[] attributes) {
		AttributeList list = new AttributeList();
		if (attributes==null||attributes.length==0) return list;		
		for (int i = 0; i < attributes.length; i++) {
		    String name = attributes[i];
		    try {
				list.add(getAttribute(name));
			} catch (Exception e) {}
		}
		return list;
	}
	
	@Override
	public MBeanInfo getMBeanInfo() {
		try {										
			OpenMBeanInfoSupport info = new OpenMBeanInfoSupport(
					 this.getClass().getName(),
					 itemsTableDescription, 
			         new OpenMBeanAttributeInfoSupport[] {new OpenMBeanAttributeInfoSupport(itemsTableName,itemsTableDescription,itemsType,true, false, false)},	
			         new OpenMBeanConstructorInfoSupport[0],
			         new OpenMBeanOperationInfoSupport[0],
			         new MBeanNotificationInfo[0]); 
			return info;
		} catch (Throwable t) {
			throw new RuntimeException("failed to get mbean info!");
		}
	}

	/**
	 * This method can be called to actually register the MBean @see #Main(String[])
	 * You can potentially call this method at the end of your constructor, but if you're using Spring etc.
	 * it's probably safer to call this method after you're sure that everything is properly instantiated
	 */
	public void registerMBean() {
		MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
		try {
			String name = this.getClass().getName() + ":name=" + this.getClass().getSimpleName(); 
			mbs.registerMBean((Object) this, new ObjectName(name));
		} catch (Exception e) {
			throw new RuntimeException("failed to register the mbean",e);
		}
	}

	//These three methods allow interaction. We're not interested in that we just want to expose some read only data. So we can ignore these methods.
	@Override
	public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {}	
	@Override
	public AttributeList setAttributes(AttributeList attributes) {return null;}
	@Override
	public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {return null;}
			
	public static void main(final String[] args) throws Exception {
		ComplexTypeExampleMBean b = new ComplexTypeExampleMBean();
		Map<String,String>subItems = new HashMap<String,String>();
		subItems.put("one","1");
		subItems.put("two","2");
		b.data.put("numbers",subItems);
		subItems = new HashMap<String,String>();
		subItems.put("black","000000");
		subItems.put("white","FFFFFF");
		b.data.put("colours",subItems);
		
		b.registerMBean();
		
		while (1==1) {
			try {Thread.sleep(10000l);} catch (Throwable t) {}
		}
	}
}

other methods

As mentioned above the easiest way to create MBeans is to implement a simple interface as described here

And if you're using Spring it can hide all this nonsense from you.

Spring uses ModelMbeans to map your POJO's to mbeans and hides all this complexity from you as described here