package de.is24.util.monitoring; import de.is24.util.monitoring.jmx.InApplicationMonitorJMXConnector; import de.is24.util.monitoring.jmx.JMXExporter; import de.is24.util.monitoring.jmx.JmxAppMon4JNamingStrategy; import de.is24.util.monitoring.keyhandler.KeyHandler; import de.is24.util.monitoring.keyhandler.TransparentKeyHandler; import de.is24.util.monitoring.tools.VirtualMachineMetrics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; /** * This plugin represents the former core functionality of InApplicationMonitor, on a way to a more * flexible implementation by plugins, to simplify testing and the first step on the way fo a more * dependency injection friendly implementation. * * This plugin will take over some functionality that only makes sense in the context of a plugin that stores * data locally in the JVM. Other plugins (namely the statsd plugin) move data aggregation out of the JVM. * And thus it makes no sense to let them implement some of the patterns like reportableObserver etc. */ public class CorePlugin extends AbstractMonitorPlugin { private static final Logger LOGGER = LoggerFactory.getLogger(CorePlugin.class); private volatile int maxHistoryEntriesToKeep = 5; private final CopyOnWriteArrayList<ReportableObserver> reportableObservers = new CopyOnWriteArrayList<ReportableObserver>(); private final Monitors<Counter> counters = new Monitors<Counter>(reportableObservers); private final Monitors<Timer> timers = new Monitors<Timer>(reportableObservers); private final Monitors<StateValueProvider> stateValues = new Monitors<StateValueProvider>(reportableObservers); private final Monitors<MultiValueProvider> multiValues = new Monitors<MultiValueProvider>(reportableObservers); private final Monitors<Version> versions = new Monitors<Version>(reportableObservers); private final Monitors<HistorizableList> historizableLists = new Monitors<HistorizableList>(reportableObservers); private volatile InApplicationMonitorJMXConnector inApplicationMonitorJMXConnector; private KeyHandler keyHandler; private final JMXExporter jmxExporter; WeakReference<ReportableObserver> syncObserverReference; private static final Object semaphore = new Object(); private final String uniqueName; public CorePlugin(JmxAppMon4JNamingStrategy jmxAppMon4JNamingStrategy, KeyHandler keyHandler) { synchronized (semaphore) { if (keyHandler != null) { this.keyHandler = keyHandler; } else { this.keyHandler = new TransparentKeyHandler(); } jmxExporter = new JMXExporter(); registerMultiValueProvider(jmxExporter); if (jmxAppMon4JNamingStrategy != null) { inApplicationMonitorJMXConnector = new InApplicationMonitorJMXConnector(this, jmxAppMon4JNamingStrategy); } uniqueName = "CorePlugin" + ((jmxAppMon4JNamingStrategy != null) ? jmxAppMon4JNamingStrategy.getJmxPrefix() : ("NoJmx" + UUID.randomUUID().toString())); initDefaultStateValues(); } LOGGER.info("CorePlugin {} initialized", uniqueName); } public void initDefaultStateValues() { registerStateValue(new StateValueProvider() { @Override public String getName() { return Runtime.class.getName() + ".totalMem"; } @Override public long getValue() { return Runtime.getRuntime().totalMemory(); } }); registerStateValue(new StateValueProvider() { @Override public String getName() { return Runtime.class.getName() + ".freeMem"; } @Override public long getValue() { return Runtime.getRuntime().freeMemory(); } }); registerVersion(new Version(this.getClass().getName(), "$Id: 2a188d1d7456c2678482bc6d04f4b4b43ee1c111 $")); VirtualMachineMetrics.registerVMStates(this); } @Override public void afterRemovalNotification() { LOGGER.info("CorePlugin {} notified of removal", uniqueName); destroy(); } public synchronized void destroy() { synchronized (semaphore) { if (isJMXInitialized()) { inApplicationMonitorJMXConnector.shutdown(); inApplicationMonitorJMXConnector = null; } } } @Override public String getUniqueName() { return uniqueName; } private boolean isJMXInitialized() { return inApplicationMonitorJMXConnector != null; } /** * @return Number of entries to keep for each Historizable list. */ public int getMaxHistoryEntriesToKeep() { return maxHistoryEntriesToKeep; } /** * Set the Number of entries to keep for each Historizable list. * Default is 5. * * @param aMaxHistoryEntriesToKeep Number of entries to keep */ public void setMaxHistoryEntriesToKeep(int aMaxHistoryEntriesToKeep) { maxHistoryEntriesToKeep = aMaxHistoryEntriesToKeep; } /** * adds a new ReportableObserver that wants to be notified about new Reportables that are * registered on the InApplicationMonitor * @param reportableObserver the class that wants to be notified */ public void addReportableObserver(final ReportableObserver reportableObserver) { reportableObservers.add(reportableObserver); LOGGER.info("registering new ReportableObserver (" + reportableObserver.getClass().getName() + ")"); // iterate over all reportables that are registered already and call the observer for each one of them reportInto(new ReportVisitor() { public void notifyReportableObserver(Reportable reportable) { reportableObserver.addNewReportable(reportable); } @Override public void reportCounter(Counter counter) { notifyReportableObserver(counter); } @Override public void reportTimer(Timer timer) { notifyReportableObserver(timer); } @Override public void reportStateValue(StateValueProvider stateValueProvider) { notifyReportableObserver(stateValueProvider); } @Override public void reportMultiValue(MultiValueProvider multiValueProvider) { notifyReportableObserver(multiValueProvider); } @Override public void reportHistorizableList(HistorizableList historizableList) { notifyReportableObserver(historizableList); } @Override public void reportVersion(Version version) { notifyReportableObserver(version); } }); } private void notifyReportableObservers(Reportable reportable) { for (ReportableObserver reportableObserver : reportableObservers) { reportableObserver.addNewReportable(reportable); } } /** * Allow disconnection of observers, mainly for testing * * @param reportableObserver the observer to be removed */ public void removeReportableObserver(final ReportableObserver reportableObserver) { reportableObservers.remove(reportableObserver); } /** * Implements the {@link de.is24.util.monitoring.InApplicationMonitor} side of the Visitor pattern. * Iterates through all registered {@link de.is24.util.monitoring.Reportable} instances and calls * the corresponding method on the {@link de.is24.util.monitoring.ReportVisitor} implementation. * @param reportVisitor The {@link de.is24.util.monitoring.ReportVisitor} instance that shall be visited * by all regieteres {@link de.is24.util.monitoring.Reportable} instances. */ public void reportInto(ReportVisitor reportVisitor) { counters.accept(reportVisitor); timers.accept(reportVisitor); stateValues.accept(reportVisitor); multiValues.accept(reportVisitor); versions.accept(reportVisitor); historizableLists.accept(reportVisitor); } /** * <p>Increase the specified counter by a variable amount.</p> * * @param name * the name of the {@code Counter} to increase * @param increment * the added to add */ @Override public void incrementCounter(String name, int increment) { incrementInternalCounter(increment, name); } @Override public void incrementHighRateCounter(String name, int increment) { incrementInternalCounter(increment, name); } private void incrementInternalCounter(int increment, String name) { getCounter(name).increment(increment); } /** * Initialization of a counter. * @param name the name of the counter to be initialized */ @Override public void initializeCounter(String name) { getCounter(name).initialize(); } /** * Add a timer measurement for the given name. * {@link de.is24.util.monitoring.Timer}s allow adding timer measurements, implicitly incrementing the count * Timers count and measure timed events. * The application decides which unit to use for timing. * Miliseconds are suggested and some {@link de.is24.util.monitoring.ReportVisitor} implementations * may imply this. * * @param name name of the {@link de.is24.util.monitoring.Timer} * @param timing number of elapsed time units for a single measurement */ @Override public void addTimerMeasurement(String name, long timing) { getTimer(name).addMeasurement(timing); } /** * Add a timer measurement for a rarely occuring event with given name. * This allows Plugins to to react on the estimated rate of the event. * Namely the statsd plugin will not sent these , as the requires storage * is in no relation to the value of the data. * {@link de.is24.util.monitoring.Timer}s allow adding timer measurements, implicitly incrementing the count * Timers count and measure timed events. * The application decides which unit to use for timing. * Miliseconds are suggested and some {@link de.is24.util.monitoring.ReportVisitor} implementations * may imply this. * * @param name name of the {@link de.is24.util.monitoring.Timer} * @param timing number of elapsed time units for a single measurement */ @Override public void addSingleEventTimerMeasurement(String name, long timing) { addTimerMeasurement(name, timing); } /** * Add a timer measurement for a rarely occuring event with given name. * This allows Plugins to to react on the estimated rate of the event. * Namely the statsd plugin will not sent these , as the requires storage * is in no relation to the value of the data. * {@link de.is24.util.monitoring.Timer}s allow adding timer measurements, implicitly incrementing the count * Timers count and measure timed events. * The application decides which unit to use for timing. * Miliseconds are suggested and some {@link de.is24.util.monitoring.ReportVisitor} implementations * may imply this. * * @param name name of the {@link de.is24.util.monitoring.Timer} * @param timing number of elapsed time units for a single measurement */ @Override public void addHighRateTimerMeasurement(String name, long timing) { addTimerMeasurement(name, timing); } /** * Initialization of a TimerMeasurement * @param name the name of the timer to be initialized */ @Override public void initializeTimerMeasurement(String name) { getTimer(name).initializeMeasurement(); } /** * Add a state value provider to this appmon4j instance. * {@link de.is24.util.monitoring.StateValueProvider} instances allow access to a numeric * value (long), that is already available in the application. * * @param stateValueProvider the StateValueProvider instance to add */ public void registerStateValue(StateValueProvider stateValueProvider) { String name = keyHandler.handle(stateValueProvider.getName()); StateValueProvider oldProvider = stateValues.put(name, stateValueProvider); if (oldProvider != null) { LOGGER.warn("StateValueProvider [{}] @{} has been replaced by [{}]!", oldProvider, stateValueProvider.getName(), stateValueProvider); } notifyReportableObservers(stateValueProvider); } /** * Add a multi value provider to this appmon4j instance. * {@link de.is24.util.monitoring.MultiValueProvider} instances allow access to multiple numeric * values (long) * * @param multiValueProvider the MultoValueProvider instance to add */ public void registerMultiValueProvider(MultiValueProvider multiValueProvider) { String name = multiValueProvider.getName(); MultiValueProvider oldProvider = multiValues.put(name, multiValueProvider); if (oldProvider != null) { LOGGER.warn("MultiValueProvider [{}] @{} has been replaced by [{}]!", oldProvider, multiValueProvider.getName(), multiValueProvider); } notifyReportableObservers(multiValueProvider); } /** * This method was intended to register module names with their * current version identifier. * This could / should actually be generalized into an non numeric * state value * * @param versionToAdd The Version Object to add */ public void registerVersion(Version versionToAdd) { String versionName = keyHandler.handle(versionToAdd.getName()); versions.put(versionName, versionToAdd); notifyReportableObservers(versionToAdd); } /** * add a {@link de.is24.util.monitoring.Historizable} instance to the list identified by historizable.getName() * * @param name key of the historizbale metric * @param historizable the historizable to add */ public void addHistorizable(String name, Historizable historizable) { HistorizableList listToAddTo = getHistorizableList(name); listToAddTo.add(historizable); } /** * @param name the name of the StatsValueProvider * @return the StatsValueProvider */ StateValueProvider getStateValue(String name) { return stateValues.get(name); } /** * @param name the name of the MultiValueProvider * @return the MultiValueProvider */ public MultiValueProvider getMultiValueProvider(String name) { return multiValues.get(name); } /** * internally used method to retrieve or create and register a named {@link de.is24.util.monitoring.Counter}. * @param name of the required {@link de.is24.util.monitoring.Counter} * @return {@link de.is24.util.monitoring.Counter} instance registered for the given name */ Counter getCounter(final String name) { return counters.get(name, new Monitors.Factory<Counter>() { @Override public Counter createMonitor() { return new Counter(name); } }); } /** * internaly used method to retrieve or create and register a named {@link de.is24.util.monitoring.Timer}. * @param name of the required {@link de.is24.util.monitoring.Timer} * @return {@link de.is24.util.monitoring.Timer} instance registered for the given name */ Timer getTimer(final String name) { return (Timer) timers.get(name, new Monitors.Factory<Timer>() { @Override public Timer createMonitor() { return new Timer(name); } }); } /** * internally used method to retrieve or create and register a named HistorizableList. * @param name of the required {@link de.is24.util.monitoring.HistorizableList} * @return {@link de.is24.util.monitoring.HistorizableList} instance registered for the given name */ HistorizableList getHistorizableList(final String name) { return historizableLists.get(name, new Monitors.Factory<HistorizableList>() { @Override public HistorizableList createMonitor() { return new HistorizableList(name, maxHistoryEntriesToKeep); } }); } public void addJMXExporterPattern(String pattern) { try { jmxExporter.addPattern(pattern); } catch (MalformedObjectNameException e) { LOGGER.warn("adding JMXExporter pattern failed", e); throw new RuntimeException(e); } } public boolean removeJMXExporterPattern(String pattern) { try { return jmxExporter.removePattern(pattern); } catch (MalformedObjectNameException e) { LOGGER.warn("removing JMXExporter pattern failed due to illegal Object Name", e); throw new RuntimeException(e); } } public List<ObjectName> listJMXExporterPattern() { return jmxExporter.listPatterns(); } public void readJMXExporterPatternFromFile(String filename) { jmxExporter.readFromFile(filename); } public void readJMXExporterPatternFromDir(String dirname) { jmxExporter.readFromDirectory(dirname); } public void syncFrom(CorePlugin corePluginToSyncWith) { for (ReportableObserver reportableObserver : corePluginToSyncWith.reportableObservers) { LOGGER.warn("while syncing: adding reportable observer {}", reportableObserver.getClass().getName()); addReportableObserver(reportableObserver); } SyncObserver syncObserver = new SyncObserver(); // this is for testing syncObserverReference = new WeakReference<ReportableObserver>(syncObserver); corePluginToSyncWith.addReportableObserver(syncObserver); } public List<String> getRegisteredReportableObservers() { ArrayList<String> list = new ArrayList<String>(); for (ReportableObserver observer : reportableObservers) { list.add(observer.toString()); } return list; } private class SyncObserver implements ReportableObserver { @Override public void addNewReportable(Reportable reportable) { String name = keyHandler.handle(reportable.getName()); LOGGER.info("syncing reportable {}", reportable.getName()); if ((reportable instanceof Counter)) { counters.putIfAbsent(name, (Counter) reportable); } else if ((reportable instanceof Timer)) { timers.putIfAbsent(name, (Timer) reportable); } else if (reportable instanceof Version) { versions.putIfAbsent(name, (Version) reportable); } else if (reportable instanceof HistorizableList) { historizableLists.putIfAbsent(name, (HistorizableList) reportable); } else if (reportable instanceof StateValueProvider) { stateValues.putIfAbsent(name, (StateValueProvider) reportable); } } } }