org.hyperic.hq.zevents.ZeventManager.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.zevents.ZeventManager.java

Source

/*
 * 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);
    }
}