/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.rtc; import java.io.IOException; import java.lang.reflect.UndeclaredThrowableException; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.exolab.castor.xml.MarshalException; import org.exolab.castor.xml.ValidationException; import org.opennms.core.concurrent.LogPreservingThreadFactory; import org.opennms.core.utils.ThreadCategory; import org.opennms.netmgt.config.RTCConfigFactory; import org.opennms.netmgt.daemon.AbstractServiceDaemon; import org.opennms.netmgt.rtc.datablock.RTCCategory; /** * Maintains calculations for categories. * <P> * The RTCManager maintains data required so as to calculate availability for * the different categories configured in categories.xml * </P> * * <P> * The RTC initializes its data from the database when it comes up. It then * subscribes to the Events subsystem to receive events of interest to keep the * data up-to-date * </P> * * <P> * Availability data is sent out to listeners who indicate that they are * listening by sending an RTC 'subscribe' event. The subscribe event has an URL * and user/passwd info. so RTC can post data to the URL * </P> * * <P> * The RTC has two timers(a low threshold and a high threshold) and a counter * that can run upto a configurable max number of events - these are used to * determine when availability information is to be sent out when event streams * are coming in at normal rates. When no events are received, a timer * configured with a user configured time(defaulting to one minute) decides the * interval at which data is sent * </P> * * @author <A HREF="mailto:sowmya@opennms.org">Sowmya Kumaraswamy </A> * @author <A HREF="http://www.opennms.org">OpenNMS.org </A> * @author <A HREF="mailto:sowmya@opennms.org">Sowmya Kumaraswamy </A> * @author <A HREF="http://www.opennms.org">OpenNMS.org </A> * @see org.opennms.netmgt.rtc.RTCConstants * @see org.opennms.netmgt.rtc.DataSender * @see org.opennms.netmgt.rtc.DataManager * @version $Id: $ */ public final class RTCManager extends AbstractServiceDaemon { /** * Singleton instance of this class */ private static final RTCManager m_singleton = new RTCManager(); /** * The id for the low threshold timer task */ private static final String LOWT_TASK = "lowTtask"; /** * The id for the high threshold timer task */ private static final String HIGHT_TASK = "highTtask"; /** * The id for the user refresh timer task */ private static final String USERTIMER = "userTimer"; /** * The initial number of updater threads */ @SuppressWarnings("unused") private static final int NUM_UPDATERS = 5; /** * The configurable rolling window read from the properties file */ private static long m_rollingWindow = -1; /** * The RTC timer */ private Timer m_timer; /** * The low threshold timer task */ private TimerTask m_lowTtask; /** * The low threshold refresh interval. The low threshold at which data is * sent out */ private long m_lowThresholdInterval = -1; /** * The high threshold timer task */ private TimerTask m_highTtask; /** * The high threshold refresh interval. The high threshold at which data is * sent out */ private long m_highThresholdInterval = -1; /** * The user refresh timer task */ private TimerTask m_userTask; /** * The user refresh interval. The interval at which data is sent even if no * events are received */ private long m_userRefreshInterval = -1; /** * The counter keeping track of the number of messages */ private int m_counter = -1; /** * The maximum number of events that are received before a resend. Note that * this or the timers going off, whichever occurs first triggers a resend */ private int MAX_EVENTS_BEFORE_RESEND = -1; /** * The events receiver */ private BroadcastEventProcessor m_eventReceiver; /** * The RunnableConsumerThreadPool that runs updaters that interpret and * update the data */ private ExecutorService m_updaterPool; /** * The DataSender */ private DataSender m_dataSender; /** * manager of the data maintained by the RTC */ private static DataManager m_dataMgr; /** * The timer scheduled task that runs and informs the RTCManager when the * timer goes off */ private class RTCTimerTask extends TimerTask { /** * The timer id */ private String m_id; /** * Constructor for the timer task * * @param id * the timertask ID */ RTCTimerTask(String id) { m_id = id; } /** * Return the ID * * @return the ID */ public String getID() { return m_id; } /** * Starts the task. When run, simply inform the manager that this has * been called by the timer */ public void run() { timerTaskComplete(this); } } /** * Handles a completed task. * * <P> * If the low threshold or high threshold timers expire, send category data * out and set both timer(task)s to null so they can be reset when the next * event comes in * <P> * * <P> * If the user refresh timer is the one that expired, send category data out * and reset the user timer(task) * <P> * * @param tt * the task that is finishing. */ private synchronized void timerTaskComplete(RTCTimerTask tt) { ThreadCategory log = ThreadCategory.getInstance(getClass()); if (log.isDebugEnabled()) log.debug("TimerTask \'" + tt.getID() + "\' complete, status: " + getStatus()); if (tt.getID().equals(LOWT_TASK)) { // cancel user timer boolean ret = m_userTask.cancel(); if (log.isDebugEnabled()) log.debug("timerTaskComplete: " + USERTIMER + " cancelled: " + ret); // send out the info and reset both timers if (m_highTtask != null) { ret = m_highTtask.cancel(); if (log.isDebugEnabled()) log.debug("timerTaskComplete: " + HIGHT_TASK + " cancelled: " + ret); m_highTtask = null; } if (isRunning()) { m_dataSender.notifyToSend(); } m_lowTtask = null; m_counter = -1; // reset the user timer m_timer.schedule((m_userTask = new RTCTimerTask(USERTIMER)), 0, m_userRefreshInterval); if (log.isDebugEnabled()) log.debug("timerTaskComplete: " + USERTIMER + " scheduled"); } else if (tt.getID().equals(HIGHT_TASK)) { // cancel user timer boolean ret = m_userTask.cancel(); if (log.isDebugEnabled()) log.debug("timerTaskComplete: " + USERTIMER + " cancelled: " + ret); // send the category information out reset all timers if (m_lowTtask != null) { ret = m_lowTtask.cancel(); if (log.isDebugEnabled()) log.debug("timerTaskComplete: " + LOWT_TASK + " cancelled: " + ret); m_lowTtask = null; } if (isRunning()) { m_dataSender.notifyToSend(); } m_highTtask = null; m_counter = -1; // reset the user timer m_timer.schedule((m_userTask = new RTCTimerTask(USERTIMER)), 0, m_userRefreshInterval); if (log.isDebugEnabled()) log.debug("timerTaskComplete: " + USERTIMER + " scheduled"); } else if (tt.getID().equals(USERTIMER)) { // send if not pasued if (isRunning()) { m_dataSender.notifyToSend(); } } } /** * The constructor for the RTCManager */ public RTCManager() { super("OpenNMS.RTCManager"); } /** * Check the timer tasks. Reset any of the timer tasks if they need to be * reset (indicated by their being set to null on timer task completion). If * the events counter has exceeded maxEventsBeforeResend, send data out and * reset timers */ public synchronized void checkTimerTasksOnEventReceipt() { ThreadCategory log = ThreadCategory.getInstance(getClass()); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: Checking if timer tasks need to be reset or data needs to be sent out"); // cancel user timer boolean ret = m_userTask.cancel(); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: " + USERTIMER + " cancelled: " + ret); // Check the counter to see if timers need to be started afresh if (m_counter == -1) { m_counter = 0; // // set timers // // set the low threshold timer task if (m_lowTtask == null) { try { m_timer.schedule((m_lowTtask = new RTCTimerTask(LOWT_TASK)), m_lowThresholdInterval); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: " + LOWT_TASK + " scheduled"); } catch (IllegalStateException isE) { log.error("checkTimerTasksOnEventReceipt: Illegal State adding new RTCTimerTask", isE); } } // set the high threshold timer task only if currently null if (m_highTtask == null) { try { m_timer.schedule((m_highTtask = new RTCTimerTask(HIGHT_TASK)), m_highThresholdInterval); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: " + HIGHT_TASK + " scheduled"); } catch (IllegalStateException isE) { log.error("checkTimerTasksOnEventReceipt: Illegal State adding new RTCTimerTask", isE); } } } if (MAX_EVENTS_BEFORE_RESEND > 0 && m_counter >= MAX_EVENTS_BEFORE_RESEND) { if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: max events before resend limit reached, resetting timers"); // send the category information out and reset all timers if (m_lowTtask != null) { ret = m_lowTtask.cancel(); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: " + LOWT_TASK + " cancelled: " + ret); m_lowTtask = null; } if (m_highTtask != null) { ret = m_highTtask.cancel(); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: " + HIGHT_TASK + " cancelled: " + ret); m_highTtask = null; } if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: max events before resend limit reached, sending data to listeners"); m_dataSender.notifyToSend(); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: max events before resend limit reached, datasender notified to send data"); m_counter = -1; } else if (m_counter != 0) { // reset the low threshold timer since getting here means // we got an event before the low threshold timer // went off if (m_lowTtask != null) { ret = m_lowTtask.cancel(); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: " + LOWT_TASK + " cancelled: " + ret); m_lowTtask = null; } try { m_timer.schedule((m_lowTtask = new RTCTimerTask(LOWT_TASK)), m_lowThresholdInterval); if (log.isDebugEnabled()) log.debug("checkTimerTasksOnEventReceipt: " + LOWT_TASK + " scheduled"); } catch (IllegalStateException isE) { log.error("checkTimerTasksOnEventReceipt: Illegal State adding new RTCTimerTask", isE); } } } /** * Reset the user timer. */ public synchronized void resetUserTimer() { // Reset the user timer if (m_userTask != null) return; ThreadCategory log = ThreadCategory.getInstance(getClass()); try { m_timer.schedule((m_userTask = new RTCTimerTask(USERTIMER)), 0, m_userRefreshInterval); if (log.isDebugEnabled()) log.debug("resetUserTimer: " + USERTIMER + " scheduled"); } catch (IllegalStateException isE) { log.error("dataReceived: Illegal State adding new RTCTimerTask", isE); } } /** * <p>onInit</p> */ protected void onInit() { // load the rtc configuration RTCConfigFactory rFactory = null; try { RTCConfigFactory.reload(); rFactory = RTCConfigFactory.getInstance(); } catch (IOException ex) { log().error("Failed to load rtc configuration", ex); throw new UndeclaredThrowableException(ex); } catch (MarshalException ex) { log().error("Failed to load rtc configuration", ex); throw new UndeclaredThrowableException(ex); } catch (ValidationException ex) { log().error("Failed to load rtc configuration", ex); throw new UndeclaredThrowableException(ex); } // // Get the required attributes // // parse the rolling window info m_rollingWindow = rFactory.getRollingWindow(); // get maxEventsBeforeResend MAX_EVENTS_BEFORE_RESEND = rFactory.getMaxEventsBeforeResend(); // parse the low threshold interval m_lowThresholdInterval = rFactory.getLowThresholdInterval(); // parse the high threshold interval m_highThresholdInterval = rFactory.getHighThresholdInterval(); // parse the user threshold interval String ur = rFactory.getUserRefreshIntervalStr(); if (ur != null) { try { m_userRefreshInterval = rFactory.getUserRefreshInterval(); } catch (Throwable nfE) { log().warn("User refresh time has an incorrect format - using 1 minute instead"); m_userRefreshInterval = 60 * 1000; } } else { log().warn("User refresh time not specified - using 1 minute instead"); m_userRefreshInterval = 60 * 1000; } // high and low thresholds cannot be the same if (m_highThresholdInterval == m_lowThresholdInterval) { throw new RuntimeException("The values for the high and low threshold intervals CANNOT BE EQUAL"); } // if high threshold is smaller than the low threshold, swap 'em if (m_highThresholdInterval < m_lowThresholdInterval) { log().warn("Swapping high and low threshold intervals.."); long tmp = m_highThresholdInterval; m_highThresholdInterval = m_lowThresholdInterval; m_lowThresholdInterval = tmp; } log().info("Rolling Window: " + m_rollingWindow + "(milliseconds)"); log().info("Low Threshold Refresh Interval: " + m_lowThresholdInterval + "(milliseconds)"); log().info("High Threshold Refresh Interval: " + m_highThresholdInterval + "(milliseconds)"); log().info("User Refresh Interval: " + m_userRefreshInterval + "(milliseconds)"); // Intialize the data from the database try { m_dataMgr = new DataManager(); } catch (Throwable ex) { throw new UndeclaredThrowableException(ex); } m_updaterPool = Executors.newFixedThreadPool( rFactory.getUpdaters(), new LogPreservingThreadFactory(getClass().getSimpleName(), rFactory.getUpdaters(), false) ); if (log().isDebugEnabled()) log().debug("Created updater pool"); m_eventReceiver = new BroadcastEventProcessor(m_updaterPool); if (log().isDebugEnabled()) log().debug("Created event receiver"); // create the data sender m_dataSender = new DataSender(getCategories(), rFactory.getSenders()); log().debug("Created DataSender"); // create the timer m_timer = new Timer(); if (log().isDebugEnabled()) { log().debug("RTC ready to receive events"); } } /** * <p>onStart</p> */ protected void onStart() { // // Start all the threads // if (log().isDebugEnabled()) { log().debug("Starting data sender "); } m_dataSender.start(); if (log().isDebugEnabled()) { log().debug("Updater threads and datasender started"); } // set the user refresh timer m_timer.schedule((m_userTask = new RTCTimerTask(USERTIMER)), 0, m_userRefreshInterval); if (log().isDebugEnabled()) log().debug(USERTIMER + " scheduled"); // // Subscribe to events // try { m_eventReceiver.start(); } catch (Throwable t) { m_dataSender.stop(); if (log().isDebugEnabled()) log().debug("DataSender shutdown"); m_updaterPool.shutdown(); if (log().isDebugEnabled()) log().debug("Updater pool shutdown"); m_timer.cancel(); if (log().isDebugEnabled()) log().debug("Timer cancelled"); throw new UndeclaredThrowableException(t); } if (log().isDebugEnabled()) { log().debug("RTC ready to receive events"); } } /** * <p>onStop</p> */ protected void onStop() { try { if (log().isDebugEnabled()) log().debug("Beginning shutdown process"); // // Close connection to the event subsystem and free associated // resources. // m_eventReceiver.close(); if (log().isDebugEnabled()) log().debug("Shutting down the data sender"); // shutdown the data sender m_dataSender.stop(); if (log().isDebugEnabled()) log().debug("DataSender shutdown"); if (log().isDebugEnabled()) log().debug("sending shutdown to updaters"); m_updaterPool.shutdown(); if (log().isDebugEnabled()) log().debug("RTC Updaters shutdown"); // cancel the timer and the timer tasks if (m_lowTtask != null) m_lowTtask.cancel(); if (m_highTtask != null) m_highTtask.cancel(); if (m_userTask != null) m_userTask.cancel(); if (log().isDebugEnabled()) log().debug("shutdown: Timer tasks Canceled"); m_timer.cancel(); if (log().isDebugEnabled()) log().debug("shutdown: Timer Canceled"); } catch (Throwable e) { log().error(e.getLocalizedMessage(), e); } } /** * Updates the number of events received. Increment the counter that keeps * track of number of events received since data was last sent out */ public synchronized void incrementCounter() { m_counter++; } /** * Get the data sender. * * @return the data sender */ public DataSender getDataSender() { return m_dataSender; } /** * Gets the categories. * * @return the categories */ public static Map<String, RTCCategory> getCategories() { return m_dataMgr.getCategories(); } /** * Gets the data manager. * * @return the data manager */ public static DataManager getDataManager() { return m_dataMgr; } /** * Sets the data manager. * * @param dataMgr a {@link org.opennms.netmgt.rtc.DataManager} object. */ public static void setDataManager(DataManager dataMgr) { m_dataMgr = dataMgr; } /** * Gets the rolling window. * * @return the configured rolling window */ public static long getRollingWindow() { return m_rollingWindow; } /** * Gets the instance of the RTCmanager. * * @return the RTCManager singleton. */ public static RTCManager getInstance() { return m_singleton; } }