/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.router;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import org.helios.apmrouter.collections.ConcurrentLongSlidingWindow;
import org.helios.apmrouter.collections.LongSlidingWindow;
import org.helios.apmrouter.destination.event.DestinationEvent;
import org.helios.apmrouter.metric.ExpandedMetric;
import org.helios.apmrouter.metric.ICEMetric;
import org.helios.apmrouter.metric.IMetric;
import org.helios.apmrouter.server.ServerComponentBean;
import org.helios.apmrouter.util.SystemClock;
import org.helios.apmrouter.util.SystemClock.ElapsedTime;
import org.helios.apmrouter.util.thread.ManagedThreadPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.support.MetricType;
/**
* <p>Title: PatternRouter</p>
* <p>Description: A pettern routing engine that distributes {@link Routable} instances to subscribers that advertise a pattern that matches the routing key supplied by the routables</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.router.PatternRouter</code></p>
* FIXME: This is supposed to be a generic class, it is temporarilly specific to IMetric.
* FIXME: Clean up the old/failed dedicated thread model
*/
public class PatternRouter extends ServerComponentBean implements UncaughtExceptionHandler, RejectedExecutionHandler {
/** The worker thread pool that reads the routing queue */
protected ExecutorService threadPool;
/** The routing queue size */
protected int routingQueueSize = 1000;
/** The routing queue fairness */
protected boolean routingQueueFairness = false;
/** The number of routing worker threads */
protected int routingWorkers = 5;
/** The routing queue */
protected BlockingQueue<IMetric> routingQueue = null;
/** The subscribers */
protected final Set<RouteDestination<IMetric>> destinations = new CopyOnWriteArraySet<RouteDestination<IMetric>>();
/** An uncaught exception handler applied to threads running in the router's thread pool */
protected final UncaughtExceptionHandler ucex = this;
/** Sliding windows of route elapsed times in ns. */
protected final LongSlidingWindow elapsedTimesNs = new ConcurrentLongSlidingWindow(15);
/** Sliding windows of route elapsed times in ms. */
protected final LongSlidingWindow elapsedTimesMs = new ConcurrentLongSlidingWindow(15);
/** Metric conflation service */
protected MetricConflationService conflator;
/**
* Injects the conflation service
* @param conflator the conflation service
*/
@Autowired(required=true)
public void setConflator(MetricConflationService conflator) {
this.conflator = conflator;
}
/**
* <p>Responds <code>true</code> for {@link DestinationEvent}s.
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponentBean#supportsEventType(java.lang.Class)
*/
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return (DestinationEvent.class.isAssignableFrom(eventType));
}
/**
* <p>Responds <code>true</code>.
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponentBean#supportsSourceType(java.lang.Class)
*/
@Override
public boolean supportsSourceType(Class<?> sourceType) {
return RouteDestination.class.isAssignableFrom(sourceType);
}
/**
* {@inheritDoc}
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
DestinationEvent de = (DestinationEvent)event;
RouteDestination<IMetric> rd = (RouteDestination<IMetric>) de.getSource();
if(de.isStarting()) {
info("Registering [", de.getBeanName(), "] as a Route Destination (evd ID:", de.getId(), ")");
destinations.add(rd);
} else {
info("Removing [", de.getBeanName(), "] as a Route Destination (evd ID:", de.getId(), ")");
destinations.remove(rd);
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponentBean#doStart()
*/
@Override
protected void doStart() throws Exception {
super.doStart();
((ManagedThreadPool)threadPool).setRejectedExecutionHandler(this);
routingQueue = new ArrayBlockingQueue<IMetric>(routingQueueSize, routingQueueFairness);
conflator.setRouter(this);
// for(int i = 0; i < routingWorkers; i++) {
// threadPool.execute(new MatchingWorker());
// }
}
// /**
// * @param initialDests
// */
// @Autowired(required=true)
// public void setDestinations(Map<String, RouteDestination> initialDests) {
// for(Map.Entry<String, RouteDestination> entry: initialDests.entrySet()) {
// destinations.add(entry.getValue());
// info("Registered [", entry.getKey(), "] as a Route Destination");
// }
// }
//RouteDestination
/**
* Returns the number of registered destinations
* @return the number of registered destinations
*/
@ManagedAttribute(description="The number of registered destinations")
public int getDestinationCount() {
return destinations.size();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponent#resetMetrics()
*/
@Override
public void resetMetrics() {
super.resetMetrics();
elapsedTimesNs.clear();
elapsedTimesMs.clear();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponentBean#doStop()
*/
@Override
protected void doStop() {
super.doStop();
}
/**
* <p>Title: MatchingWorker</p>
* <p>Description: Worker runnables for executing matches</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.router.PatternRouter.MatchingWorker</code></p>
* FIXME: what do we do when the interrupted exception is caught ?
*/
protected class MatchingWorker implements Runnable {
@Override
public void run() {
Thread.currentThread().setUncaughtExceptionHandler(ucex);
while(true) {
try {
IMetric metric = routingQueue.take();
if(metric.getType()==org.helios.apmrouter.metric.MetricType.BLOB) {
metric = new ExpandedMetric((ICEMetric)metric);
}
for(RouteDestination<IMetric> destination: destinations) {
destination.acceptRoute(metric);
}
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
}
}
}
void queue(IMetric...metrics) {
for(final IMetric metric: metrics) {
if(metric==null) continue;
this.threadPool.execute(new Runnable(){
public void run() {
try {
SystemClock.startTimer();
IMetric routableMetric = metric;
if(metric.getType()==org.helios.apmrouter.metric.MetricType.BLOB) {
routableMetric = new ExpandedMetric((ICEMetric)metric);
}
for(RouteDestination<IMetric> destination: destinations) {
destination.acceptRoute(routableMetric);
incr("CompletedRoutes");
}
ElapsedTime et = SystemClock.endTimer();
elapsedTimesNs.insert(et.elapsedNs);
elapsedTimesMs.insert(et.elapsedMs);
} catch (Throwable e) {
SystemClock.endTimer();
incr("DroppedRoutes");
e.printStackTrace(System.err);
}
}
});
}
}
public void route(Collection<IMetric> metrics) {
route(metrics.toArray(new IMetric[0]));
}
public void queue(Collection<IMetric> metrics) {
queue(metrics.toArray(new IMetric[0]));
}
/**
* Routes an array of routables to their pattern matched endpoints
* @param metrics The routables to route
*/
public void route(IMetric...metrics) {
//conflator.queue(metrics); // TODO: Only use conflator in actual destinations that need it.
queue(metrics);
}
// /**
// * Routes a collection of routables to their pattern matched endpoints
// * @param metrics The routables to route
// */
// public void route(Collection<IMetric> metrics) {
// for(IMetric metric: metrics) {
// if(metric==null) continue;
// if(routingQueue.offer(metric)) {
// incr("CompletedRoutes");
// } else {
// incr("DroppedRoutes");
// }
// }
// }
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponent#getSupportedMetricNames()
*/
@Override
public Set<String> getSupportedMetricNames() {
Set<String> metrics = new HashSet<String>(super.getSupportedMetricNames());
metrics.add("DroppedRoutes");
metrics.add("CompletedRoutes");
return metrics;
}
/**
* Returns the configured size of the routing queue
* @return the routingQueueSize
*/
@ManagedAttribute
public int getRoutingQueueSize() {
return routingQueueSize;
}
/**
* Returns the number of metrics in the routing queue
* @return the routingQueueSize
*/
@ManagedMetric(category="MetricRouter", metricType=MetricType.COUNTER, description="The number of metrics in the routing queue")
public long getRoutingQueueDepth() {
return routingQueue.size();
}
/**
* Returns the sliding average elapsed time in ns. of the last 15 route events
* @return the sliding average elapsed time in ns. of the last 15 route events
*/
@ManagedMetric(category="MetricRouter", metricType=MetricType.GAUGE, description="The sliding average elapsed time in ns. of the last 15 route events")
public long getAverageRouteTimeNs() {
return elapsedTimesNs.avg();
}
/**
* Returns the sliding average elapsed time in ms. of the last 15 route events
* @return the sliding average elapsed time in ms. of the last 15 route events
*/
@ManagedMetric(category="MetricRouter", metricType=MetricType.GAUGE, description="The sliding average elapsed time in ms. of the last 15 route events")
public long getAverageRouteTimeMs() {
return elapsedTimesMs.avg();
}
/**
* Returns the number of routed metrics
* @return the number of routed metrics
*/
@ManagedMetric(category="MetricRouter", metricType=MetricType.COUNTER, description="The number of metrics routed")
public long getRoutedMetricCount() {
return getMetricValue("CompletedRoutes");
}
/**
* Returns the number of metrics dropped in routing
* @return the number of metrics dropped in routing
*/
@ManagedMetric(category="MetricRouter", metricType=org.springframework.jmx.support.MetricType.COUNTER, description="The number of metrics dropped in routing")
public long getDroppedMetricCount() {
return getMetricValue("DroppedRoutes");
}
/**
* Sets
* @param routingQueueSize the routingQueueSize to set
*/
public void setRoutingQueueSize(int routingQueueSize) {
this.routingQueueSize = routingQueueSize;
}
/**
* Indicates if the routing queue is fair
* @return true if the routing queue is fair, false otherwise
*/
@ManagedAttribute
public boolean isRoutingQueueFair() {
return routingQueueFairness;
}
/**
* Indicates if the routing queue should be fair
* @param routingQueueFairness the routingQueueFairness to set
*/
public void setRoutingQueueFair(boolean routingQueueFairness) {
this.routingQueueFairness = routingQueueFairness;
}
/**
* Sets the routing thread pool
* @param threadPool the threadPool to set
*/
public void setThreadPool(ExecutorService threadPool) {
this.threadPool = threadPool;
}
/**
* Returns the number of routing worker threads
* @return the number of routing worker threads
*/
@ManagedAttribute
public int getRoutingWorkers() {
return routingWorkers;
}
/**
* Sets the number of routing worker threads
* @param routingWorkers the number of routing worker threads to set
*/
public void setRoutingWorkers(int routingWorkers) {
this.routingWorkers = routingWorkers;
}
/**
* {@inheritDoc}
* @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable)
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
warn("Uncaught exception [", t, "]", e);
}
/**
* {@inheritDoc}
* @see java.util.concurrent.RejectedExecutionHandler#rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)
*/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
warn("Pattern Router Rejected execution\n\tTask:", r.getClass().getName(), "\n\tWorker QueueDepth:" + executor.getQueue().size(), new Throwable());
incr("DroppedRoutes");
}
}