package healthMonitor;
//import com.gemstone.gemfire.*;
import com.gemstone.gemfire.admin.*;
import com.gemstone.gemfire.admin.jmx.Agent;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.management.*;
import javax.management.remote.*;
import javax.management.monitor.*;
//import javax.naming.*;
/**
* This class is a JMX client application that connects to the GemFire JMX Agent
* via the RMIConnector defined in JSR 160 to monitor the health of a GemFire
* distributed system.
*
* @author GemStone Systems, Inc.
* @since 3.5
*/
public class HealthMonitor implements NotificationListener {
/** Remote connection to the JMX Server */
private JMXConnector jmxConnection;
/** The MBeanServer for the GemFire JMX Agent */
private MBeanServerConnection mbs;
/** ObjectName for the GemFire JMX Agent MBean */
private ObjectName agentName;
/** ObjectName for the DistributedSystem MBean */
private ObjectName systemName;
/** ObjectName for the GemFireHealth MBean */
private ObjectName healthName;
/** ObjectName for the DistributedSystemHealthConfig MBean */
private ObjectName systemHealthConfigName;
/** ObjectName for default GemFireHealthConfig MBean */
private ObjectName defaultHealthConfigName;
/** ObjectName for health StringMonitor MBean */
private ObjectName healthMonitorName;
/**
* Map of host names to GemFireHealthConfig MBean ObjectNames
*
* key='String host'.....value='ObjectName gemfireHealthConfigName'
*/
private final Map healthConfigHostMap = new HashMap();
/** Is true indicates that application is running */
private volatile boolean running = true;
/** Object for main thread to wait on */
private final Object go = new Object();
/**
* Adds a shutdown hook and loops until user hits CTRL-C. JMX Notifications
* will be coming in on a separate thread.
*/
public void go() {
Runtime.getRuntime().addShutdownHook(
new Thread() {
public void run() {
disconnect();
}
});
try {
synchronized (go) {
while (this.running) {
go.wait();
}
}
}
catch (InterruptedException e) {
// ignore and exit
}
}
/**
* Stops the example application and cleans up the connection to the
* GemFire JMX Agent.
*/
public void disconnect() {
synchronized (go) {
this.running = false;
go.notify();
}
// close JMX connection after removing all health MBeans and listeners...
try {
this.mbs.removeNotificationListener(this.systemName, this);
}
catch (Exception e) {
e.printStackTrace();
}
unregisterHealthMBeans();
try {
this.jmxConnection.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
/**
* Assumes we registered all health related MBeans and unregisters them all.
*/
private void unregisterHealthMBeans() {
// unregister the StringMonitor for the health attribute...
unregisterMBean(healthMonitorName);
// unregister all MBeans with names in our healthConfigHostMap...
for (Iterator iter = this.healthConfigHostMap.keySet().iterator();
iter.hasNext();) {
String host = (String) iter.next();
ObjectName gemfireHealthConfigName = (ObjectName)
this.healthConfigHostMap.get(host);
unregisterMBean(gemfireHealthConfigName);
}
// unregister the main health MBeans...
unregisterMBean(healthName);
unregisterMBean(systemHealthConfigName);
unregisterMBean(defaultHealthConfigName);
}
/**
* Unregisters the named MBean without throwing any exceptions.
*/
private void unregisterMBean(ObjectName objectName) {
try {
this.mbs.unregisterMBean(objectName);
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Connects to the GemFire JMX Agent and configures the settings for this
* example application.
*/
public void initialize(String host, int port, String propsFileName) throws Exception {
// connect to GemFire JMX Agent via JSR 160 defined RMIConnector...
// The JMXConnectorServer protocol, in this case is RMI.
String serverProtocol = "rmi";
// The RMI server's host: this is actually ignored by JSR 160
// since this information is stored in the RMI stub.
String serverHost = host; //"host";
// The host, port and path where the rmiregistry runs.
String namingHost = host; //"localhost";
int namingPort = port; //1099;
String jndiPath = Agent.JNDI_NAME;
// The address of the connector server
String urlString = "service:jmx:" + serverProtocol + "://" +
serverHost + "/jndi/rmi://" + namingHost + ":" +
namingPort + jndiPath;
JMXServiceURL url = new JMXServiceURL(urlString);
// Connect a JSR 160 JMXConnector to the server side
this.jmxConnection = JMXConnectorFactory.connect(url);
this.mbs = this.jmxConnection.getMBeanServerConnection();
this.agentName = new ObjectName("GemFire:type=Agent");
// if agent is not already connected, we'll configure it and connect...
if (!isAgentConnected()) {
// configure agent for distributed system...
configureAgent(propsFileName);
this.systemName = (ObjectName)
this.mbs.invoke(this.agentName, "connectToSystem",
new Object[0], new String[0]);
}
else {
this.systemName = (ObjectName)
this.mbs.invoke(this.agentName, "manageDistributedSystem",
new Object[0], new String[0]);
}
/* register for system JMX notifications...
* NOTE: any class that implements NotificationListener can register
*/
this.mbs.addNotificationListener(this.systemName, this, null, new Object());
// setup the health monitoring MBeans...
setupHealthMonitors();
}
/**
* Registers and configures health MBeans for the system.
*/
private void setupHealthMonitors() throws Exception {
// register the GemFireHealth MBean for the system...
this.healthName = (ObjectName)
this.mbs.invoke(this.systemName, "monitorGemFireHealth",
new Object[0], new String[0]);
String systemId = (String) this.mbs.getAttribute(this.systemName, "id");
// get the names of the default health config MBeans...
this.systemHealthConfigName = new ObjectName(
"GemFire:type=DistributedSystemHealthConfig,id=" + systemId);
this.defaultHealthConfigName = new ObjectName(
"GemFire:type=GemFireHealthConfig,id=" + systemId + ",host=default");
if (true) {
Integer seconds = (Integer) this.mbs.getAttribute(
this.defaultHealthConfigName, "healthEvaluationInterval");
// set up StringMonitor for the health attribute on GemFireHealth MBean...
this.healthMonitorName = new ObjectName(
"monitors:type=String,attr=health,system=" + systemId);
this.mbs.createMBean("javax.management.monitor.StringMonitor",
this.healthMonitorName);
AttributeList al = new AttributeList();
al.add(new Attribute("ObservedObject", this.healthName));
al.add(new Attribute("ObservedAttribute", "healthStatus"));
al.add(new Attribute("GranularityPeriod",
new Integer(seconds.intValue() * 1000))); // in millis
al.add(new Attribute("StringToCompare",
GemFireHealth.GOOD_HEALTH.toString()));
al.add(new Attribute("NotifyMatch", new Boolean(true)));
al.add(new Attribute("NotifyDiffer", new Boolean(true)));
this.mbs.setAttributes(this.healthMonitorName, al);
this.mbs.addNotificationListener(this.healthMonitorName, this, null, this.mbs);
this.mbs.invoke(this.healthMonitorName, "start", new Object[0], new String[0]);
}
// next register health config MBeans for each host in the system...
setupHealthForAllHosts();
}
/**
* Registers health config MBean for all hosts that we can find
* member applications running on.
*/
private void setupHealthForAllHosts() throws Exception {
// make sure health config MBeans are registered for all application hosts...
ObjectName[] memberNames = (ObjectName[])
this.mbs.invoke(this.systemName, "manageSystemMemberApplications",
new Object[0], new String[0]);
for (int i = 0; i < memberNames.length; i++) {
checkHostFor(memberNames[i]);
}
}
/**
* Verifies that a GemFireHealthConfig MBean is registered for
* the host that the named member resides on. If it doesn't already exist,
* a GemFireHealthConfig will be created for that host.
*/
private void checkHostFor(ObjectName memberName) throws Exception {
if (memberName == null) return;
synchronized(this.healthConfigHostMap) {
// get the member's host...
String host = (String) this.mbs.getAttribute(memberName, "host");
// is there a config MBean registered for the host yet?
ObjectName gemfireHealthConfigName = (ObjectName)
this.healthConfigHostMap.get(host);
if (gemfireHealthConfigName == null ||
!this.mbs.isRegistered(memberName)) {
// if not registered, then create a new config MBean for the host...
gemfireHealthConfigName = (ObjectName)
this.mbs.invoke(this.healthName, "manageGemFireHealthConfig",
new Object[] { host },
new String[] { "java.lang.String" });
this.healthConfigHostMap.put(host, gemfireHealthConfigName);
}
}
}
/**
* Returns the ObjectName for SystemMember
* MBean that represents the memberId. Returns
* null if no matching MBean is found or if any exception occurs.
*/
private ObjectName findSystemMember(String memberId) {
try {
// is there an SystemMember MBean of type APPLICATION registered...
ObjectName memberName = new ObjectName(
"GemFire.Member:id=" + makeCompliantMBeanNameProperty(memberId) +
",type=" + SystemMemberType.APPLICATION);
if (this.mbs.isRegistered(memberName)) {
return memberName;
}
}
catch (Exception e) {
e.printStackTrace();
}
// nothing found... return null
return null;
}
/**
* Returns true if Agent MBean is connected to a distributed system.
*/
private boolean isAgentConnected() throws Exception {
Object obj = this.mbs.getAttribute(this.agentName, "connected");
return ((Boolean) obj).booleanValue();
}
/**
* Configures Agent MBean to connect to the distributed system described
* in the specified gemfire.properties file. The Agent MBean will then be
* connected to the system.
*/
private void configureAgent(String propsFileName) throws Exception {
// if propsFileName was specified, use it for gemfire.properties...
if (propsFileName != null) {
System.setProperty("gemfirePropertyFile", propsFileName);
}
// define DistributedSystemConfig which will pick up gemfire properties...
DistributedSystemConfig config =
AdminDistributedSystemFactory.defineDistributedSystem();
System.out.println("using mcast-port = " + config.getMcastPort());
// configure agent to connect to the system defined by "config"...
AttributeList al = new AttributeList();
al.add(new Attribute("mcastAddress", config.getMcastAddress()));
al.add(new Attribute("mcastPort", new Integer(config.getMcastPort())));
al.add(new Attribute("locators", config.getLocators()));
this.mbs.setAttributes(this.agentName, al);
}
/**
* Receives JMX Notifications by handling gemfire membership notifications,
* gemfire alert notifications, and notifications that the gemfire health
* attribute has changed. This example simply prints a line to System.out
* and makes sure that health MBeans are registered for any host that has
* GemFire system member(s).
*/
public void handleNotification(Notification notification,
Object handback) {
String type = notification.getType();
System.out.println("handleNotification: " + type);
if ("gemfire.distributedsystem.member.joined".equals(type)) {
String memberId = notification.getMessage();
System.out.println("MEMBER JOINED: " + memberId);
try {
checkHostFor(findSystemMember(memberId));
}
catch (Exception e) {
e.printStackTrace();
}
}
else if ("gemfire.distributedsystem.member.left".equals(type)) {
String memberId = notification.getMessage();
System.out.println("MEMBER LEFT: " + memberId);
}
else if ("gemfire.distributedsystem.member.crashed".equals(type)) {
String memberId = notification.getMessage();
System.out.println("MEMBER CRASHED: " + memberId);
}
else if ("gemfire.distributedsystem.alert".equals(type)) {
String alert = notification.getMessage();
System.out.println("ALERT: " + alert);
}
else if (MonitorNotification.STRING_TO_COMPARE_VALUE_DIFFERED.equals(type)) {
String health = notification.getMessage();
System.out.println("HEALTH UPDATED: " + health);
}
}
/**
* Replaces illegal characters in string with '-' to ensure that the string
* can be used for an MBean's ObjectName property value.
*/
public static String makeCompliantMBeanNameProperty(String value) {
value = value.replace(':', '-');
value = value.replace(',', '-');
value = value.replace('=', '-');
value = value.replace('*', '-');
value = value.replace('?', '-');
if (value.length() < 1) {
value = "nothing";
}
return value;
}
/**
* Parses the command line and runs the HealthMonitor
* example.
*/
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.err.println(
"Usage: java HealthMonitor host port [