/** * 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.destination.accumulator; import java.io.IOException; import java.io.OutputStream; import java.util.Date; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.helios.apmrouter.jmx.ScheduledThreadPoolFactory; import org.helios.apmrouter.metric.IMetric; import org.helios.apmrouter.util.SystemClock; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBufferOutputStream; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.DirectChannelBufferFactory; /** * <p>Title: MetricTextAccumulator</p> * <p>Description: Accumulates {@link IMetric}s as byte text in preparation for a metric count or time based flush.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.destination.MetricTextAccumulator</code></p> */ public class MetricTextAccumulator implements Runnable { /** The scheduler shared amongst all monitor instances */ protected static final ScheduledThreadPoolExecutor scheduler = ScheduledThreadPoolFactory.newScheduler("MetricAccumulator"); /** The channel buffer factory */ protected static final DirectChannelBufferFactory bufferFactory = new DirectChannelBufferFactory(); /** The accumulation buffer */ protected final ChannelBuffer accum; /** The outputstream to write meteric test bytes into the accum buffer */ protected final OutputStream os; /** The number of accumulated metrics */ protected final AtomicInteger metricCount = new AtomicInteger(0); /** The metric formatter */ protected final MetricTextFormatter formatter; /** The flush receiver that will receive the metric text bytes buffer when a flush is triggered */ protected final MetricTextFlushReceiver receiver; /** The last flush event timestamp */ protected final AtomicLong lastFlush = new AtomicLong(0L); /** The metric count size flush trigger */ protected final int sizeTrigger; /** The elapsed time flush trigger in ms. */ protected final long timeTrigger; /** Indicates if a flush is in progress */ protected final AtomicBoolean flushInProgress = new AtomicBoolean(false); /** The timed flush schedule handle */ protected ScheduledFuture<?> scheduleHandle = null; /** * Creates a new MetricTextAccumulator * @param formatter The metric formatter * @param receiver The flush receiver that will receive the metric text bytes buffer when a flush is triggered * @param bufferSize The initial buffer size (in bytes) for the accumulation buffer * @param sizeTrigger The number of accumulated metrics that will trigger a flush * @param timeTrigger The elapsed time that will trigger a flush * @param unit The unit of the time trigger */ public MetricTextAccumulator(MetricTextFormatter formatter, MetricTextFlushReceiver receiver, int bufferSize, int sizeTrigger, long timeTrigger, TimeUnit unit) { accum = ChannelBuffers.dynamicBuffer(bufferSize, bufferFactory); os = new ChannelBufferOutputStream(accum); this.formatter = formatter; this.receiver = receiver; this.sizeTrigger = sizeTrigger; this.timeTrigger = TimeUnit.MILLISECONDS.convert(timeTrigger, unit); scheduleHandle = scheduler.scheduleAtFixedRate(this, this.timeTrigger, this.timeTrigger, TimeUnit.MILLISECONDS); } /** * Stops this accumulator and releases all its resources */ public void shutdown() { if(!scheduleHandle.isCancelled()) { scheduleHandle.cancel(true); } accum.clear(); try { os.close(); } catch (Exception e) {} } /** * {@inheritDoc} * @see java.lang.Runnable#run() */ @Override public void run() { if(SystemClock.elapsedMsSince(lastFlush.get()) >= timeTrigger) { flush(); } } /** * Appends the passed {@link IMetric}s to the accumulation buffer in the configured metric format * @param metrics The metrics to accumulate * @throws IOException thrown on any error writing to the output stream */ public void append(IMetric...metrics) throws IOException { if(scheduleHandle.isCancelled()) { throw new IllegalStateException("This MetricTextAccumulator has been shutdown", new Throwable()); } if(metrics!=null && metrics.length>0) { int newCount = metricCount.addAndGet(formatter.format(os, metrics)); if(newCount >= sizeTrigger) { flush(); } } } /** * Returns the number of accumulated metrics * @return the number of accumulated metrics */ public int size() { return metricCount.get(); } /** * Copies the accumulated buffer into a new buffer, flushes the copy, clears the accumulated buffer and returns the copy */ public void flush() { if(flushInProgress.compareAndSet(false, true)) { try { ChannelBuffer toSend = ChannelBuffers.copiedBuffer(accum); accum.clear(); int mc = metricCount.getAndSet(0); lastFlush.set(SystemClock.time()); receiver.flush(toSend, mc); } finally { flushInProgress.set(false); } } } /** * Returns the timestamp of the last flush * @return the timestamp of the last flush */ public long getLastFlushTimestamp() { return lastFlush.get(); } /** * Returns the date of the last flush * @return the date of the last flush */ public Date getLastFlushDate() { return new Date(lastFlush.get()); } /** * Returns the configured size trigger for this accumulator * @return the configured size trigger for this accumulator */ public int getSizeTrigger() { return sizeTrigger; } /** * Returns the configured time trigger in ms. for this accumulator * @return the configured time trigger in ms. for this accumulator */ public long getTimeTrigger() { return timeTrigger; } /** * Indicates if there is a flush in progress * @return true if there is a flush in progress, false otherwise */ public boolean getFlushInProgress() { return flushInProgress.get(); } }