package de.is24.util.monitoring; import de.is24.util.monitoring.keyhandler.DefaultKeyEscaper; import de.is24.util.monitoring.keyhandler.KeyHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * * This is the central class of appmon4j.<br> * appmon4j is a lightweight, easy to use in application monitoring system * allowing measurements of "real traffic" performance values * in high throughput java applications.<br><br> * * This class is an "old school" singleton, which is accessed by using * the static getInstance() method. * * @author OSchmitz */ public class InApplicationMonitor { private static final Logger LOGGER = LoggerFactory.getLogger(InApplicationMonitor.class); protected static final Object semaphore = new Object(); private volatile boolean monitorActive = true; private final CopyOnWriteArrayList<MonitorPlugin> plugins = new CopyOnWriteArrayList<MonitorPlugin>(); private volatile KeyHandler keyHandler; private volatile CorePlugin corePlugin; protected static InApplicationMonitor instance; static { KeyHandler keyHandler = new DefaultKeyEscaper(); CorePlugin corePlugin = new CorePlugin(null, keyHandler); instance = new InApplicationMonitor(corePlugin, keyHandler); } protected InApplicationMonitor(CorePlugin corePlugin, KeyHandler keyHandler) { this.keyHandler = keyHandler; this.corePlugin = corePlugin; registerPlugin(corePlugin); } /** * Delivers the Singleton instance of InApplicationMonitor. * * @return InApplicationMonitor Singleton */ public static InApplicationMonitor getInstance() { return instance; } public static InApplicationMonitor initInstance(CorePlugin corePlugin, KeyHandler keyHandler) { CorePlugin previousCorePlugin; synchronized (semaphore) { LOGGER.info("+++ initializing InApplicationMonitor() +++"); instance.keyHandler = keyHandler; LOGGER.info("syncing from previous core plugin"); previousCorePlugin = instance.corePlugin; corePlugin.syncFrom(previousCorePlugin); instance.plugins.add(corePlugin); instance.plugins.remove(previousCorePlugin); instance.corePlugin = corePlugin; LOGGER.info("InApplicationMonitor updated successfully."); } if ((previousCorePlugin != null) && (previousCorePlugin != corePlugin)) { previousCorePlugin.destroy(); } return instance; } /** * @see #isMonitorActive */ public void activate() { monitorActive = true; } /** * @see #isMonitorActive */ public void deactivate() { monitorActive = false; } /** * If true, monitoring is active. * If false incrementCounter and addTimer calls * will return without doing anything (thus not synchronizing on any resource). * Initialize calls will be processed however. * registerStateValue, registerVersion and add Historizable will be processed, too. * @return true if the monitor is currently active, false if not */ public boolean isMonitorActive() { return monitorActive; } /** * @return Number of entries to keep for each Historizable list. * @deprecated use corePlugin directly, will be removed from InApplicationMonitor */ @Deprecated public int getMaxHistoryEntriesToKeep() { return getCorePlugin().getMaxHistoryEntriesToKeep(); } /** * Set the Number of entries to keep for each Historizable list. * Default is 5. * @deprecated use corePlugin directly, will be removed from InApplicationMonitor * @param aMaxHistoryEntriesToKeep Number of entries to keep */ @Deprecated public void setMaxHistoryEntriesToKeep(int aMaxHistoryEntriesToKeep) { getCorePlugin().setMaxHistoryEntriesToKeep(aMaxHistoryEntriesToKeep); } /** * adds a new ReportableObserver that wants to be notified about new Reportables that are * registered on the InApplicationMonitor * @deprecated use corePlugin directly, will be removed from InApplicationMonitor * @param reportableObserver the class that wants to be notified */ @Deprecated public void addReportableObserver(final ReportableObserver reportableObserver) { getCorePlugin().addReportableObserver(reportableObserver); } /** * Allow disconnection of observers, mainly for testing * @deprecated use corePlugin directly, will be removed from InApplicationMonitor * @param reportableObserver */ @Deprecated public void removeReportableObserver(final ReportableObserver reportableObserver) { getCorePlugin().removeReportableObserver(reportableObserver); } /** * Implements the {@link InApplicationMonitor} side of the Visitor pattern. * Iterates through all registered {@link Reportable} instances and calls * the corresponding method on the {@link ReportVisitor} implementation. * @param reportVisitor The {@link ReportVisitor} instance that shall be visited * by all regieteres {@link Reportable} instances. * @deprecated use corePlugin directly, will be removed from InApplicationMonitor */ @Deprecated public void reportInto(ReportVisitor reportVisitor) { getCorePlugin().reportInto(reportVisitor); } /** * Increment the named {@link Counter} by one. * @param name name of the {@link Counter} to increment */ public void incrementCounter(String name) { incrementCounter(name, 1); } /** * Increment the named {@link Counter} by one. * Using this method instead of incrementCounter is a hint to some plugins * that this is an event that may happen very often. Plugins may use sampling to * to limit load or network traffic. * @param name name of the {@link Counter} to increment */ public void incrementHighRateCounter(String name) { if (monitorActive) { String escapedName = keyHandler.handle(name); for (MonitorPlugin p : getPlugins()) { p.incrementHighRateCounter(escapedName, 1); } } } /** * <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 */ public void incrementCounter(String name, int increment) { if (monitorActive) { String escapedName = keyHandler.handle(name); for (MonitorPlugin p : getPlugins()) { p.incrementCounter(escapedName, increment); } } } /** * If you want to ensure existance of a counter, for example you want to prevent * spelling errors in an operational monitoring configuration, you may initialize a counter * using this method. The plugins will decide how to handle this initialization. * @param name the name of the counter to be initialized */ public void initializeCounter(String name) { String escapedName = keyHandler.handle(name); for (MonitorPlugin p : getPlugins()) { p.initializeCounter(escapedName); } } /** * Add a timer measurement for the given name. * {@link 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 ReportVisitor} implementations * may imply this. * * @param name name of the {@link Timer} * @param timing number of elapsed time units for a single measurement */ public void addTimerMeasurement(String name, long timing) { if (monitorActive) { String escapedName = keyHandler.handle(name); for (MonitorPlugin p : getPlugins()) { p.addTimerMeasurement(escapedName, 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 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 ReportVisitor} implementations * may imply this. * * @param name name of the {@link Timer} * @param timing number of elapsed time units for a single measurement */ public void addSingleEventTimerMeasurement(String name, long timing) { if (monitorActive) { String escapedName = keyHandler.handle(name); for (MonitorPlugin p : getPlugins()) { p.addSingleEventTimerMeasurement(escapedName, timing); } } } /** * Add a timer measurement for a often occuring event with given name. * This allows Plugins to to react on the estimated rate of the event. * Namely the statsd plugin will use sampling on these, to reduce network traffic. * {@link 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 ReportVisitor} implementations * may imply this. * * @param name name of the {@link Timer} * @param timing number of elapsed time units for a single measurement */ public void addHighRateTimerMeasurement(String name, long timing) { if (monitorActive) { String escapedName = keyHandler.handle(name); for (MonitorPlugin p : getPlugins()) { p.addHighRateTimerMeasurement(escapedName, timing); } } } /** * Add a timer measurement for the given name. * {@link 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 ReportVisitor} implementations * may imply this. * * @param name name of the {@link Timer} * @param begin number of elapsed time units at the beginning of the single measurement * @param end number of elapsed time units at the end of the single measurement */ public void addTimerMeasurement(String name, long begin, long end) { addTimerMeasurement(name, end - begin); } /** * If you want to ensure existence of a timer, for example you want to prevent * spelling errors in an operational monitoring configuration, you may initialize a timer * using this method. The plugins will decide how to handle this initialization. * @param name the name of the timer to be initialized */ public void initializeTimerMeasurement(String name) { String escapedName = keyHandler.handle(name); for (MonitorPlugin p : getPlugins()) { p.initializeTimerMeasurement(escapedName); } } /** * Add a state value provider to this appmon4j instance. * {@link 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) { getCorePlugin().registerStateValue(stateValueProvider); } /** * 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 name name of the versionized "thing" (class, module etc.) * @param version identifier of the version */ public void registerVersion(String name, String version) { Version versionToAdd = new Version(keyHandler.handle(name), version); getCorePlugin().registerVersion(versionToAdd); } /** * add a {@link Historizable} instance to the list identified by historizable.getName() * * @param historizable the historizable to add */ public void addHistorizable(Historizable historizable) { getCorePlugin().addHistorizable(keyHandler.handle(historizable.getName()), historizable); } /** * Register a plugin to able to hook into monitoring with your own monitor. * * @param plugin the plugin to adapt a new monitor. */ public void registerPlugin(MonitorPlugin plugin) { plugins.addIfAbsent(plugin); } public List<String> getRegisteredPluginKeys() { List<String> installedPluginKeys = new ArrayList<String>(); for (MonitorPlugin plugin : getPlugins()) { installedPluginKeys.add(plugin.getUniqueName()); } return installedPluginKeys; } public void removeAllPlugins() { getPlugins().clear(); getPlugins().add(corePlugin); } protected KeyHandler getKeyHandler() { return keyHandler; } protected List<MonitorPlugin> getPlugins() { return plugins; } public CorePlugin getCorePlugin() { return corePlugin; } public void setThreadLocalState() { throw new UnsupportedOperationException( "setThreadLocalState not supported on production InApplicationMonitor, initialize Testing Version"); } public void resetThreadLocalState() { throw new UnsupportedOperationException( "resetThreadLocalState not supported on production InApplicationMonitor, initialize Testing Version"); } }