/* * NOTE: This copyright does *not* cover user programs that use Hyperic * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2011], VMware, Inc. * This file is part of Hyperic. * * Hyperic is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.zevents; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.hq.common.DiagnosticObject; import org.hyperic.hq.common.DiagnosticsLogger; import org.hyperic.hq.context.Bootstrap; import org.hyperic.hq.stats.ConcurrentStatsCollector; import org.hyperic.util.PrintfFormat; import org.hyperic.util.stats.StatCollector; import org.hyperic.util.stats.StatUnreachableException; import org.hyperic.util.thread.LoggingThreadGroup; import org.hyperic.util.thread.ThreadGroupFactory; import org.hyperic.util.thread.ThreadWatchdog; import org.hyperic.util.thread.ThreadWatchdog.InterruptToken; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; /** * The Zevent subsystem is an event system for fast, non-reliable transmission * of events. Important data should not be transmitted on this bus, since it is * not persisted, and there is never any guarantee of receipt. * * This manager provides no transactional guarantees, so the caller must * rollback additions of listeners if the transaction fails. */ @Component public class ZeventManager implements ZeventEnqueuer { private static final Log _log = LogFactory.getLog(ZeventManager.class); private static final long DEFAULT_TIMEOUT = 1; private static final Object INIT_LOCK = new Object(); // The thread group that the {@link EventQueueProcessor} comes from private final LoggingThreadGroup _threadGroup; // The actual queue processor thread private Thread _processorThread; private final Object _listenerLock = new Object(); /* Set of {@link ZeventListener}s listening to events of all types */ private Set<TimingListenerWrapper<Zevent>> _globalListeners = new HashSet<TimingListenerWrapper<Zevent>>(); /* * Map of {@link Class}es subclassing {@link ZEvent} onto lists of {@link * ZeventListener}s */ private Map<Class<? extends Zevent>, List<TimingListenerWrapper<Zevent>>> _listeners = new HashMap<Class<? extends Zevent>, List<TimingListenerWrapper<Zevent>>>(); /* Map of {@link Queue} onto the target listeners using them. */ private WeakHashMap<Queue<?>, TimingListenerWrapper<Zevent>> _registeredBuffers = new WeakHashMap<Queue<?>, TimingListenerWrapper<Zevent>>(); // For diagnostics and warnings private long _lastWarnTime; private final long _listenerTimeout; private final long _warnSize; private final long _warnInterval; private long _maxTimeInQueue; private long _numEvents; private BlockingQueue<Zevent> _eventQueue; private DiagnosticsLogger diagnosticsLogger; private final ThreadWatchdog threadWatchdog; private final long maxQueue; private final long batchSize; private final ConcurrentStatsCollector concurrentStatsCollector; @Autowired public ZeventManager(DiagnosticsLogger diagnosticsLogger, ThreadWatchdog threadWatchdog, ConcurrentStatsCollector concurrentStatsCollector, @Value("#{tweakProperties['hq.zevent.maxQueueEnts'] }") Long maxQueue, @Value("#{tweakProperties['hq.zevent.batchSize'] }") Long batchSize, @Value("#{tweakProperties['hq.zevent.warnInterval'] }") Long warnInterval, @Value("#{tweakProperties['hq.zevent.warnSize'] }") Long warnSize, @Value("#{tweakProperties['hq.zevent.listenerTimeout'] }") Long listenerTimeout) { this._threadGroup = new LoggingThreadGroup("ZEventProcessor"); this._threadGroup.setDaemon(true); this.diagnosticsLogger = diagnosticsLogger; this.threadWatchdog = threadWatchdog; this.concurrentStatsCollector = concurrentStatsCollector; this.maxQueue = maxQueue; this.batchSize = batchSize; this._warnInterval = warnInterval; this._warnSize = warnSize; this._listenerTimeout = listenerTimeout; } @PostConstruct @SuppressWarnings("unused") private void initialize() { _eventQueue = new LinkedBlockingQueue<Zevent>((int) maxQueue); QueueProcessor p = new QueueProcessor(this, _eventQueue, (int) batchSize); _processorThread = new Thread(_threadGroup, p, "ZeventProcessor"); _processorThread.setDaemon(true); _processorThread.start(); DiagnosticObject myDiag = new DiagnosticObject() { public String getStatus() { return getDiagnostics(); } public String getShortStatus() { return getStatus(); } @Override public String toString() { return "ZEvent Subsystem"; } public String getName() { return "ZEvents"; } public String getShortName() { return "zevents"; } }; diagnosticsLogger.addDiagnosticObject(myDiag); concurrentStatsCollector.register(ConcurrentStatsCollector.ZEVENT_QUEUE_SIZE); concurrentStatsCollector.register(new StatCollector() { public long getVal() throws StatUnreachableException { return getTotalRegisteredBufferSize(); } public String getId() { return ConcurrentStatsCollector.ZEVENT_REGISTERED_BUFFER_SIZE; } }); } public long getQueueSize() { return _eventQueue.size(); } @PreDestroy public void shutdown() throws InterruptedException { while (!_eventQueue.isEmpty()) { System.out.println("Waiting for empty queue: " + _eventQueue.size()); Thread.sleep(1000); } _processorThread.interrupt(); _processorThread.join(5000); _threadGroup.interrupt() ; diagnosticsLogger = null ; this._globalListeners = null ; this._listeners = null ; this._eventQueue = null ; synchronized(this._registeredBuffers) { for (Entry<Queue<?>, TimingListenerWrapper<Zevent>> entry : _registeredBuffers.entrySet()) { entry.getKey().clear() ; }//EO while there are more buffers this._registeredBuffers = null ; }//EO sync block } public long getMaxTimeInQueue() { synchronized (INIT_LOCK) { return _maxTimeInQueue; } } public long getZeventsProcessed() { synchronized (INIT_LOCK) { return _numEvents; } } private long getWarnSize() { synchronized (INIT_LOCK) { return _warnSize; } } private long getListenerTimeout() { synchronized (INIT_LOCK) { return _listenerTimeout; } } private long getWarnInterval() { synchronized (INIT_LOCK) { return _warnInterval; } } private void assertClassIsZevent(Class<? extends Zevent> c) { if (!Zevent.class.isAssignableFrom(c)) { throw new IllegalArgumentException("[" + c.getName() + "] does not subclass [" + Zevent.class.getName() + "]"); } } /** * Registers a buffer with the internal list, so data about its contents can * be printed by the diagnostic thread. */ public void registerBuffer(Queue<?> q, ZeventListener<? extends Zevent> e) { synchronized (_registeredBuffers) { _registeredBuffers.put(q, new TimingListenerWrapper<Zevent>(e)); } } /** * Register an event class. These classes must be registered prior to * attempting to listen to individual event types. * * @param eventClass a subclass of {@link Zevent} * @return false if the eventClass was already registered */ public boolean registerEventClass(Class<? extends Zevent> eventClass) { assertClassIsZevent(eventClass); synchronized (_listenerLock) { if (_listeners.containsKey(eventClass)) return false; _listeners.put(eventClass, new LinkedList<TimingListenerWrapper<Zevent>>()); if (_log.isDebugEnabled()) { _log.debug("Register ZEvent " + eventClass); } return true; } } /** * Register a list of event classes. These classes must be registered prior to * attempting to listen to individual event types. * * @param eventClasses a list of event classes to register * @return false if an event class is already registered */ @Resource(name="preregisterZevents") public boolean registerEventClass(List<String> eventClasses) { if (_log.isDebugEnabled()) { _log.debug("Zevent classes to register: " + eventClasses); } boolean success = true; for (String className : eventClasses) { Class<? extends Zevent> clazz; className = className.trim(); if (className.length() == 0 || className.startsWith("#")) { continue; } try { clazz = (Class<? extends Zevent>) Class.forName(className); } catch (Exception e) { _log.warn("Unable to find Zevent class [" + className + "]", e); continue; } if (!registerEventClass(clazz)) { // return false if there is at least one failure success = false; } } return success; } /** * Unregister an event class * * @param eventClass subclass of {@link Zevent} * @return false if the eventClass was not registered */ public boolean unregisterEventClass(Class<? extends Zevent> eventClass) { assertClassIsZevent(eventClass); synchronized (_listenerLock) { return _listeners.remove(eventClass) != null; } } /** * Add an event listener which is called for every event type which comes * through the queue. * * @return false if the listener was already listening */ public boolean addGlobalListener(ZeventListener<? extends Zevent> listener) { synchronized (_listenerLock) { return _globalListeners.add(new TimingListenerWrapper<Zevent>(listener)); } } public boolean addBufferedGlobalListener(ZeventListener<? extends Zevent> listener) { ThreadGroupFactory threadFact = new ThreadGroupFactory(_threadGroup, listener.toString()); listener = new TimingListenerWrapper<Zevent>(listener); BufferedListener<Zevent> bListen = new BufferedListener(listener, threadFact); synchronized (_listenerLock) { return _globalListeners.add(new TimingListenerWrapper<Zevent>(bListen)); } } /** * Remove a global event listener * * @return false if the listener was not listening */ public boolean removeGlobalListener(ZeventListener<? extends Zevent> listener) { synchronized (_listenerLock) { return _globalListeners.remove(listener); } } private List<TimingListenerWrapper<Zevent>> getEventTypeListeners(Class<? extends Zevent> eventClass) { synchronized (_listenerLock) { List<TimingListenerWrapper<Zevent>> res = _listeners.get(eventClass); if (res == null) { // Register it registerEventClass(eventClass); return _listeners.get(eventClass); } return res; } } /** * Add a buffered listener for event types. A buffered listener is one which * implements its own private queue and thread for processing entries. If * the actions performed by your listener take a while to complete, then a * buffered listener is a good candidate for use, since it means that it * will not be holding up the regular Zevent queue processor. * * @param eventClasses {@link Class}es which subclass {@link Zevent} to * listen for * @param listener Listener to invoke with events * * @return the buffered listener. This return value must be used when trying * to remove the listener later on. */ public BufferedListener<Zevent> addBufferedListener(Set<Class<? extends Zevent>> eventClasses, ZeventListener<? extends Zevent> listener) { ThreadGroupFactory threadFact = new ThreadGroupFactory(_threadGroup, listener.toString()); listener = new TimingListenerWrapper<Zevent>(listener); BufferedListener<Zevent> bListen = new BufferedListener(listener, threadFact); for (Class<? extends Zevent> clazz : eventClasses) { addListener(clazz, bListen); } return bListen; } public BufferedListener<Zevent> addBufferedListener(Class<? extends Zevent> eventClass, ZeventListener<? extends Zevent> listener) { Set<Class<? extends Zevent>> s = new HashSet<Class<? extends Zevent>>(); s.add(eventClass); return addBufferedListener(s, listener); } /** * Add a listener for a specific type of event. * * @param eventClass A subclass of {@link Zevent} * @return false if the listener was already registered */ public boolean addListener(Class<? extends Zevent> eventClass, ZeventListener<? extends Zevent> listener) { assertClassIsZevent(eventClass); synchronized (_listenerLock) { List<TimingListenerWrapper<Zevent>> listeners = getEventTypeListeners(eventClass); if (listeners.contains(listener)) { return false; } listeners.add(new TimingListenerWrapper<Zevent>(listener)); return true; } } /** * Remove a specific event type listener. * @see #addListener(Class, ZeventListener) */ public boolean removeListener(Class<? extends Zevent> eventClass, ZeventListener<? extends Zevent> listener) { assertClassIsZevent(eventClass); synchronized (_listenerLock) { List<TimingListenerWrapper<Zevent>> listeners = getEventTypeListeners(eventClass); return listeners.remove(listener); } } /** * Enqueue events onto the event queue. This method will block if the thread * is full. * * @param events List of {@link Zevent}s * @throws InterruptedException if the queue was full and the thread was * interrupted */ public void enqueueEvents(List<? extends Zevent> events, long timeout) throws InterruptedException { if (_eventQueue.size() > getWarnSize() && (System.currentTimeMillis() - _lastWarnTime) > getWarnInterval()) { _lastWarnTime = System.currentTimeMillis(); _log.warn("Your event queue is having a hard time keeping up. " + "Get a faster CPU, or reduce the amount of events!"); } boolean debug = _log.isDebugEnabled(); for (Zevent e : events) { e.enterQueue(); boolean b = _eventQueue.offer(e, timeout, TimeUnit.SECONDS); if (debug) { _log.debug((b?"succeed":"failed") + " pushing " + e); } } concurrentStatsCollector.addStat(_eventQueue.size(), ConcurrentStatsCollector.ZEVENT_QUEUE_SIZE); } public void enqueueEvents(List<? extends Zevent> events) throws InterruptedException { enqueueEvents(events, DEFAULT_TIMEOUT); } public void enqueueEventAfterCommit(Zevent event, long timeout) { enqueueEventsAfterCommit(Collections.singletonList(event),timeout); } public void enqueueEventAfterCommit(Zevent event) { enqueueEventAfterCommit(event,DEFAULT_TIMEOUT); } public void enqueueEventsAfterCommit(List<? extends Zevent> inEvents) { enqueueEventsAfterCommit(inEvents, DEFAULT_TIMEOUT); } /** * Enqueue events if the current running transaction successfully commits. * @see #enqueueEvents(List) */ public void enqueueEventsAfterCommit(List<? extends Zevent> inEvents, final long timeout) { final List<Zevent> events = new ArrayList<Zevent>(inEvents); TransactionSynchronization txListener = new TransactionSynchronization() { public void afterCommit() { try { if (_log.isDebugEnabled()) { _log.debug("Listener[" + this.toString() + "] after tx. Enqueueing. "); } enqueueEvents(events, timeout); } catch (InterruptedException e) { _log.warn("Interrupted while enqueueing events: ",e); } catch (Exception e) { _log.error("Errorwhile enqueueing events: ",e); } } public void afterCompletion(int status) { } public void beforeCommit(boolean readOnly) { } public void beforeCompletion() { } public void flush() { } public void resume() { } public void suspend() { } }; if (_log.isDebugEnabled()) { _log.debug("Listener[" + txListener + "] Enqueueing events: " + inEvents); } TransactionSynchronizationManager.registerSynchronization(txListener); } public void enqueueEvent(Zevent event) throws InterruptedException { enqueueEvents(Collections.singletonList(event)); } /** * Wait until the queue is empty. This is a non-performant function, so * please only use it in test suites. */ public void waitUntilNoEvents() throws InterruptedException { while (_eventQueue.size() != 0) Thread.sleep(100); } /** * Returns the listeners which have specifically registered for the * specified event type. This method does not return global event listeners. * * If the event type was never registered, null will be returned, otherwise * a list of {@link ZeventListener}s (of potentially size=0) */ private List<TimingListenerWrapper<Zevent>> getTypeListeners(Zevent z) { synchronized (_listenerLock) { return _listeners.get(z.getClass()); } } /** * Internal method to dispatch events. Called by the {@link QueueProcessor}. * * The strategy used in this method creates mini-batches of events to send * to each listener. There is no defined order for listener execution. */ void dispatchEvents(List<? extends Zevent> events) { synchronized (INIT_LOCK) { for (Zevent z : events) { long timeInQueue = z.getQueueExitTime() - z.getQueueEntryTime(); if (timeInQueue > _maxTimeInQueue) _maxTimeInQueue = timeInQueue; _numEvents++; } } List<Zevent> validEvents = new ArrayList<Zevent>(events.size()); Map<ZeventListener<Zevent>, List<Zevent>> listenerBatches; synchronized (_listenerLock) { listenerBatches = new HashMap<ZeventListener<Zevent>, List<Zevent>>(_globalListeners.size()); for (Zevent z : events) { List<TimingListenerWrapper<Zevent>> typeListeners = getTypeListeners(z); if (typeListeners == null) { _log.warn("Unable to dispatch event of type [" + z.getClass().getName() + "]: Not registered"); continue; } validEvents.add(z); for (ZeventListener<Zevent> listener : typeListeners) { List<Zevent> batch = listenerBatches.get(listener); if (batch == null) { batch = new LinkedList<Zevent>(); listenerBatches.put(listener, batch); } batch.add(z); } } for (ZeventListener<Zevent> listener : _globalListeners) { listenerBatches.put(listener, validEvents); } } long timeout = getListenerTimeout(); for (Entry<ZeventListener<Zevent>, List<Zevent>> ent : listenerBatches.entrySet()) { ZeventListener<Zevent> listener = ent.getKey(); List<Zevent> batch = ent.getValue(); synchronized (_listenerLock) { InterruptToken t = null; try { t = threadWatchdog.interruptMeIn(timeout, TimeUnit.SECONDS, "Processing listener events"); listener.processEvents(Collections.unmodifiableList(batch)); } catch (RuntimeException e) { _log.warn("Exception while invoking listener [" + listener + "]", e); } finally { if (t != null) { threadWatchdog.cancelInterrupt(t); } } } } } private String getDiagnostics() { synchronized (INIT_LOCK) { StringBuffer res = new StringBuffer(); res.append("ZEvent Manager Diagnostics:\n").append( " Queue Size: " + _eventQueue.size() + "\n").append( " Events Handled: " + _numEvents + "\n").append( " Max Time In Queue: " + _maxTimeInQueue + "ms\n\n").append( "ZEvent Listener Diagnostics:\n"); PrintfFormat timingFmt = new PrintfFormat(" %-30s max=%-7.2f avg=%-5.2f " + "num=%-5d\n"); synchronized (_listenerLock) { for (Entry<Class<? extends Zevent>, List<TimingListenerWrapper<Zevent>>> ent : _listeners.entrySet()) { List<TimingListenerWrapper<Zevent>> listeners = ent.getValue(); res.append(" EventClass: " + ent.getKey() + "\n"); for (TimingListenerWrapper<Zevent> l : listeners) { Object[] args = new Object[] { l.toString(), new Double(l.getMaxTime()), new Double(l.getAverageTime()), new Long(l.getNumEvents()) }; res.append(timingFmt.sprintf(args)); } res.append("\n"); } res.append(" Global Listeners:\n"); for (TimingListenerWrapper<Zevent> l : _globalListeners) { Object[] args = new Object[] { l.toString(), new Double(l.getMaxTime()), new Double(l.getAverageTime()), new Long(l.getNumEvents()), }; res.append(timingFmt.sprintf(args)); } } synchronized (_registeredBuffers) { PrintfFormat fmt = new PrintfFormat(" %-30s size=%d\n"); res.append("\nZevent Registered Buffers:\n"); for (Entry<Queue<?>, TimingListenerWrapper<Zevent>> ent : _registeredBuffers.entrySet()) { Queue<?> q = ent.getKey(); TimingListenerWrapper<Zevent> targ = ent.getValue(); res.append(fmt.sprintf(new Object[] { targ.toString(), new Integer(q.size()), })); res.append(timingFmt.sprintf(new Object[] { "", // Target // already // printed // above new Double(targ.getMaxTime()), new Double(targ.getAverageTime()), new Long(targ.getNumEvents()), })); } } return res.toString(); } } private long getTotalRegisteredBufferSize() { synchronized (_registeredBuffers) { long rtn = 0; for (Entry<Queue<?>, TimingListenerWrapper<Zevent>> ent : _registeredBuffers.entrySet()) { rtn += ent.getKey().size(); } return rtn; } } public static ZeventEnqueuer getInstance() { return Bootstrap.getBean(ZeventEnqueuer.class); } }