/* * * Copyright (c) void.fm * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name void.fm nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ package etm.core.monitor; import etm.core.aggregation.Aggregator; import etm.core.aggregation.BufferedThresholdAggregator; import etm.core.aggregation.RootAggregator; import etm.core.configuration.EtmMonitorFactory; import etm.core.metadata.EtmMonitorMetaData; import etm.core.monitor.event.AggregationStateListener; import etm.core.monitor.event.AggregationStateLoadedEvent; import etm.core.monitor.event.CollectionDisabledEvent; import etm.core.monitor.event.CollectionEnabledEvent; import etm.core.monitor.event.DefaultEventDispatcher; import etm.core.monitor.event.EtmMonitorEvent; import etm.core.monitor.event.EtmMonitorListener; import etm.core.monitor.event.EventDispatcher; import etm.core.plugin.EtmPlugin; import etm.core.renderer.MeasurementRenderer; import etm.core.timer.ExecutionTimer; import etm.core.util.Log; import etm.core.util.LogAdapter; import etm.core.util.Version; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Timer; /** * Abstract base class for the execution time measurement monitors. * An EtmMonitor mandates the following life cycle for measurement * points. * </p> * <ol> * <li> * Newly created MeasurementPoint instances register * themself automatically before the actual measurement process using * {@link #visitPreMeasurement(MeasurementPoint)}. * </li> * <li> * Within {@link #visitPreMeasurement} the EtmMonitor sets the start time * of the measurement. * </li> * <li> * The calling business code executes. * </li> * <li> * After business code execution the Measurement Point calls * {@link #visitPostCollect(MeasurementPoint)}. This call is triggered by * {@link MeasurementPoint#collect()}. * </li> * <li> * Within {@link #visitPostCollect} the EtmMonitor sets the end time * of the measurement and stores this transaction for further aggregation. * </li> * </ol> * * @author void.fm * @version $Revision$ */ public abstract class EtmMonitorSupport implements EtmMonitor, AggregationStateListener { private static final LogAdapter LOG = Log.getLog(EtmMonitor.class); protected final String description; protected final ExecutionTimer timer; protected final Aggregator aggregator; protected List plugins; private Timer scheduler; private EventDispatcher dispatcher; private Date startTime; private Date lastReset; private boolean started = false; private boolean collecting = true; private boolean noStartedErrorMessageFlag = false; /** * Creates a EtmMonitorSupport instance. * * @param aDescription The description for this monitor. * @param aTimer The timer to use. * @param aAggregator The aggregator to use. */ protected EtmMonitorSupport(String aDescription, ExecutionTimer aTimer, Aggregator aAggregator) { description = aDescription; if (aTimer != null) { timer = aTimer; } else { timer = EtmMonitorFactory.bestAvailableTimer(); } if (aAggregator != null) { aggregator = aAggregator; } else { aggregator = getDefaultAggregator(); } startTime = new Date(); lastReset = startTime; } public EtmPoint createPoint(String symbolicName) { return new MeasurementPoint(this, symbolicName); } public final void visitPreMeasurement(MeasurementPoint measurementPoint) { try { if (!collecting) { return; } if (!started) { if (!noStartedErrorMessageFlag) { showMonitorNotStartedMessage(); } return; } if (measurementPoint == null) { return; } doVisitPreMeasurement(measurementPoint); measurementPoint.setTicks(timer.getTicksPerSecond()); measurementPoint.setStartTime(timer.getCurrentTime()); // catch all exceptions here // in order to avoid negative side effects for // our business logic } catch (Exception e) { LOG.warn("Caught exception within measurement code. ", e); } } public final void visitPostCollect(MeasurementPoint measurementPoint) { if (!collecting || !started) { return; } if (measurementPoint == null || measurementPoint.isCollected()) { return; } try { measurementPoint.setEndTime(timer.getCurrentTime()); doVisitPostCollect(measurementPoint); aggregator.add(measurementPoint); // catch all exceptions here // in order to avoid negative side effects for // our business logic } catch (Exception e) { LOG.warn("Caught exception within measurement code.", e); } } public final void aggregate() { aggregator.flush(); } public void render(MeasurementRenderer renderer) { // we do not lock here since we assume non blocking read aggregator.render(renderer); } public void reset() { aggregator.reset(); lastReset = new Date(); } public void reset(String measurementPoint) { aggregator.reset(measurementPoint); } public final EtmMonitorMetaData getMetaData() { List pluginMetaData = getPluginMetaData(); return new EtmMonitorMetaData( getClass(), description, startTime, lastReset, aggregator.getMetaData(), timer.getMetaData(), pluginMetaData); } public void start() { if (started) { collecting = true; return; } scheduler = new Timer(true); if (dispatcher == null) { dispatcher = new DefaultEventDispatcher(); } dispatcher.register(this); // 1. start plugins startPlugins(); // 2. init aggregators aggregator.init(new EtmMonitorSupportContext(this, scheduler)); // 3. start aggregators aggregator.start(); started = true; collecting = true; LOG.info("JETM " + Version.getVersion() + " started."); } public void stop() { LOG.info("Shutting down JETM."); if (!started) { collecting = false; return; } collecting = false; started = false; scheduler.cancel(); aggregator.stop(); shutdownPlugins(); dispatcher.deregister(this); } public boolean isStarted() { return started; } public void enableCollection() { collecting = true; dispatcher.fire(new CollectionEnabledEvent(this)); } public void disableCollection() { collecting = false; dispatcher.fire(new CollectionDisabledEvent(this)); } public boolean isCollecting() { return collecting; } public void addPlugin(EtmPlugin aEtmPlugin) { if (plugins == null) { plugins = new ArrayList(); } plugins.add(aEtmPlugin); if (started) { startPlugin(aEtmPlugin); } } public void setPlugins(List newPlugins) { if (plugins != null) { throw new IllegalStateException("Unable to set a list of plugins after plugins exists."); } for (int i = 0; i < newPlugins.size(); i++) { EtmPlugin plugin = (EtmPlugin) newPlugins.get(i); addPlugin(plugin); } } public void onStateLoaded(AggregationStateLoadedEvent event) { startTime = event.getState().getStartTime(); lastReset = event.getState().getLastResetTime(); } /** * <p/> * Callback method for derived classes. * </p> * <p/> * This method is called immediately after the measurement point was created. Note that * neither {@link MeasurementPoint#getTicks()} nor {@link MeasurementPoint#getStartTime()} are set * at that point. * </p> * * @param aMeasurementPoint The measurement point just created. */ protected abstract void doVisitPreMeasurement(MeasurementPoint aMeasurementPoint); /** * <p/> * Callback method for derived classes. * </p> * <p/> * This method is called immediately after the measurement point was collected and marked as * closed. At that point the all required information are valid for that measurement point. * </p> * * @param aPoint The point to collect. */ protected abstract void doVisitPostCollect(MeasurementPoint aPoint); protected Aggregator getDefaultAggregator() { return new BufferedThresholdAggregator(new RootAggregator()); } protected void shutdownPlugins() { if (plugins != null) { for (int i = 0; i < plugins.size(); i++) { EtmPlugin etmPlugin = (EtmPlugin) plugins.get(i); try { if (etmPlugin instanceof EtmMonitorListener) { dispatcher.deregister((EtmMonitorListener) etmPlugin); } etmPlugin.stop(); } catch (Exception e) { LOG.warn("Error while shutting down " + etmPlugin.getPluginMetaData(), e); } } } } protected void startPlugins() { if (plugins != null) { for (int i = 0; i < plugins.size(); i++) { EtmPlugin etmPlugin = (EtmPlugin) plugins.get(i); startPlugin(etmPlugin); } } } private void startPlugin(EtmPlugin aEtmPlugin) { try { aEtmPlugin.init(new EtmMonitorSupportContext(this, scheduler)); if (aEtmPlugin instanceof EtmMonitorListener) { dispatcher.register((EtmMonitorListener) aEtmPlugin); } aEtmPlugin.start(); } catch (Exception e) { LOG.warn("Error starting plugin " + aEtmPlugin.getPluginMetaData() + ". Keep plugin disabled. ", e); } } private List getPluginMetaData() { if (plugins != null) { List metaData = new ArrayList(plugins.size()); for (int i = 0; i < plugins.size(); i++) { metaData.add(((EtmPlugin) plugins.get(i)).getPluginMetaData()); } return metaData; } return null; } private void showMonitorNotStartedMessage() { LOG.warn("Warning - Performance Monitoring currently disabled. " + "If you did not start the current EtmMonitor on purpose, " + "you may ignore this warning. Otherwhise ensure to call EtmMonitor.start() " + "at some point in your application."); noStartedErrorMessageFlag = true; } class EtmMonitorSupportContext implements EtmMonitorContext { private EtmMonitor monitor; private Timer scheduler; public EtmMonitorSupportContext(EtmMonitor aMonitor, Timer aScheduler) { monitor = aMonitor; scheduler = aScheduler; } public EtmMonitor getEtmMonitor() { return monitor; } public Timer getScheduler() { return scheduler; } public void fireEvent(EtmMonitorEvent event) { dispatcher.fire(event); } } }