/** * 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.trace; import org.helios.apmrouter.jmx.ThreadPoolFactory; import org.helios.apmrouter.metric.IMetric; import org.helios.apmrouter.sender.ISender; import org.helios.apmrouter.sender.SenderFactory; import org.helios.apmrouter.util.SystemClock; import java.lang.management.ManagementFactory; import java.util.Collection; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; /** * <p>Title: CollectionFunnel</p> * <p>Description: The drop-off point for tracers to drop their collected metrics.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.util.CollectionFunnel</code></p> */ public class CollectionFunnel implements RejectedExecutionHandler, MetricSubmitter { /** the singleton instance */ private static volatile CollectionFunnel instance = null; /** the singleton instance ctor lock */ private static final Object lock = new Object(); /** The timer thread */ private final Thread timerThread; /** The timer period in ms. */ private final long timerPeriod; /** The maximum size in bytes of a DMC before it is flushed */ private final int maxDmcBytes; /** The maximum number of metrics in a DMC before it is flushed */ private final int maxDmcMetrics; /** The size of the switch queue */ private final int switchQueueSize; /** The DCM switch that causes messages to be queued while the current DCM is being flushed */ private final AtomicBoolean switchToQueue = new AtomicBoolean(false); /** The number of metrics dropped while trying to queue */ private final AtomicLong dropped = new AtomicLong(0L); /** The number of metrics queued */ private final AtomicLong queued = new AtomicLong(0L); /** The number of metrics sent */ private final AtomicLong sent = new AtomicLong(0L); /** The send thread pool */ private final ThreadPoolExecutor executor; /** The sender, for synchronous sends */ private final ISender sender; /** The timestamp of the last flush */ private volatile long lastFlush; /** The current DCM */ private DirectMetricCollection dmc = null; /** The switch queue that metrics are written to while the dmc is being flushed */ private final BlockingQueue<IMetric[]> offLineQueue; /** * Returns the CollectionFunnel singleton * @return the CollectionFunnel singleton */ public static CollectionFunnel getInstance() { if(instance==null) { synchronized(lock) { if(instance==null) { instance = new CollectionFunnel(); } } } return instance; } /** * Sends a metric directly, bypassing the local buffer * @param metric The metric to send * @param timeout The period of time to wait for a confirm in ms. * @throws TimeoutException Thrown if the confirmation is not received in the specified time. */ @Override public void submitDirect(IMetric metric, long timeout) throws TimeoutException { if(metric!=null) { sender.send(metric, timeout); } } /** * Submits the passed metrics for a send to the apmrouter * @param metrics A collection of metrics to send */ @Override public void submit(Collection<IMetric> metrics) { if(metrics!=null && !metrics.isEmpty()) { submit(metrics.toArray(new IMetric[0])); } } /** * Submits the passed metrics for a send to the apmrouter * @param metrics An array of metrics to send */ @Override public void submit(IMetric...metrics) { if(metrics.length<1) return; if(switchToQueue.get()) { if(!offLineQueue.offer(metrics)) { dropped.addAndGet(metrics.length); } else { queued.addAndGet(metrics.length); } } else { synchronized(switchToQueue) { if(dmc.append(maxDmcBytes, maxDmcMetrics, metrics)) { flush(); } } } } /** * Resets the sent and dropped localStats */ @Override public void resetStats() { dropped.set(0L); sent.set(0L); queued.set(0); } /** * Timer flush */ protected void timerFlush() { if(SystemClock.elapsedMsSince(lastFlush) >= timerPeriod) { //System.out.println("============> TIMER FLUSH"); flush(); } } /** * Flushes the current DCM */ protected void flush() { if(switchToQueue.compareAndSet(false, true)) { try { DirectMetricCollection toSend = null; if(dmc.getMetricCount()>0) { toSend = dmc; dmc = DirectMetricCollection.newDirectMetricCollection(); switchToQueue.set(false); sendDcm(toSend); } drainQueue(); } finally { lastFlush = SystemClock.time(); switchToQueue.compareAndSet(true, false); } } } /** * Drains the offline queue once the main DCM comes back online after a flush. */ protected void drainQueue() { if(!offLineQueue.isEmpty()) { IMetric[] metrics = null; DirectMetricCollection toSend = DirectMetricCollection.newDirectMetricCollection(); while((metrics=offLineQueue.poll())!=null) { if(toSend.append(maxDmcBytes, maxDmcMetrics, metrics)) { sendDcm(toSend); toSend = DirectMetricCollection.newDirectMetricCollection(); } } } } /** * Sends the DCM * @param dcmToSend the DCM to send */ protected void sendDcm(final DirectMetricCollection dcmToSend) { if(dcmToSend==null) { System.err.println("Null DCM to send"); new Throwable().printStackTrace(System.err); return; } sent.addAndGet(dcmToSend.getMetricCount()); executor.execute(dcmToSend); } // /** // * Sends the DMC in the current thread // * @param dcmToSend The DMC to send // * @param timeout The timeout on this send in ms. // */ // protected void sendDcmInCurrentThread(final DirectMetricCollection dcmToSend, long timeout) { // sent.addAndGet(dcmToSend.getMetricCount()); // sender.send(dcmToSend, timeout); // } /** * Returns the total number of metrics dropped * @return the total number of metrics dropped */ @Override public long getDroppedMetrics() { return dropped.get(); } /** * Returns the total number of metrics sent * @return the total number of metrics sent */ @Override public long getSentMetrics() { return sent.get(); } /** * Returns the total number of metrics queued * @return the total number of metrics queued */ public long getQueued() { return queued.get(); } /** * Returns the timer flush period in ms. * @return the timer flush period */ public long getTimerPeriod() { return timerPeriod; } private CollectionFunnel() { timerPeriod = 3000; maxDmcBytes = 10240 * 10; maxDmcMetrics = 100 * 100; switchQueueSize = 1000; executor = new ThreadPoolFactory( ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors()/2, ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(), 60000, 100000, false, this, true, getClass().getPackage().getName(), "CollectionFunnel" ); sender = SenderFactory.getInstance().getDefaultSender(); executor.allowCoreThreadTimeOut(false); offLineQueue = new ArrayBlockingQueue<IMetric[]>(switchQueueSize, false); timerThread = new Thread("CollectionFunnelTimer") { @Override public void run() { while(true) { try { SystemClock.sleep(timerPeriod); timerFlush(); } catch (Exception e) {} } } }; timerThread.setPriority(Thread.MAX_PRIORITY); timerThread.setDaemon(true); timerThread.start(); dmc = DirectMetricCollection.newDirectMetricCollection(); } /** * {@inheritDoc} * @see java.util.concurrent.RejectedExecutionHandler#rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) */ @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { if(r!=null && (r instanceof DirectMetricCollection)) { long dr = dropped.addAndGet(((DirectMetricCollection)r).getMetricCount()); System.err.println("Execution Dropped Count:" + dr + " Queue Depth:" + executor.getQueue().size()); } } public int getCorePoolSize() { return executor.getCorePoolSize(); } public int getMaximumPoolSize() { return executor.getMaximumPoolSize(); } public int getPoolSize() { return executor.getPoolSize(); } public int getActiveCount() { return executor.getActiveCount(); } public int getLargestPoolSize() { return executor.getLargestPoolSize(); } public long getTaskCount() { return executor.getTaskCount(); } public long getCompletedTaskCount() { return executor.getCompletedTaskCount(); } public String status() { return String .format("CollectionFunnel Status[\n\tDropped=%s \n\tSent=%s, \n\tQueued=%s \n\tTimerPeriod=%s \n\tCorePoolSize=%s \n\tMaximumPoolSize=%s \n\tPoolSize=%s \n\tActiveCount=%s \n\tLargestPoolSize=%s \n\tTaskCount=%s \n\tCompletedTaskCount=%s\n]", getDroppedMetrics(), getSentMetrics(), getQueued(), getTimerPeriod(), getCorePoolSize(), getMaximumPoolSize(), getPoolSize(), getActiveCount(), getLargestPoolSize(), getTaskCount(), getCompletedTaskCount()); } /** * {@inheritDoc} * @see org.helios.apmrouter.trace.MetricSubmitter#getQueuedMetrics() */ @Override public long getQueuedMetrics() { try { return dmc.getMetricCount(); } catch (Exception e) { return 0; } } }