package de.is24.util.monitoring.jmx; import de.is24.util.monitoring.CorePlugin; import de.is24.util.monitoring.Counter; import de.is24.util.monitoring.HistorizableList; import de.is24.util.monitoring.InApplicationMonitor; import de.is24.util.monitoring.MultiValueProvider; import de.is24.util.monitoring.Reportable; import de.is24.util.monitoring.ReportableObserver; import de.is24.util.monitoring.StateValueProvider; import de.is24.util.monitoring.Timer; import de.is24.util.monitoring.Version; import de.is24.util.monitoring.state2graphite.StateValuesToGraphite; import de.is24.util.monitoring.statsd.StatsdPlugin; import de.is24.util.monitoring.visitors.HistogramLikeValueAnalysisVisitor; import de.is24.util.monitoring.visitors.StringWriterReportVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.DynamicMBean; import javax.management.InvalidAttributeValueException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.ReflectionException; import javax.management.openmbean.CompositeData; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; /** * This class publishes values registered at the Core Plugin as JMX MBeans and exposes * some management operations of InApplicationMonitor. * Simple values (Counter, Version, StateValue) are published directly via this class * as dynamic MBean, complex types are published using an own MBean for each Reportable. * */ public final class InApplicationMonitorJMXConnector implements DynamicMBean, ReportableObserver { private static final String DUMP_STRING_WRITER = "dumpStringWriter"; private static final String DUMP_HISTOGRAM_LIKE_VALUE_ANALYSIS = "dumpHistogramLikeValueAnalysis"; private static final String ACTIVATE = "activate"; private static final String DEACTIVATE = "deactivate"; private static final String IS_MONITOR_ACTIVE = "isMonitorActive"; private static final String GET_REGISTERED_PLUGIN_KEYS = "getRegisteredPluginKeys"; private static final String GET_REGISTERED_OBSERVERS = "getRegisteredReportableObservers"; private static final String REMOVE_ALL_PLUGINS = "removeAllPlugins"; private static final String ADD_STATSD_PLUGIN = "addStatsdPlugin"; private static final String ADD_STATE_VALUES_TO_GRAPHITE = "addStateValuesToGraphite"; private static final String ADD_JMX_EXPORTER_PATTERN = "addJmxExporterPattern"; private static final String LIST_JMX_EXPORTER_PATTERN = "listJmxExporterPattern"; private static final String REMOVE_JMX_EXPORTER_PATTERN = "removeJmxExporterPattern"; private static final Logger LOG = LoggerFactory.getLogger(InApplicationMonitorJMXConnector.class); private static volatile InApplicationMonitorJMXConnector instance; private static final Object semaphore = new Object(); private final Map<String, Reportable> reportables = new ConcurrentHashMap<String, Reportable>(); private final JMXBeanRegistrationHelper jmxBeanRegistrationHelper; private final CorePlugin corePlugin; public InApplicationMonitorJMXConnector(CorePlugin corePlugin, JmxAppMon4JNamingStrategy jmxAppMon4JNamingStrategy) { synchronized (semaphore) { LOG.info("initializing InApplicationMonitorJMXConnector"); if (instance != null) { LOG.error("JMXConnector allready initialized, this is not allowed"); throw new IllegalStateException("JMXConnector already initialized"); } this.corePlugin = corePlugin; this.jmxBeanRegistrationHelper = new JMXBeanRegistrationHelper(jmxAppMon4JNamingStrategy); registerJMXStuff(); // register yourself as ReportableObserver so that we're notified about every new reportable corePlugin.addReportableObserver(this); instance = this; } } public void shutdown() { synchronized (semaphore) { LOG.info("shutting down InApplicationMonitorJMXConnector "); corePlugin.removeReportableObserver(this); removeAllReportables(); try { jmxBeanRegistrationHelper.unregisterMBeanOnJMX("InApplicationMonitor", null); } catch (Exception e) { LOG.warn("problem when unregistering InApplicationMonitorJMXConnector during shutdown", e); } instance = null; } } /** * registers the InApplicationMonitor as JMX MBean on the running JMX * server - if no JMX server is running, one is started automagically. */ private void registerJMXStuff() { LOG.info("registering InApplicationMonitorDynamicMBean on JMX server"); try { jmxBeanRegistrationHelper.registerMBeanOnJMX(this, "InApplicationMonitor", null); } catch (Exception e) { LOG.error("could not register MBean server : ", e); } } /** * This method is called for each reportable that is registered on the InApplicationMonitor. * Basically, it checks if the JMX implementation is interested in the reportable and adds * it to the "reportables" map which is the base for both getAttribute(s) and getMBeanInfo(). */ public void addNewReportable(Reportable reportable) { // use intern string representation - so we can synchronize on it final String reportableKey = reportable.getName().intern(); //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (reportableKey) { boolean beanAlreadyRegistred = reportables.containsKey(reportableKey); reportables.put(reportableKey, reportable); // MBean for each reportable if ((reportable instanceof Timer) || (reportable instanceof HistorizableList)) { InApplicationMonitorDynamicMBean bean = new InApplicationMonitorDynamicMBean(reportable); try { if (beanAlreadyRegistred) { jmxBeanRegistrationHelper.unregisterMBeanOnJMX(reportableKey, "InApplicationMonitor"); } jmxBeanRegistrationHelper.registerMBeanOnJMX(bean, reportableKey, "InApplicationMonitor"); } catch (Exception e) { LOG.error("could not register MBean for " + reportableKey, e); } } if (LOG.isDebugEnabled()) { LOG.debug("registered new reportable " + reportableKey + " for JMX"); } } } public void removeAllReportables() { for (Reportable reportable : reportables.values()) { // use intern string representation - so we can synchronize on it final String reportableKey = reportable.getName().intern(); //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (reportableKey) { // MBean for each reportable if ((reportable instanceof Timer) || (reportable instanceof HistorizableList)) { try { jmxBeanRegistrationHelper.unregisterMBeanOnJMX(reportableKey, "InApplicationMonitor"); } catch (Exception e) { LOG.error("could not unregister MBean for " + reportableKey, e); } } } } reportables.clear(); } /* MBean methods */ public MBeanInfo getMBeanInfo() { List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>(); for (Entry<String, Reportable> entry : reportables.entrySet()) { /* we do not handle the not-so-primitive data types like "Timer" and "HistorizableList" * because those get separate MBeans */ if ((entry.getValue() instanceof Counter) || (entry.getValue() instanceof StateValueProvider)) { attributes.add(new MBeanAttributeInfo(entry.getKey(), "long", entry.getKey(), true, false, false)); } else if (entry.getValue() instanceof MultiValueProvider) { LOG.info("### add multi value " + entry.getKey()); attributes.add(new MBeanAttributeInfo(entry.getKey(), "javax.management.openmbean.CompositeData", entry.getKey(), true, false, false)); } else if (entry.getValue() instanceof Version) { attributes.add(new MBeanAttributeInfo(entry.getKey(), "String", entry.getKey(), true, false, false)); } } // now build the attribute list MBeanAttributeInfo[] beanAttributeInfos = new MBeanAttributeInfo[attributes.size()]; if (LOG.isDebugEnabled()) { LOG.debug("getMBeanInfo returning " + attributes.size() + " reportables."); } for (int loop = 0; loop < attributes.size(); loop++) { beanAttributeInfos[loop] = attributes.get(loop); } // add operations MBeanOperationInfo[] beanOperationInfos = new MBeanOperationInfo[] { new MBeanOperationInfo(DUMP_STRING_WRITER, "", null, "String", MBeanOperationInfo.ACTION_INFO), new MBeanOperationInfo(DUMP_HISTOGRAM_LIKE_VALUE_ANALYSIS, "", new MBeanParameterInfo[] { new MBeanParameterInfo("base", "java.lang.String", "") }, "String", MBeanOperationInfo.ACTION_INFO), new MBeanOperationInfo(ACTIVATE, "enable monitoring", null, "void", MBeanOperationInfo.ACTION), new MBeanOperationInfo(DEACTIVATE, "disable monitoring", null, "void", MBeanOperationInfo.ACTION), new MBeanOperationInfo(IS_MONITOR_ACTIVE, "check if monitor is active", null, "java.lang.Boolean", MBeanOperationInfo.INFO), new MBeanOperationInfo(GET_REGISTERED_PLUGIN_KEYS, "list registered plugin keys", null, "java.util.List", MBeanOperationInfo.INFO), new MBeanOperationInfo(GET_REGISTERED_OBSERVERS, "list registered reportable observers", null, "java.util.List", MBeanOperationInfo.INFO), new MBeanOperationInfo(REMOVE_ALL_PLUGINS, "remove all plugins", null, "void", MBeanOperationInfo.ACTION), new MBeanOperationInfo(ADD_STATSD_PLUGIN, "", new MBeanParameterInfo[] { new MBeanParameterInfo("statsd hostname", "java.lang.String", ""), new MBeanParameterInfo("statsd port", "java.lang.Integer", ""), new MBeanParameterInfo("app name", "java.lang.String", ""), new MBeanParameterInfo("sample rate", "java.lang.Double", "") }, "void", MBeanOperationInfo.ACTION), new MBeanOperationInfo(ADD_STATE_VALUES_TO_GRAPHITE, "", new MBeanParameterInfo[] { new MBeanParameterInfo("graphite hostname", "java.lang.String", ""), new MBeanParameterInfo("graphite port", "java.lang.Integer", ""), new MBeanParameterInfo("app name", "java.lang.String", "") }, "void", MBeanOperationInfo.ACTION), new MBeanOperationInfo(ADD_JMX_EXPORTER_PATTERN, "This will add an ObjectName pattern to the JMXExporter", new MBeanParameterInfo[] { new MBeanParameterInfo("ObjectName pattern", "java.lang.String", ""), }, "void", MBeanOperationInfo.ACTION), new MBeanOperationInfo(LIST_JMX_EXPORTER_PATTERN, "List current JMXExporter Patterns", null, "java.util.List", MBeanOperationInfo.ACTION), new MBeanOperationInfo(REMOVE_JMX_EXPORTER_PATTERN, "This will remove an ObjectName pattern to the JMXExporter", new MBeanParameterInfo[] { new MBeanParameterInfo("ObjectName pattern", "java.lang.String", ""), }, "java.lang.Boolean", MBeanOperationInfo.ACTION), }; // assemble the MBean description return new MBeanInfo("de.is24.util.monitoring.InApplicationMonitorDynamicMBeanThing", "InApplication Monitor dynamic MBean", beanAttributeInfos, null, beanOperationInfos, null); } private Object getValueForReportable(String attribute) { LOG.debug("getting value for attribute " + attribute); Reportable reportable = reportables.get(attribute); if (reportable != null) { if (reportable instanceof Counter) { return ((Counter) reportable).getCount(); } else if (reportable instanceof StateValueProvider) { return ((StateValueProvider) reportable).getValue(); } else if (reportable instanceof MultiValueProvider) { CompositeData compositeData = new MultiValueProviderHelper(((MultiValueProvider) reportable)).toComposite(); LOG.info("type : " + compositeData.getCompositeType().toString()); return compositeData; } else if (reportable instanceof Version) { return ((Version) reportable).getValue(); } } else { LOG.warn("attribute " + attribute + " not found"); } return null; } public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { return getValueForReportable(attribute); } public AttributeList getAttributes(String[] attributes) { AttributeList attributeList = new AttributeList(); for (Attribute attribute : attributeList.asList()) { attributeList.add(new Attribute(attribute.getName(), getValueForReportable(attribute.getName()))); } return attributeList; } /* got no setters at the moment */ public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { } public AttributeList setAttributes(AttributeList attributes) { return null; } public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException { if (actionName.equals(DUMP_STRING_WRITER)) { return dumpStringWriter(); } else if (actionName.equals(DUMP_HISTOGRAM_LIKE_VALUE_ANALYSIS)) { return dumpHistogramLikeValueAnalysis((String) params[0]); } else if (actionName.equals(ACTIVATE)) { activate(); } else if (actionName.equals(DEACTIVATE)) { deactivate(); } else if (actionName.equals(IS_MONITOR_ACTIVE)) { return isMonitorActive(); } else if (actionName.equals(GET_REGISTERED_PLUGIN_KEYS)) { return getRegisteredPluginKeys(); } else if (actionName.equals(GET_REGISTERED_OBSERVERS)) { return getRegisteredObservers(); } else if (actionName.equals(REMOVE_ALL_PLUGINS)) { InApplicationMonitor.getInstance().removeAllPlugins(); } else if (actionName.equals(ADD_STATSD_PLUGIN)) { String host = (String) params[0]; Integer port = (Integer) params[1]; String appName = (String) params[2]; Double sampleRate = (Double) params[3]; addStatsdPlugin(host, port, appName, sampleRate); } else if (actionName.equals(ADD_STATE_VALUES_TO_GRAPHITE)) { String host = (String) params[0]; Integer port = (Integer) params[1]; String appName = (String) params[2]; addStateValuesToGraphite(host, port, appName); } else if (actionName.equals(ADD_JMX_EXPORTER_PATTERN)) { String pattern = (String) params[0]; corePlugin.addJMXExporterPattern(pattern); } else if (actionName.equals(LIST_JMX_EXPORTER_PATTERN)) { return corePlugin.listJMXExporterPattern(); } else if (actionName.equals(REMOVE_JMX_EXPORTER_PATTERN)) { String pattern = (String) params[0]; return corePlugin.removeJMXExporterPattern(pattern); } return null; } /* operations that can be invoked via JMX */ private void addStatsdPlugin(String host, Integer port, String appName, Double sampleRate) { StatsdPlugin statsdPlugin; try { statsdPlugin = new StatsdPlugin(host, port, appName, sampleRate); InApplicationMonitor.getInstance().registerPlugin(statsdPlugin); } catch (Exception e) { throw new RuntimeException(e); } } private void addStateValuesToGraphite(String host, Integer port, String appName) { try { LOG.info("About to create a StateValueToGraphite Instance in JMXConnector with host: " + host + ", port: " + port + ", appName: " + appName); new StateValuesToGraphite(host, port, appName); LOG.info("Creation of StateValueToGraphite Instance succeeded"); } catch (Exception e) { LOG.warn("Creation of StateValueToGraphite Instance in JMXConnector failed: host: " + host + ", port: " + port + ", appName: " + appName, e); throw new RuntimeException(e); } } public String dumpStringWriter() { StringWriterReportVisitor visitor = new StringWriterReportVisitor(); corePlugin.reportInto(visitor); LOG.info(visitor.toString()); return visitor.toString(); } public String dumpHistogramLikeValueAnalysis(String base) { HistogramLikeValueAnalysisVisitor visitor = new HistogramLikeValueAnalysisVisitor(base); corePlugin.reportInto(visitor); LOG.info(visitor.toString()); return visitor.toString(); } public void activate() { InApplicationMonitor.getInstance().activate(); } public void deactivate() { InApplicationMonitor.getInstance().deactivate(); } public Boolean isMonitorActive() { return Boolean.valueOf(InApplicationMonitor.getInstance().isMonitorActive()); } public List<String> getRegisteredPluginKeys() { return InApplicationMonitor.getInstance().getRegisteredPluginKeys(); } public List<String> getRegisteredObservers() { return InApplicationMonitor.getInstance().getCorePlugin().getRegisteredReportableObservers(); } }