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 []"); System.exit(1); } // get host that the JMX Agent is running on... String host = null; try { host = args[0]; InetAddress.getByName(host); } catch (java.net.UnknownHostException e) { System.err.println( "Failed to find host: " + host); System.exit(1); } // get the rmi port that the JMX Agent is listening on... int port = 0; try { port = Integer.parseInt(args[1]); if (port < 0 || port > 65535) { throw new NumberFormatException("Port out of range: " + port); } } catch (NumberFormatException e) { System.err.println( "Port must be an integer between zero and 65535."); System.exit(1); } // check for optional gemfire.properties... String propsFileName = null; if (args.length > 2) { propsFileName = args[2]; File propsFile = new File(propsFileName); if (!propsFile.exists()) { System.err.println( "Supplied GemFire properties file does not exist"); System.exit(1); } } // initialize the monitor and start it... HealthMonitor monitor = new HealthMonitor(); monitor.initialize(host, port, propsFileName); monitor.go(); System.exit(0); } }