You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2014/11/22 16:40:35 UTC

svn commit: r1641083 - in /jmeter/trunk: src/components/org/apache/jmeter/visualizers/backend/graphite/ xdocs/

Author: pmouawad
Date: Sat Nov 22 15:40:35 2014
New Revision: 1641083

URL: http://svn.apache.org/r1641083
Log:
Bug 57246 - BackendListener : Create a Graphite implementation
Bugzilla Id: 57246

Added:
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java   (with props)
    jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java   (with props)
Modified:
    jmeter/trunk/xdocs/changes.xml

Added: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java?rev=1641083&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java Sat Nov 22 15:40:35 2014
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.visualizers.backend.graphite;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
+
+/**
+ * Base class for {@link GraphiteMetricsSender}
+ * @since 2.13
+ */
+abstract class AbstractGraphiteMetricsSender implements GraphiteMetricsSender {
+
+    /**
+     * @return GenericKeyedObjectPool
+     * 
+     */
+    protected GenericKeyedObjectPool<SocketConnectionInfos, SocketOutputStream> createSocketOutputStreamPool() {
+        GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
+        config.setTestOnBorrow(true);
+        config.setTestWhileIdle(true);
+        config.setMaxTotalPerKey(-1);
+        config.setMaxTotal(-1);
+        config.setMaxIdlePerKey(-1);
+        config.setMinEvictableIdleTimeMillis(TimeUnit.MINUTES.toMillis(3));
+        config.setTimeBetweenEvictionRunsMillis(TimeUnit.MINUTES.toMillis(3));
+
+        return new GenericKeyedObjectPool<SocketConnectionInfos, SocketOutputStream>(
+                new SocketOutputStreamPoolFactory(SOCKET_CONNECT_TIMEOUT_MS, SOCKET_TIMEOUT), config);
+    }
+    
+    /**
+     * Replaces Graphite reserved chars:
+     * <ul>
+     * <li>' ' by '-'</li>
+     * <li>'\\' by '-'</li>
+     * <li>'.' by '_'</li>
+     * </ul>
+     * @param s
+     * @return
+     */
+    static final String sanitizeString(String s) {
+        // String#replace uses regexp
+        return StringUtils.replaceChars(s, "\\ .", "--_");
+    }    
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/AbstractGraphiteMetricsSender.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java?rev=1641083&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java Sat Nov 22 15:40:35 2014
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.visualizers.backend.graphite;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.jmeter.config.Arguments;
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jmeter.threads.JMeterContextService;
+import org.apache.jmeter.threads.JMeterContextService.ThreadCounts;
+import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient;
+import org.apache.jmeter.visualizers.backend.BackendListenerContext;
+import org.apache.jmeter.visualizers.backend.SamplerMetric;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+
+/**
+ * Graphite based Listener using Pickle Protocol
+ * @see http://graphite.readthedocs.org/en/latest/overview.html
+ * @since 2.13
+ */
+public class GraphiteBackendListenerClient extends AbstractBackendListenerClient implements Runnable {
+    private static final int DEFAULT_PICKLE_PORT = 2004;
+    private static final String CUMULATED_CONTEXT_NAME = "cumulated";
+
+    private static final Logger LOGGER = LoggingManager.getLoggerForClass();
+    private static final String DEFAULT_METRICS_PREFIX = "jmeter."; //$NON-NLS-1$
+    private static final String CUMULATED_METRICS = "__cumulated__"; //$NON-NLS-1$
+    private static final String METRIC_ACTIVE_THREADS = "activeThreads"; //$NON-NLS-1$
+    private static final String METRIC_STARTED_THREADS = "startedThreads"; //$NON-NLS-1$
+    private static final String METRIC_STOPPED_THREADS = "stoppedThreads"; //$NON-NLS-1$
+    private static final String METRIC_FAILED_REQUESTS = "failure"; //$NON-NLS-1$
+    private static final String METRIC_SUCCESSFUL_REQUESTS = "success"; //$NON-NLS-1$
+    private static final String METRIC_TOTAL_REQUESTS = "total"; //$NON-NLS-1$
+    private static final String METRIC_MIN_RESPONSE_TIME = "min"; //$NON-NLS-1$
+    private static final String METRIC_MAX_RESPONSE_TIME = "max"; //$NON-NLS-1$
+    private static final String METRIC_PERCENTILE90_RESPONSE_TIME = "percentile90"; //$NON-NLS-1$
+    private static final String METRIC_PERCENTILE95_RESPONSE_TIME = "percentile95"; //$NON-NLS-1$
+    private static final long ONE_SECOND = 1L;
+    private static final int MAX_POOL_SIZE = 1;
+
+    private String graphiteHost;
+    private int graphitePort;
+    private boolean summaryOnly;
+    private String rootMetricsPrefix;
+    private String samplersList = ""; //$NON-NLS-1$
+    private transient Set<String> samplersToFilter;
+    
+
+    private GraphiteMetricsSender pickleMetricsManager;
+
+    private ScheduledExecutorService scheduler;
+    
+    public GraphiteBackendListenerClient() {
+        super();
+    }    
+
+    @Override
+    public void run() {
+        // Need to convert millis to seconds for Graphite
+        long timestamp = TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+        for (Map.Entry<String, SamplerMetric> entry : getMetricsPerSampler().entrySet()) {
+            SamplerMetric metric = entry.getValue();
+            if(entry.getKey().equals(CUMULATED_METRICS)) {
+                addMetrics(timestamp, CUMULATED_CONTEXT_NAME, metric);
+            } else {
+                addMetrics(timestamp, AbstractGraphiteMetricsSender.sanitizeString(entry.getKey()), metric);                
+            }
+            // We are computing on interval basis so cleanup
+            metric.resetForTimeInterval();
+        }
+        
+        ThreadCounts tc = JMeterContextService.getThreadCounts();
+        pickleMetricsManager.addMetric(timestamp, CUMULATED_CONTEXT_NAME, METRIC_ACTIVE_THREADS, Integer.toString(tc.activeThreads));
+        pickleMetricsManager.addMetric(timestamp, CUMULATED_CONTEXT_NAME, METRIC_STARTED_THREADS, Integer.toString(tc.startedThreads));
+        pickleMetricsManager.addMetric(timestamp, CUMULATED_CONTEXT_NAME, METRIC_STOPPED_THREADS, Integer.toString(tc.finishedThreads));
+
+        pickleMetricsManager.writeAndSendMetrics();
+    }
+
+
+    /**
+     * @param timestamp
+     * @param contextName
+     * @param metric
+     */
+    private void addMetrics(long timestamp, String contextName, SamplerMetric metric) {
+        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_FAILED_REQUESTS, Integer.toString(metric.getFailure()));
+        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_SUCCESSFUL_REQUESTS, Integer.toString(metric.getSuccess()));
+        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_TOTAL_REQUESTS, Integer.toString(metric.getTotal()));
+        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_MIN_RESPONSE_TIME, Long.toString(metric.getMinTime()));
+        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_MAX_RESPONSE_TIME, Long.toString(metric.getMaxTime()));
+        // TODO Make this customizable
+        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_PERCENTILE90_RESPONSE_TIME, Double.toString(metric.getPercentile(90)));
+        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_PERCENTILE95_RESPONSE_TIME, Double.toString(metric.getPercentile(95)));
+    }
+
+    /**
+     * @return the samplersList
+     */
+    public String getSamplersList() {
+        return samplersList;
+    }
+
+    /**
+     * @param samplersList the samplersList to set
+     */
+    public void setSamplersList(String samplersList) {
+        this.samplersList = samplersList;
+    }
+
+    @Override
+    public void handleSampleResults(List<SampleResult> sampleResults,
+            BackendListenerContext context) {
+        for (SampleResult sampleResult : sampleResults) {
+            if(!summaryOnly && samplersToFilter.contains(sampleResult.getSampleLabel())) {
+                SamplerMetric samplerMetric = getSamplerMetric(sampleResult.getSampleLabel());
+                samplerMetric.add(sampleResult);
+            }
+            SamplerMetric cumulatedMetrics = getSamplerMetric(CUMULATED_METRICS);
+            cumulatedMetrics.add(sampleResult);                    
+        }
+    }
+
+    @Override
+    public void setupTest(BackendListenerContext context) throws Exception {
+        String graphiteMetricsSenderClass = context.getParameter("graphiteMetricsSender");
+        
+        graphiteHost = context.getParameter("graphiteHost");
+        graphitePort = context.getIntParameter("graphitePort", DEFAULT_PICKLE_PORT);
+        summaryOnly = context.getBooleanParameter("summaryOnly", true);
+        samplersList = context.getParameter("samplersList", "");
+        rootMetricsPrefix = context.getParameter("rootMetricsPrefix", DEFAULT_METRICS_PREFIX);
+        Class clazz = Class.forName(graphiteMetricsSenderClass);
+        this.pickleMetricsManager = (GraphiteMetricsSender) clazz.newInstance();
+        pickleMetricsManager.setup(graphiteHost, graphitePort, rootMetricsPrefix);
+        String[] samplers = samplersList.split(",");
+        samplersToFilter = new HashSet<String>();
+        for (String samplerName : samplers) {
+            samplersToFilter.add(samplerName);
+        }
+        scheduler = Executors.newScheduledThreadPool(MAX_POOL_SIZE);
+        // Don't change this as metrics are per second
+        scheduler.scheduleAtFixedRate(this, ONE_SECOND, ONE_SECOND, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void teardownTest(BackendListenerContext context) throws Exception {
+        scheduler.shutdown();
+        try {
+            scheduler.awaitTermination(30, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            LOGGER.error("Error waiting for end of scheduler");
+        }
+        
+        samplersToFilter.clear();
+        pickleMetricsManager.destroy();
+        super.teardownTest(context);
+    }
+
+    @Override
+    public Arguments getDefaultParameters() {
+        Arguments arguments = new Arguments();
+        arguments.addArgument("graphiteMetricsSender", TextGraphiteMetricsSender.class.getName());
+        arguments.addArgument("graphiteHost", "");
+        arguments.addArgument("graphitePort", Integer.toString(DEFAULT_PICKLE_PORT));
+        arguments.addArgument("rootMetricsPrefix", DEFAULT_METRICS_PREFIX);
+        arguments.addArgument("summaryOnly", "true");
+        arguments.addArgument("samplersList", "");
+        return arguments;
+    }
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteBackendListenerClient.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java?rev=1641083&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java Sat Nov 22 15:40:35 2014
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.visualizers.backend.graphite;
+
+/**
+ * @since 2.13
+ */
+interface GraphiteMetricsSender {
+    final int SOCKET_CONNECT_TIMEOUT_MS = 1000;
+    final int SOCKET_TIMEOUT = 1000;
+
+
+    String CHARSET_NAME = "UTF-8"; //$NON-NLS-1$
+
+    final class MetricTuple {
+        String name;
+        long timestamp;
+        String value;
+        MetricTuple(String name, long timestamp, String value) {
+            this.name = name;
+            this.timestamp = timestamp;
+            this.value = value;
+        }
+    }
+    /**
+     * Convert the metric to a python tuple of the form:
+     *      (timestamp, (prefix.contextName.metricName, metricValue))
+     * And add it to the list of metrics. 
+     * @param timestamp in Seconds from 1970
+     * @param contextName
+     * @param metricName
+     * @param metricValue
+     */
+    public abstract void addMetric(long timestamp, String contextName,
+            String metricName, String metricValue);
+
+    /**
+     * 
+     * @param graphiteHost Host
+     * @param graphitePort Port
+     * @param prefix Root Data prefix
+     */
+    public void setup(String graphiteHost, int graphitePort, String prefix);
+    
+    /**
+     * Write metrics to Graphite using custom format
+     */
+    public abstract void writeAndSendMetrics();
+
+    /**
+     * Destroy sender
+     */
+    public abstract void destroy();
+
+}
\ No newline at end of file

Propchange: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/GraphiteMetricsSender.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java?rev=1641083&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java Sat Nov 22 15:40:35 2014
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.visualizers.backend.graphite;
+
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+
+/**
+ * Pickle Graphite format 
+ * Partly based on https://github.com/BrightcoveOS/metrics-graphite-pickle/blob/master/src/main/java/com/brightcove/metrics/reporting/GraphitePickleReporter.java 
+ * as per license https://github.com/BrightcoveOS/metrics-graphite-pickle/blob/master/LICENSE.txt
+ * @since 2.13
+ */
+class PickleGraphiteMetricsSender extends AbstractGraphiteMetricsSender {
+    private static final Logger LOG = LoggingManager.getLoggerForClass();    
+
+    /**
+     * Pickle opcodes needed for implementation
+     */
+    private static final char APPEND = 'a';
+    private static final char LIST = 'l';
+    private static final char LONG = 'L';
+    private static final char MARK = '(';
+    private static final char STOP = '.';
+    private static final char STRING = 'S';
+    private static final char TUPLE = 't';
+    private static final char QUOTE = '\'';
+    private static final char LF = '\n';
+        
+    private String prefix;
+
+    // graphite expects a python-pickled list of nested tuples.
+    private List<MetricTuple> metrics = new LinkedList<MetricTuple>();
+
+    private GenericKeyedObjectPool<SocketConnectionInfos, SocketOutputStream> socketOutputStreamPool;
+
+    private SocketConnectionInfos socketConnectionInfos;
+
+
+    PickleGraphiteMetricsSender() {
+        super();
+    }
+    
+    /**
+     * @param graphiteHost Graphite Host
+     * @param graphitePort Graphite Port
+     * @param prefix Common Metrics prefix
+     */
+    @Override
+    public void setup(String graphiteHost, int graphitePort, String prefix) {
+        this.prefix = prefix;
+        this.socketConnectionInfos = new SocketConnectionInfos(graphiteHost, graphitePort);
+        this.socketOutputStreamPool = createSocketOutputStreamPool();
+
+        if(LOG.isInfoEnabled()) {
+            LOG.info("Created PickleGraphiteMetricsSender with host:"+graphiteHost+", port:"+graphitePort+", prefix:"+prefix);
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#addMetric(long, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void addMetric(long timestamp, String contextName, String metricName, String metricValue) {
+        StringBuilder sb = new StringBuilder(50);
+        sb
+            .append(prefix)
+            .append(contextName)
+            .append(".")
+            .append(metricName);
+        metrics.add(new MetricTuple(sb.toString(), timestamp, metricValue));
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#writeAndSendMetrics()
+     */
+    @Override
+    public void writeAndSendMetrics() {        
+        if (metrics.size()>0) {
+            SocketOutputStream out = null;
+            try {
+                String payload = convertMetricsToPickleFormat(metrics);
+
+                int length = payload.length();
+                byte[] header = ByteBuffer.allocate(4).putInt(length).array();
+
+                out = socketOutputStreamPool.borrowObject(socketConnectionInfos);
+                out.write(header);
+                Writer pickleWriter = new OutputStreamWriter(out, CHARSET_NAME);
+                pickleWriter.write(payload);
+                pickleWriter.flush();
+                socketOutputStreamPool.returnObject(socketConnectionInfos, out);
+            } catch (Exception e) {
+                if(out != null) {
+                    try {
+                        socketOutputStreamPool.invalidateObject(socketConnectionInfos, out);
+                    } catch (Exception e1) {
+                        LOG.warn("Exception invalidating socketOutputStream connected to graphite server '"+socketConnectionInfos.getHost()+"':"+socketConnectionInfos.getPort(), e1);
+                    }
+                }
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Error writing to Graphite", e);
+                } else {
+                    LOG.warn("Error writing to Graphite:"+e.getMessage());
+                }
+            }
+            
+            // if there was an error, we might miss some data. for now, drop those on the floor and
+            // try to keep going.
+            if(LOG.isDebugEnabled()) {
+                LOG.debug("Wrote "+ metrics.size() +" metrics");
+            }
+            metrics.clear();
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#destroy()
+     */
+    @Override
+    public void destroy() {
+        socketOutputStreamPool.close();
+    }
+
+    /**
+     * See: http://readthedocs.org/docs/graphite/en/1.0/feeding-carbon.html
+     */
+    private static final String convertMetricsToPickleFormat(List<MetricTuple> metrics) {
+        StringBuilder pickled = new StringBuilder(metrics.size()*75);
+        pickled.append(MARK).append(LIST);
+
+        for (MetricTuple tuple : metrics) {
+            // begin outer tuple
+            pickled.append(MARK);
+
+            // the metric name is a string.
+            pickled.append(STRING)
+            // the single quotes are to match python's repr("abcd")
+                .append(QUOTE).append(tuple.name).append(QUOTE).append(LF);
+
+            // begin the inner tuple
+            pickled.append(MARK);
+
+            // timestamp is a long
+            pickled.append(LONG).append(tuple.timestamp)
+            // the trailing L is to match python's repr(long(1234))             
+                .append(LONG).append(LF);
+
+            // and the value is a string.
+            pickled.append(STRING).append(QUOTE).append(tuple.value).append(QUOTE).append(LF);
+
+            pickled.append(TUPLE) // end inner tuple
+                .append(TUPLE); // end outer tuple
+
+            pickled.append(APPEND);
+        }
+
+        // every pickle ends with STOP
+        pickled.append(STOP);
+        return pickled.toString();
+    }
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/PickleGraphiteMetricsSender.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java?rev=1641083&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java Sat Nov 22 15:40:35 2014
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.visualizers.backend.graphite;
+
+/**
+ * Bean to embed host/port to Graphite
+ * @since 2.13
+ */
+public class SocketConnectionInfos {
+    private String host;
+    private int port;
+    
+    /**
+     * @param host
+     * @param port
+     */
+    public SocketConnectionInfos(String host, int port) {
+        super();
+        this.host = host;
+        this.port = port;
+    }
+    
+    /**
+     * @return the host
+     */
+    public String getHost() {
+        return host;
+    }
+    /**
+     * @param host the host to set
+     */
+    public void setHost(String host) {
+        this.host = host;
+    }
+    /**
+     * @return the port
+     */
+    public int getPort() {
+        return port;
+    }
+    /**
+     * @param port the port to set
+     */
+    public void setPort(int port) {
+        this.port = port;
+    }
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketConnectionInfos.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java?rev=1641083&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java Sat Nov 22 15:40:35 2014
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.visualizers.backend.graphite;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+ * Convenience class for writing bytes to a {@linkplain java.net.Socket}.
+ * @since 2.13
+ */
+public class SocketOutputStream extends FilterOutputStream {
+
+    private final Socket socket;
+
+    public SocketOutputStream(InetSocketAddress inetSocketAddress) throws IOException {
+        this(new Socket(inetSocketAddress.getAddress(), inetSocketAddress.getPort()));
+    }
+
+    public SocketOutputStream(Socket socket) throws IOException {
+        super(socket.getOutputStream());
+        this.socket = socket;
+    }
+
+    /**
+     * Return the underlying Socket
+     */
+    public Socket getSocket() {
+        return socket;
+    }
+
+    @Override
+    public String toString() {
+        return "SocketOutputStream{" +
+                "socket=" + socket +
+                '}';
+    }
+
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStream.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java?rev=1641083&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java Sat Nov 22 15:40:35 2014
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.visualizers.backend.graphite;
+
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+import org.apache.commons.pool2.BaseKeyedPooledObjectFactory;
+import org.apache.commons.pool2.KeyedPooledObjectFactory;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+
+/**
+ * Pool Factory of {@link SocketOutputStream}
+ * @since 2.13
+ */
+public class SocketOutputStreamPoolFactory 
+    extends BaseKeyedPooledObjectFactory<SocketConnectionInfos, SocketOutputStream> 
+    implements KeyedPooledObjectFactory<SocketConnectionInfos, SocketOutputStream> {
+
+    private final int socketTimeoutInMillis;
+    private final int socketConnectTimeoutInMillis;
+
+    public SocketOutputStreamPoolFactory(int socketConnectTimeoutInMillis, int socketTimeoutInMillis) {
+        this.socketConnectTimeoutInMillis = socketConnectTimeoutInMillis;
+        this.socketTimeoutInMillis = socketTimeoutInMillis;
+    }
+
+    @Override
+    public PooledObject<SocketOutputStream> makeObject(SocketConnectionInfos connectionInfos) throws Exception {
+        return wrap(create(connectionInfos));
+    }
+
+    @Override
+    public void destroyObject(SocketConnectionInfos socketConnectionInfos, PooledObject<SocketOutputStream> socketOutputStream) throws Exception {
+        super.destroyObject(socketConnectionInfos, socketOutputStream);
+        SocketOutputStream outputStream = socketOutputStream.getObject();
+        outputStream.close();
+        outputStream.getSocket().close();
+    }
+
+    /**
+     */
+    @Override
+    public boolean validateObject(SocketConnectionInfos HostAndPort, PooledObject<SocketOutputStream> socketOutputStream) {
+        Socket socket = socketOutputStream.getObject().getSocket();
+        return socket.isConnected()
+                && socket.isBound()
+                && !socket.isClosed()
+                && !socket.isInputShutdown()
+                && !socket.isOutputShutdown();
+    }
+
+    @Override
+    public SocketOutputStream create(SocketConnectionInfos connectionInfos)
+            throws Exception {
+        Socket socket = new Socket();
+        socket.setKeepAlive(true);
+        socket.setSoTimeout(socketTimeoutInMillis);
+        socket.connect(new InetSocketAddress(connectionInfos.getHost(), connectionInfos.getPort()), socketConnectTimeoutInMillis);
+
+        return new SocketOutputStream(socket);
+    }
+
+    @Override
+    public PooledObject<SocketOutputStream> wrap(SocketOutputStream outputStream) {
+        return new DefaultPooledObject<SocketOutputStream>(outputStream);
+    }
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/SocketOutputStreamPoolFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java?rev=1641083&view=auto
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java (added)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java Sat Nov 22 15:40:35 2014
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.visualizers.backend.graphite;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+
+/**
+ * PlainText Graphite sender
+ * @since 2.13
+ */
+class TextGraphiteMetricsSender extends AbstractGraphiteMetricsSender {
+    private static final Logger LOG = LoggingManager.getLoggerForClass();        
+        
+    private String prefix;
+
+    private List<MetricTuple> metrics = new ArrayList<MetricTuple>();
+
+    private GenericKeyedObjectPool<SocketConnectionInfos, SocketOutputStream> socketOutputStreamPool;
+
+    private SocketConnectionInfos socketConnectionInfos;
+
+    /**
+     * @param graphiteHost Graphite Host
+     * @param graphitePort Graphite Port
+     * @param prefix Common Metrics prefix
+     */
+    TextGraphiteMetricsSender() {
+        super();
+    }
+    
+    @Override
+    public void setup(String graphiteHost, int graphitePort, String prefix) {
+        this.prefix = prefix;
+        this.socketConnectionInfos = new SocketConnectionInfos(graphiteHost, graphitePort);
+        this.socketOutputStreamPool = createSocketOutputStreamPool();
+
+        if(LOG.isInfoEnabled()) {
+            LOG.info("Created TextGraphiteMetricsSender with host:"+graphiteHost+", port:"+graphitePort+", prefix:"+prefix);
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#addMetric(long, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void addMetric(long timestamp, String contextName, String metricName, String metricValue) {
+        StringBuilder sb = new StringBuilder(50);
+        sb
+            .append(prefix)
+            .append(contextName)
+            .append(".")
+            .append(metricName);
+        metrics.add(new MetricTuple(sb.toString(), timestamp, metricValue));
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#writeAndSendMetrics()
+     */
+    @Override
+    public void writeAndSendMetrics() {        
+        if (metrics.size()>0) {
+            SocketOutputStream out = null;
+            try {
+                out = socketOutputStreamPool.borrowObject(socketConnectionInfos);
+                PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, CHARSET_NAME), false);
+                for (MetricTuple metric: metrics) {
+                    pw.printf("%s %s %d%n", metric.name, metric.value, Long.valueOf(metric.timestamp));
+                }
+                pw.flush();
+                // if there was an error, we might miss some data. for now, drop those on the floor and
+                // try to keep going.
+                if(LOG.isDebugEnabled()) {
+                    LOG.debug("Wrote "+ metrics.size() +" metrics");
+                }
+                socketOutputStreamPool.returnObject(socketConnectionInfos, out);
+            } catch (Exception e) {
+                if(out != null) {
+                    try {
+                        socketOutputStreamPool.invalidateObject(socketConnectionInfos, out);
+                    } catch (Exception e1) {
+                        LOG.warn("Exception invalidating socketOutputStream connected to graphite server '"+
+                                socketConnectionInfos.getHost()+"':"+socketConnectionInfos.getPort(), e1);
+                    }
+                }
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Error writing to Graphite", e);
+                } else {
+                    LOG.warn("Error writing to Graphite:"+e.getMessage());
+                }
+            }
+            // We drop metrics in all cases
+            metrics.clear();
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.jmeter.visualizers.backend.graphite.GraphiteMetricsSender#destroy()
+     */
+    @Override
+    public void destroy() {
+        socketOutputStreamPool.close();
+    }
+
+}

Propchange: jmeter/trunk/src/components/org/apache/jmeter/visualizers/backend/graphite/TextGraphiteMetricsSender.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1641083&r1=1641082&r2=1641083&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml (original)
+++ jmeter/trunk/xdocs/changes.xml Sat Nov 22 15:40:35 2014
@@ -189,6 +189,7 @@ See  <bugzilla>56357</bugzilla> for deta
 <h3>Listeners</h3>
 <ul>
 <li><bugzilla>55932</bugzilla> - Create a Async BackendListener to allow easy plug of new listener (Graphite, JDBC, Console,...)</li>
+<li><bugzilla>57246</bugzilla> - BackendListener : Create a Graphite implementation</li>
 </ul>
 
 <h3>Timers, Assertions, Config, Pre- &amp; Post-Processors</h3>