/** * 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.seriesly; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.helios.apmrouter.destination.accumulator.MetricAccumulator; import org.helios.apmrouter.destination.accumulator.MetricFlushReceiver; import org.helios.apmrouter.destination.netty.NettyTCPDestination; import org.helios.apmrouter.metric.IMetric; import org.helios.apmrouter.metric.JSONFormatterImpl; import org.helios.apmrouter.util.SystemClock; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBufferFactory; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.DirectChannelBufferFactory; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.Channels; import org.jboss.netty.handler.codec.http.DefaultHttpRequest; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpVersion; import org.jboss.netty.handler.logging.LoggingHandler; import org.jboss.netty.logging.InternalLogLevel; import org.springframework.jmx.export.annotation.ManagedAttribute; /** * <p>Title: SerieslyDestination</p> * <p>Description: Metric destination for the <a href="https://github.com/dustin/seriesly">Seriesly</a> time series database.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.destination.seriesly.SerieslyDestination</code></p> * NOTE: This query worked ! * http://localhost:3133/helios/_query?from=2012&to=2013&group=3600000&ptr=/data/localhost_eggs-cellent_platform=os_resource=cpu_cpu=all:Sys/value&reducer=avg */ public class SerieslyDestination extends NettyTCPDestination implements MetricFlushReceiver { /** The JSON formatter */ protected JSONFormatterImpl jsonFormatter = new JSONFormatterImpl(true, false); /** THe metric accumulator */ protected MetricAccumulator accumulator; /** The seriesly db name */ protected String dbName = "helios"; //protected String serieslyUrl = uriPrefix "http://localhost:3133/helios"; /** The time based flush trigger in ms. */ protected long timeTrigger = 15000; /** The size based flush trigger in number of metrics accumulated */ protected int sizeTrigger = 100; /** Indicates if the db has been created */ protected final AtomicBoolean dbCreated = new AtomicBoolean(false); /** The URI prefix for the seriesly server data submission endpoint */ protected String uriPrefixTemplate = null; /** The content channel buffer factory */ protected static final ChannelBufferFactory channelBufferFactory = new DirectChannelBufferFactory(); /** JSON Opener Channel Buffer Constant */ protected static final ChannelBuffer JSON_OPEN = ChannelBuffers.wrappedBuffer("{".getBytes()); /** JSON Closer Channel Buffer Constant */ protected static final ChannelBuffer JSON_CLOSE = ChannelBuffers.wrappedBuffer("}".getBytes()); /** * Creates a new SerieslyDestination * @param patterns The metric type patterns accepted by this detination */ public SerieslyDestination(String... patterns) { super(patterns); } /** * Creates a new SerieslyDestination * @param patterns The metric type patterns accepted by this detination */ public SerieslyDestination(Collection<String> patterns) { super(patterns); } /** * Creates a new SerieslyDestination */ public SerieslyDestination() { } /** * Creates the seriesly DB * @return true if the DB was created or verified successfully, false otherwise */ protected synchronized boolean createDb() { info("Validating Seriesly DB [", dbName, "]"); HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, String.format("http://%s:%s/%s", host, port, dbName)); request.setHeader(HttpHeaders.Names.HOST, host); request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE); ChannelFutureListener fl = new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) throws Exception { if(f.isSuccess()) { dbCreated.set(true); info("Validated Seriesly DB [", dbName, "]"); } else { dbCreated.set(false); error("Failed to validate Seriesly DB [", dbName, "]", f.getCause()); Throwable t = f.getCause().getCause(); if(t!=null) { t.printStackTrace(System.err); } } } }; ChannelFuture cf = channel.write(request); cf.addListener(fl); if(!cf.awaitUninterruptibly(1000)) { return false; } return dbCreated.get(); } /** * <p>Creates the seriesly URI prefix. * {@inheritDoc} * @see org.helios.apmrouter.destination.netty.NettyDestination#doStart() */ @Override protected void doStart() throws Exception { super.doStart(); accumulator = new MetricAccumulator(this, sizeTrigger, sizeTrigger, timeTrigger, TimeUnit.MILLISECONDS); uriPrefixTemplate = String.format("http://%s:%s/%s?ts=%s", host, port, dbName, "%s"); createDb(); } // /** // * {@inheritDoc} // * @see org.jboss.netty.channel.ChannelPipelineFactory#getPipeline() // */ // @Override // public ChannelPipeline getPipeline() throws Exception { // ChannelPipeline pipeline = Channels.pipeline(); // pipeline.addLast("LOG", new LoggingHandler("SerieslyLogging", InternalLogLevel.INFO, false)); // LinkedHashMap<String, ChannelHandler> tmpHandlers = null; // synchronized(resolvedHandlers) { // tmpHandlers = new LinkedHashMap<String, ChannelHandler>(resolvedHandlers); // } // for(Map.Entry<String, ChannelHandler> entry: tmpHandlers.entrySet()) { // pipeline.addLast(entry.getKey(), entry.getValue()); // } // return pipeline; // } /** * Accept Route additive for BaseDestination extensions * @param routable The metric to route */ @Override protected void doAcceptRoute(IMetric routable) { try { accumulator.append(routable); } catch (Exception e) { incr("MetricsForwardFailures"); } } /** * {@inheritDoc} * @see org.helios.apmrouter.destination.accumulator.MetricFlushReceiver#flush(java.util.Collection, int) */ @Override public void flush(Collection<IMetric> metrics, final int metricCount) { //info("Flushing [", metricCount, "] metrics"); if(metricCount < 1) return; if(!dbCreated.get()) { if(!createDb()) { incr("MetricsDropped", metricCount); return; } } byte[] content = null; try { content = jsonFormatter.toJSONBytes(metrics.toArray(new IMetric[0])); } catch (Exception e) { incr("MetricsForwardFailures", metricCount); error("JSON Formatting Error", e); } String uri = String.format(uriPrefixTemplate, SystemClock.time()); final HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri); request.setHeader(HttpHeaders.Names.HOST, host); request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE); request.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/json;charset=UTF-8"); request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, content.length); //request.setContent(ChannelBuffers.copiedBuffer(JSON_OPEN, metricText, JSON_CLOSE)); request.setContent(ChannelBuffers.wrappedBuffer(content)); content = null; //request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP); ((ClientBootstrap)bstrap).connect(socketAddress).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) throws Exception { if(!f.isSuccess()) { error("Failed to forward [", metricCount, "] metrics"); incr("MetricsForwardFailures", metricCount); } else { f.getChannel().write(request).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) throws Exception { if(f.isSuccess()) { debug("Successfully Forwarded [", metricCount, "] metrics"); incr("MetricsForwarded", metricCount); } else { error("Failed to forward [", metricCount, "] metrics"); incr("MetricsForwardFailures", metricCount); f.getCause().printStackTrace(System.err); Throwable t = f.getCause().getCause(); if(t!=null) { t.printStackTrace(System.err); } } } }); } } }); } // OutputStream output = null; // HttpURLConnection connection = null; // try { // connection = (HttpURLConnection)new URL(serieslyUrl + "?ts=" + SystemClock.time()).openConnection(); // //connection.setDoOutput(true); // Triggers POST. // connection.setRequestMethod("POST"); // connection.setRequestProperty("Accept-Charset", "UTF-8"); // connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); // connection.setDoOutput(true); // output = connection.getOutputStream(); // output.write(jsonFormatter.toJSONBytes(routable)); // output.flush(); // incr("MetricsForwarded"); // } catch (Exception e) { // incr("MetricsForwardFailures"); // } finally { // try { output.close(); } catch (Exception e) {} // } /** * Sets the json formatter that will build the json documents to store in Seriesly * @param jsonFormatter the jsonFormatter to set */ public void setJsonFormatter(JSONFormatterImpl jsonFormatter) { this.jsonFormatter = jsonFormatter; } /** * Returns the time based flush trigger in ms. * @return the time based flush trigger */ @ManagedAttribute(description="The time based flush trigger in ms.") public long getTimeTrigger() { return timeTrigger; } /** * Sets the time based flush trigger * @param timeTrigger the frequency that the buffer is flushed in ms. */ public void setTimeTrigger(long timeTrigger) { this.timeTrigger = timeTrigger; } /** * Returns the size based flush trigger * @return the size based flush trigger */ @ManagedAttribute(description="The metric size based flush trigger") public int getSizeTrigger() { return sizeTrigger; } /** * Sets the size based flush trigger * @param sizeTrigger the number of metrics to accumulate before they are flushed */ public void setSizeTrigger(int sizeTrigger) { this.sizeTrigger = sizeTrigger; } /** * Returns the name of the seriesly database * @return the name of the seriesly database */ @ManagedAttribute(description="The name of the seriesly database") public String getDbName() { return dbName; } /** * Sets the name of the seriesly database * @param dbName the name of the seriesly database */ public void setDbName(String dbName) { this.dbName = dbName; } }