Broadway and the Java Management Architecture
The Broaday API offers deep integration with the Java Management eXtention (JMX) Technologies. The API makes available several Java management components (MBeans) specifically designed for management context. These components can be used to monitor other JMX management components and take action based on specified monitoring expression (see below). The API lets you monitor JMX management components whether they are local or distributed to other remote machines using JMX Connector architecture.
Broadway JMX Components
Brodway provides two JMX components perform monitor and execute actions based on monitored states of other registered components.
JmxMonitor
This MBean component encapsulates all necessary methods to monitor MBeans registered in an MBeanServer. It provides a management interface that facilitates the registration of monitored MBeans, setup monitoring expression, and provides control methods to start and stop the component. The JmxMonitor component expects a standard JMX Timer Service MBean to be available for heartbeat services. With each heartbeat, the JmxComponent evaluates the monitor expression for specific conditions. When that condition occurs, it fires a JMX NotificationEvent.
JmxScriptedAction
This MBean lets you setup a script file to be executed as result of a monitor event. The JmxScriptedAction component listens for NotificationEvent from on the MBeanServer bus. JmxScriptedAction is a passive component in that it only executes the specified action script when it receives the event notification from the MBeanServer event bus.
Monitoring MBeans with Broadway
This portion of the tutorial shows how to use the Broadway JMX api to monitor management beans that are registered in a specified MBeanServer. The API supports both in-vm and distributed out-of-vm monitoring using the JMX Connector Service API. The following example shows how to use the Broadway API to create a directory synchronizer using distributed JMX nodes. One one running in a VM captures the size of the directories. A separate VM runs the monitoring code that synchronizes the directories when they go out of sync. The image below illustrates the components that make up the directory synchronizer.
The Agent Node
The JMX agent node hosts management beans that reflect the size of two separate directories. This tutorial uses a programmatic approach to construct the agent node. However, this can also be done with technologies such as the Spring Framework or any other framework that supports exporting management beans to an MBeanServer.
public class DirSyncAgent {
private MBeanServer mbs;
private static String DIR_ONE = "org.broadway:observedDirectory=dir1";
private static String DIR_TWO = "org.broadway:observedDirectory=dir2";
...
public void bootAgent() {
// setup observed directory MBeans
org.broadway.demo.jmx.ObservedDirectory dir1 = new org.broadway.demo.jmx.ObservedDirectory();
dir1.setDirectoryName("./dir1/");
JmxUtil.registerMBean(mbs, dir1, JmxUtil.createObjectName(DIR_ONE));
org.broadway.demo.jmx.ObservedDirectory dir2 = new org.broadway.demo.jmx.ObservedDirectory();
dir2.setDirectoryName("./dir2/");
JmxUtil.registerMBean(mbs, dir2, JmxUtil.createObjectName(DIR_TWO));
}
...
}
In this snippet of code, we are creating instances of the management beans that represent the observed directories and register them with the MBeanServer using the object name org.broadway:observedDirectory=dir1 and org.broadway:observedDirectory=dir2 respectively. Next, we explicitly export our MBeanServer as RMI stub using the Service Connector architecture so that it is made available for remote monitoring.
...
public void startServer(){
...
LocateRegistry.createRegistry(port);
mbs = ManagementFactory.getPlatformMBeanServer();
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:"+ port + "/jmxrmi");
JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
cs.start();
...
}
...
Once the ConnectorServer has been started, the MBeanServer stub can be accessed remotely for monitoring. It should be noted that RMI is among several other protocol available for remote monitoring.
The Monitor Node
The monitor node, as the name implies, runs the Broadway monitor code that evaluates the management bean states (in the agent node) at a given time interval. When the monitored condition occurs (in this case the two directories are of different sizes), the monitor sends a JMX event notification that is picked up by the JmxScriptedAction component. This component executes the specified script that synchronizes the two directories.
public class DirSyncMonitor {
private Timer jmxTimer;
private JmxMonitor monitor;
private JmxScriptedAction action;
private MBeanServer localMbs;
private MBeanServerConnection agentMbs;
private static String BROADWAY_TIMER = "org.broadway:service=timer";
private static String BROADWAY_MONITOR = "org.broadway:service=broadwayMonitor";
private static String BROADWAY_ACTION = "org.broadway:service=broadwayAction";
private static String DIR_ONE = "org.broadway:observedDirectory=dir1";
private static String DIR_TWO = "org.broadway:observedDirectory=dir2";
private static String AGENT_ADDRESS = "service:jmx:rmi:///jndi/rmi://localhost:8888/jmxrmi";
...
private void bootMonitor() {
localMbs = JmxUtil.getMBeanServer();
agentMbs = JmxUtil.getMBeanServer(AGENT_ADDRESS); // connect to remote agent MBeanServer
// create components
jmxTimer = new Timer();
monitor = new JmxMonitor(localMbs, new BeanMonitor());
action = new JmxScriptedAction(localMbs, new ScriptedAction("scripts/synchronizeDir.groovy"));
// register timer with local MBeanServer
jmxTimer.addNotification(JmxUtil.JmxConstants.BROADWAY_TIMER_SIGNAL.toString(), null, null, new Date(), 3000L);
JmxUtil.registerMBean(localMbs, jmxTimer, JmxUtil.createObjectName(BROADWAY_TIMER));
jmxTimer.start();
// register monitor with local MBeanServer
JmxUtil.registerMBean(localMbs, monitor, JmxUtil.createObjectName(BROADWAY_MONITOR));
// register Action component with localMBeanServer
JmxUtil.registerMBean(localMbs, action, JmxUtil.createObjectName(BROADWAY_ACTION));
// setup monitor
monitor.setTimerServiceName(BROADWAY_TIMER); // listens to timer for heartbeat
monitor.addObservedMBean("firstDir", DIR_ONE, agentMbs); // register mbeans to monitor
monitor.addObservedMBean("secondDir", DIR_TWO, agentMbs);
monitor.setSendActionNotificationWhen(true); // sends notification when expression evaluates to true
monitor.setMonitorExpression(
"context.resources.firstDir.directorySize != context.resources.secondDir.directorySize;");
monitor.start();
// attach action to monitor
action.addJmxMonitor(BROADWAY_MONITOR);
action.start();
}
The code does two things. Firstly, it connects to the remote JMX agent node using a MBeanServerConnection instance. This provides access to bean values that are registered with the agent MBeanServer so that they can be monitored. Secondly, it creates a local MBeanServer where the monitor component and the action components are registered and operate within the same local management context. Once a JMX Timer component is fully-realized, it is used to drive the monitor object using a 3 second interval.
The monitor component is at the heart of this and therefore contains more information settings. It first registers for Timer service using the monitor.setTimerServiceName() to receive timing events. Secondly, it registers the two MBeans that are observed using the monitor.addObservedMBean() method. The observed beans are specified using an alias ("firstDir"), the JMX ObjectName, and the server where that object is registered. Broadway lets you create compound monitoring scenarios from objects that are in different nodes. Lastly, the monitor object establishes the monitoring expression to be evaluated (monitor.setMonitorExpress()). In this case, it is comparing the size of the two directory object.
The action component registers itself with the monitor component (action.addJmxMonitor()). When the monitor's expression evaluates to the expected state, it sends a JMX event notification that is picked up by the action component. The JmxScriptedAction component executes the specified script file. In this case, the script synchronizes the two directories by copying content from one to the other. Broadway supports any scripting engine supported by Apache BSF. The example shown here is using Groovy, however, you can use any other supported scripting language.
Script
def dirPath1 = context.resources.firstDir.directoryName;
def dirPath2 = context.resources.secondDir.directoryName;
print "***** Synchronizing Directories [Mirror $dirPath2 to $dirPath1] *****\n";
def File dir1 = new File(dirPath1);
dir1.eachFile {origFile ->
def origFileName = origFile.name;
def destFile = new File(dirPath2+ File.separator + origFileName);
if(!destFile.exists() || (destFile.lastModified() < origFile.lastModified())){
print "Synching file File $origFile.name ...\n";
def fis = origFile.newInputStream();
new FileOutputStream(destFile).withStream {fos ->
fos << fis;
}
}
}
Conclusion
This tutorial shows how to setup Broadway to easily integrate with JMX. Broadway exposes two management components, mainly, JmxMonitor and JmxScriptedAction. These components can be hosted within the context of an MBeanServer and setup to monitor and react to changes in states of other management beans.