1.Purpose of Monitoring service
JMX Monitoring service provide a mechanism of monitoring your observable object with a predefined observable attribute. In the simplest case, you might want to get an email to be informed about the state of application especally when it crashes, then you can take some corrective actions. Or you might also want to be informed when some critical files change.
2.JMX Monitoring Service architecture
Let's get a overview of JMX Monitoring Service architecture first:
As the picture shows above, we can see that JMX Monitoring Service provide three types of monitor:StringMonitor,GaugeMonitor and CounterMonitor. And they are all standard MBean. Later I will cover them in detail one by one with the following aspects:
- What is it for?
- How to use monitor of this type?
- Its Notification type.
Before we examine them one by one, I will walk you through its parent class Monitor and their common interface MonitorMBean first.
Now let's take a look at the class diagram of MonitorMBean and see what we can get:
- start()&stop() - to start/stop the monitor.
- addObservedObject(ObjectName),removeObservedObject(ObjectName),containsObservedObject(ObjectName) and getObservedObjects() - these four methods provide a mechanism of handling the observed object.
- setObservedAttribute(String)&getObservedAttribute() - tell the monitor the attribute to observe.
- setGranularityPeriod(long)&getGranularityPeriod() - sets or return the observation intervals of the monitor.
In brief, MonitorMBean acts like a small container, it hold observed objects, set the observed attribute and determins how long it will check the observed attribute. MonitorMBean show us a big picture of monitor service in JMX.
And about Monitor, since it implements MonitorMBean and extends NotificationBroadcasterSupport, therefore, it is not only a monitor but also a Notification broadcaster. And right here what I want to show you is that how monitor works, that is, how it detect deserved attribute changes and how it send notification out when deserved attribute changes. Before we start our trip, I assume you have the basic knowledge of the new java concurrency package.
From the picture above, we can see that monitor uses the following four classes to implement its monitor service.
- ScheduledExecutorService indicates a daemon thread here while ExecutorService indicates a thread pool.
- ScheduledExecutorService use ScheduleTask as its runnable command while ExecutorService take MonitorTask as its runnable command.
Actually Monitor starts a deamon thread to monitor its observed objects.
#Monitor.class
private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory("Scheduler"));
private static class DaemonThreadFactory implements ThreadFactory {
......
public Thread newThread(Runnable r) {
Thread t = new Thread(group,
r,
namePrefix +
threadNumber.getAndIncrement() +
nameSuffix,
0);
t.setDaemon(true);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
As we can see from DaemonFactory, it newes a daemon thread with the NORM_PRIORITY. Every Monitor has its own daemon thread. And the parameter Runnable r of this newThread(Runnable) method is actually a SchedulerTask, when we walk deeply into the code, we find that SchedulerTask is only a wrapper of MonitorTask, MonitorTask is the task which really starts the monitoring logic. When we start the monitor, it will start the daemon thread within its doStart() method:
#Monitor.class
private final MonitorTask monitorTask = new MonitorTask();
private final SchedulerTask schedulerTask = new SchedulerTask(monitorTask);
void doStart() {
......
// Start the scheduler.
schedulerFuture = scheduler.schedule(schedulerTask,
getGranularityPeriod(),
TimeUnit.MILLISECONDS);
}
}
From the code above, we know that schedulerTask will be executed only once after granularityPeriod milliseconds' delay(scheduler is a single-threaded executor that can schedule commands to run after a given delay). Some people might be wonderring how this single-threaded executor run commands periodically, the answer is in the MonitorTask.
public void run() {
...
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
if (Monitor.this.isActive()) {
final int an[] = alreadyNotifieds;
int index = 0;
for (ObservedObject o : Monitor.this.observedObjects) {
if (Monitor.this.isActive()) {
Monitor.this.monitor(o, index++, an);
}
}
}
return null;
}
}, Monitor.this.acc);
synchronized (Monitor.this) {
if (Monitor.this.isActive() &&
Monitor.this.schedulerFuture == sf) {
Monitor.this.monitorFuture = null;
Monitor.this.schedulerFuture =
scheduler.schedule(Monitor.this.schedulerTask,
Monitor.this.getGranularityPeriod(),
TimeUnit.MILLISECONDS);
}
}
}
In MonitorTask, it first invoke Monitor#monitor() method to detect observed attribute changes and send notification, then it will invoke the ScheduledExecutorService#schedule(Runnable,long,TimeUnit) again to reach the goal of running schedulerTask periodically. But I am wondering why ScheduledExecutorService#schedule(Runnable,long,TimeUnit) is being used here to reach the goal of running schedulerTask periodically? Can we just use ScheduledExecutorService#scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) instead in Monitor#doStart() method?Not like the ScheduledExecutorService#schedule method, ScheduledExecutorService#scheduleAtFixedRate can schedule commands to run periodically.
#Monitor.class
private final MonitorTask monitorTask = new MonitorTask();
private final SchedulerTask schedulerTask = new SchedulerTask(monitorTask);
void doStart() {
......
// Start the scheduler.
// schedulerFuture = scheduler.schedule(schedulerTask,
// getGranularityPeriod(),
// TimeUnit.MILLISECONDS);
//use this one instead of the above one
schedulerFuture = scheduler.scheduleAtFixedRate
(schedulerTask, initialDelay time,
getGranularityPeriod(),
TimeUnit.MILLISECONDS);
}
}
The only one reason I can see here is about granularityPeriod variable defined in Monitor class, since granularityPeriod can be changed on the runtime,if we use ScheduledExecutorService#scheduleAt-FixedRate instead, the change of granularity doesn't make sence any more. And once the period has been setted for ScheduledExecutorService#scheduleAtFixedRate,it can't be changed any longer.
The following sequence diagram could give you a better understand of what I said above:
As we saw above, we know when MonitorTask executes, it invokes Monitor#monitor() to observe the observed attribute. Let's take a look at what happens in the monitor() method.
From the picture above, first, monitor will get the observed attribute value of the observed object, then build notification according to different monitor type. Take string monitor for example, it will compare the observed attribute value with the value setted by setStringToCompare() mothod, finally, send the notifications if any. Actually buildAlarmNotification() is a template method implemented by subclasses of monitor.
And now, I am sure you must have known how monitor works. So, let's check the JMX monitors one by one now.
StringMonitor:
- Purpose
As its name suggests, StringMonitor is used to monitor a attribute of a string type in your observable object.
- Using String Monitor
Before using String Monitor, we need to configure three values since String Monitor's behavior is based on them, and these three values can be setted by the following methods:
Method Name | Description |
setStringToCompare(String) | set the internal string that will be used to compare with the attribute value of observed object. |
setNotifyMatch(boolean) | tells monitor to send notification if the attribute value you apply to the observed object matches the internal string setted by setStringToCompare(string) |
setNotifyDiffer(boolean) | tells monitor to send notification if the attribute value you apply to the observed object does not match the internal string setted by setStringToCompare(string) |
- String Monitor Notification Type
StringMonitor supports two Notification types: Match type and Differ type.
Notification Type | Description |
jmx.monitor.string.matches | that the attribute value you apply to the observable object is the matching value. |
jmx.monitor.string.differs | indicates that the attribute value you apply to the observable object is different from the matching value |
Note: Monitor will send notification only when the observed attribute transitions from a match to a non-match or vice versa. It wont continuously send notification if the value always matches or always differs.
GaugeMonitor:
- Purpose
Gauge Monitor is used to monitor a range of values because it is observing an attribute that possibly cross the predefined low threshold or the predefined high threshold.
- Using Gauge Monitor
Method Name | Description |
setThresholds(Number highValue, Number lowValue) | Sets the high and the low threshold values of the gauge monitor |
setNotifyHigh(boolean) | tells monitor to send notification if the observed attribute crosses the high threshold of monitor |
setNotifyLow(boolean) | tells monitor to send notification if the observed attribute goes below the high threshold of monitor |
setDifferenceMode(boolean) | if true, uses the substraction to calculate its derived gauge |
- Gauge Monitor Notification Type
In addition to the common notification type, there are still two notification types for gauge monitor.
Notification Type | Description |
jmx.monitor.gauge.high | tells monitor to send High type notification once the observed attribute crosses the high threshold of monitor |
jmx.monitor.gauge.low | tells monitor to send Low type notification once the observed attribute goes below the high threshold of monitor |
Counter Monitor:
- Purpose
Since it is counter monitor, the observed attribute is assumed to be positive and to have an increasing value. And counter monitor is used to check if the observed attribute reaches the maximum value, if it reaches, counter monitor will send out a notification to notify this event.
- Using Counter Monitor
The following table lists the methods of counter monitor, which should be used to describe its behavior.
Method | Description |
setInitThreshold(Number) | set the initial threshold for this monitor,which is used to compare with the observed attribute value. |
setOffset(Number) | set the offset value of the monitor |
setModulus(Number) | set the maximum value for this monitor |
setNotify(boolean) | if true, tells monitor to send out notifications when the observed attribute value reaches its counter |
setDifferenceMode(boolean) | Same as gauge monitor, if ture,it will recalcute its derived gauge |
As we can see from the above table, the initThreshold is used to compare with the observed attribute value, but what are the offset and modulus for? Offset is added to the initThreshold value when the observed attribute value exceeds the initThreshod and counter monitor will keep adding its offset value to the threshold until it is greater than the observed attribute. If the modulus has been setted, and if adding the offset exceeds the modulus,the initThreshold will reset to its original value.
- Counter Monitor Notification Type
Like the gauge monitor, counter monitor not only shares the common notification type,'jmx.monitor.error.threshold', but also adds a new notification type,'jmx.monitor.counter.threshold'
Notification Type | Description |
jmx.monitor.error.threshod | indicate the monitor's threshold,offset or modulus is not the same type as the observed counter attribute value |
jmx.monitor.counter.threshold | indicate the observed attribute value has been reached the monitor's specified threshold. |
3.Monitor Example
We have discussed each Monitor MBean's behavior and important methods. Now it is time to write some code. First, we need an observed object.
public interface ObservableObjectMBean {
//for string monitor
public void setName(String name);
public String getName();
//for gauge monitor
public void setRange(Float range);
public Float getRange();
//for counter monitor
public void setCounter(int counter);
public int getCounter();
}
public class ObservableObject implements ObservableObjectMBean {
private String name;
private Float range;
private int counter;
public Float getRange() {
return range;
}
public void setRange(Float range) {
this.range = range;
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
}
And then our Monitor Agent.
public class MonitorAgent implements NotificationListener{
public MonitorAgent() throws Exception
{
MBeanServer server = MBeanServerFactory.createMBeanServer();
// new a adaptor and register it in the server
HtmlAdaptorServer adaptor = new HtmlAdaptorServer();
adaptor.setPort(8888);
ObjectName adaptorName = new ObjectName("Monitor:name=adaptor");
server.registerMBean(adaptor, adaptorName);
//new a observable object
ObjectName observableObjName=new ObjectName("Monitor:name=observable_obj");
ObservableObjectMBean observableObj=new ObservableObject();
server.registerMBean(observableObj, observableObjName);
//new a String monitor
StringMonitor stringMonitor=new StringMonitor();
ObjectName monitorObjName=new ObjectName("Monitor:name=StringMonitor");
stringMonitor.setObservedAttribute("Name");
stringMonitor.setStringToCompare("kevinWang");
stringMonitor.setNotifyDiffer(true);
stringMonitor.setNotifyMatch(true);
stringMonitor.addObservedObject(observableObjName);
stringMonitor.start();
server.registerMBean(stringMonitor,monitorObjName);
//new a gauge monitor
GaugeMonitor gaugeMonitor=new GaugeMonitor();
ObjectName gaugeObjName=new ObjectName("Monitor:name=GaugeMonitor");
gaugeMonitor.setObservedAttribute("Range");
gaugeMonitor.setDifferenceMode(false);
gaugeMonitor.setThresholds(5.5f, 2.1f);
gaugeMonitor.setNotifyHigh(true);
gaugeMonitor.setNotifyLow(true);
gaugeMonitor.addObservedObject(observableObjName);
gaugeMonitor.start();
server.registerMBean(gaugeMonitor, gaugeObjName);
//new a counter monitor
CounterMonitor counterMonitor=new CounterMonitor();
ObjectName counterObjName=new ObjectName("Monitor:name=CounterMonitor");
counterMonitor.addObservedObject(observableObjName);
counterMonitor.setObservedAttribute("Counter");
counterMonitor.setInitThreshold(3);
counterMonitor.setOffset(1);
counterMonitor.setModulus(6);
counterMonitor.setNotify(true);
counterMonitor.start();
server.registerMBean(counterMonitor, counterObjName);
//add listener
server.addNotificationListener(monitorObjName, this, null, null);
server.addNotificationListener(gaugeObjName, this, null, null);
server.addNotificationListener(counterObjName, this, null, null);
adaptor.start();
}
public static void main(String[] args) throws Exception{
MonitorAgent agent=new MonitorAgent();
}
public void handleNotification(Notification notification, Object handback) {
System.out.println(notification.getType());
}
}
After we run Monitor Agent, you can get access to http://localhost:8888 and test them.
For String Monitor, input one string that is different from 'kevinWang' for the observed attribute 'name', for Gauge Monitor, input a float number that is not btw 2.1f and 5.5f for the observed attribute 'range', and for Counter Monitor, input a int number that is over the initThreshold for the observed attribute 'counter'. The following is the snapshot of inputted parameter.
Then click the 'Apply' button, you will see the output as below:
jmx.monitor.string.differs
jmx.monitor.gauge.high
jmx.monitor.counter.threshold