View | Details | Raw Unified | Return to bug 55932
Collapse All | Expand All

(-)build.properties (-1 / +10 lines)
Lines 109-115 Link Here
109
commons-lang3.loc           = ${maven2.repo}/org/apache/commons/commons-lang3/${commons-lang3.version}
109
commons-lang3.loc           = ${maven2.repo}/org/apache/commons/commons-lang3/${commons-lang3.version}
110
commons-lang3.md5           = 71b48e6b3e1b1dc73fe705604b9c7584
110
commons-lang3.md5           = 71b48e6b3e1b1dc73fe705604b9c7584
111
111
112
113
commons-logging.version     = 1.1.3
112
commons-logging.version     = 1.1.3
114
commons-logging.jar         = commons-logging-${commons-logging.version}.jar
113
commons-logging.jar         = commons-logging-${commons-logging.version}.jar
115
commons-logging.loc         = ${maven2.repo}/commons-logging/commons-logging/${commons-logging.version}
114
commons-logging.loc         = ${maven2.repo}/commons-logging/commons-logging/${commons-logging.version}
Lines 117-127 Link Here
117
#commons-logging.md5         = E2C390FE739B2550A218262B28F290CE
116
#commons-logging.md5         = E2C390FE739B2550A218262B28F290CE
118
commons-logging.md5         = 92eb5aabc1b47287de53d45c086a435c
117
commons-logging.md5         = 92eb5aabc1b47287de53d45c086a435c
119
118
119
commons-math3.version         = 3.2
120
commons-math3.jar             = commons-math3-${commons-math3.version}.jar
121
commons-math3.loc             = ${maven2.repo}/org/apache/commons/commons-math3/${commons-math3.version}
122
commons-math3.md5             = aaa32530c0f744813570ff73db018698
123
120
commons-net.version         = 3.3
124
commons-net.version         = 3.3
121
commons-net.jar             = commons-net-${commons-net.version}.jar
125
commons-net.jar             = commons-net-${commons-net.version}.jar
122
commons-net.loc             = ${maven2.repo}/commons-net/commons-net/${commons-net.version}
126
commons-net.loc             = ${maven2.repo}/commons-net/commons-net/${commons-net.version}
123
commons-net.md5             = c077ca61598e9c21f43f8b6488fbbee9
127
commons-net.md5             = c077ca61598e9c21f43f8b6488fbbee9
124
128
129
commons-pool2.version         = 2.0
130
commons-pool2.jar             = commons-pool2-${commons-pool2.version}.jar
131
commons-pool2.loc             = ${maven2.repo}/org/apache/commons/commons-pool2/${commons-pool2.version}
132
commons-pool2.md5             = 8ff37c3bb0ff638ceeef4888d3ccd6f2
133
125
excalibur-datasource.version = 1.1.1
134
excalibur-datasource.version = 1.1.1
126
excalibur-datasource.jar    = excalibur-datasource-${excalibur-datasource.version}.jar
135
excalibur-datasource.jar    = excalibur-datasource-${excalibur-datasource.version}.jar
127
excalibur-datasource.loc    = ${maven2.repo}/excalibur-datasource/excalibur-datasource/${excalibur-datasource.version}
136
excalibur-datasource.loc    = ${maven2.repo}/excalibur-datasource/excalibur-datasource/${excalibur-datasource.version}
(-)src/components/org/apache/jmeter/reporters/graphite/GraphiteListener.java (+287 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
19
package org.apache.jmeter.reporters.graphite;
20
21
import java.io.Serializable;
22
import java.util.HashSet;
23
import java.util.Map;
24
import java.util.Set;
25
import java.util.concurrent.ConcurrentHashMap;
26
import java.util.concurrent.Executors;
27
import java.util.concurrent.ScheduledExecutorService;
28
import java.util.concurrent.TimeUnit;
29
30
import org.apache.jmeter.engine.util.NoThreadClone;
31
import org.apache.jmeter.reporters.AbstractListenerElement;
32
import org.apache.jmeter.samplers.Clearable;
33
import org.apache.jmeter.samplers.Remoteable;
34
import org.apache.jmeter.samplers.SampleEvent;
35
import org.apache.jmeter.samplers.SampleListener;
36
import org.apache.jmeter.samplers.SampleResult;
37
import org.apache.jmeter.testbeans.TestBean;
38
import org.apache.jmeter.testelement.TestStateListener;
39
import org.apache.jmeter.threads.JMeterContextService;
40
import org.apache.jmeter.threads.JMeterContextService.ThreadCounts;
41
import org.apache.jmeter.visualizers.Visualizer;
42
import org.apache.jorphan.logging.LoggingManager;
43
import org.apache.log.Logger;
44
45
/**
46
 * Graphite based Listener using Pickle Protocol
47
 * @see http://graphite.readthedocs.org/en/latest/overview.html
48
 * @since 2.10.1
49
 */
50
public class GraphiteListener extends AbstractListenerElement implements SampleListener, Clearable, Serializable,
51
    TestStateListener, Remoteable, NoThreadClone, TestBean, Visualizer, Runnable {
52
    
53
    private static final String CUMULATED_CONTEXT_NAME = "cumulated";
54
    /**
55
     * 
56
     */
57
    private static final long serialVersionUID = -4862041959317479172L;
58
    private static final Logger LOGGER = LoggingManager.getLoggerForClass();
59
    private static final String METRICS_PREFIX = "jmeter."; //$NON-NLS-1$
60
    private static final String CUMULATED_METRICS = "__cumulated__"; //$NON-NLS-1$
61
    private static final String METRIC_ACTIVE_THREADS = "activeThreads"; //$NON-NLS-1$
62
    private static final String METRIC_STARTED_THREADS = "startedThreads"; //$NON-NLS-1$
63
    private static final String METRIC_STOPPED_THREADS = "stoppedThreads"; //$NON-NLS-1$
64
    private static final String METRIC_FAILED_REQUESTS = "failure"; //$NON-NLS-1$
65
    private static final String METRIC_SUCCESSFUL_REQUESTS = "success"; //$NON-NLS-1$
66
    private static final String METRIC_TOTAL_REQUESTS = "total"; //$NON-NLS-1$
67
    private static final String METRIC_MIN_RESPONSE_TIME = "min"; //$NON-NLS-1$
68
    private static final String METRIC_MAX_RESPONSE_TIME = "max"; //$NON-NLS-1$
69
    private static final String METRIC_PERCENTILE90_RESPONSE_TIME = "percentile90"; //$NON-NLS-1$
70
    private static final String METRIC_PERCENTILE95_RESPONSE_TIME = "percentile95"; //$NON-NLS-1$
71
    private static final long ONE_SECOND = 1L;
72
    private static final int MAX_POOL_SIZE = 1;
73
74
    private String graphiteHost;
75
    private int graphitePort;
76
    private String summaryOnly;
77
    private String samplersList = ""; //$NON-NLS-1$
78
    private transient Set<String> samplersToFilter;
79
    
80
    private ConcurrentHashMap<String, SamplerMetric> metricsPerSampler = new ConcurrentHashMap<String, SamplerMetric>();
81
82
    private PickleMetricsManager pickleMetricsManager;
83
84
    private ScheduledExecutorService scheduler;
85
    
86
    public GraphiteListener() {
87
        super();
88
    }
89
90
    /** {@inheritDoc} */
91
    @Override
92
    public void clear() {
93
        super.clear();
94
        metricsPerSampler.clear();
95
    }
96
    
97
    /* (non-Javadoc)
98
     * @see org.apache.jmeter.reporters.ResultCollector#testStarted(java.lang.String)
99
     */
100
    @Override
101
    public void testStarted(String host) {
102
        String[] samplers = samplersList.split(",");
103
        samplersToFilter = new HashSet<String>();
104
        for (String samplerName : samplers) {
105
            samplersToFilter.add(samplerName);
106
        }
107
        this.pickleMetricsManager = new PickleMetricsManager(graphiteHost, graphitePort, METRICS_PREFIX);
108
        scheduler = Executors.newScheduledThreadPool(MAX_POOL_SIZE);
109
        // Don't change this as metrics are per second
110
        scheduler.scheduleAtFixedRate(this, ONE_SECOND, ONE_SECOND, TimeUnit.SECONDS);
111
    }
112
    
113
    /* (non-Javadoc)
114
     * @see org.apache.jmeter.reporters.ResultCollector#testStarted()
115
     */
116
    @Override
117
    public void testStarted() {
118
        testStarted("");
119
    }
120
    
121
    /* (non-Javadoc)
122
     * @see org.apache.jmeter.reporters.ResultCollector#testEnded(java.lang.String)
123
     */
124
    @Override
125
    public void testEnded(String host) {
126
        scheduler.shutdown();
127
        try {
128
            scheduler.awaitTermination(30, TimeUnit.SECONDS);
129
        } catch (InterruptedException e) {
130
            LOGGER.error("Error waiting for end of scheduler");
131
        }
132
        samplersToFilter.clear();
133
        pickleMetricsManager.destroy();
134
        metricsPerSampler.clear();
135
    }
136
137
    /* (non-Javadoc)
138
     * @see org.apache.jmeter.reporters.ResultCollector#testEnded()
139
     */
140
    @Override
141
    public void testEnded() {
142
        testEnded("");
143
    }
144
145
    /** {@inheritDoc} */
146
    @Override
147
    public void sampleOccurred(SampleEvent e) {
148
        SampleResult sampleResult = e.getResult();
149
        if(!"true".equals(summaryOnly) && samplersToFilter.contains(sampleResult.getSampleLabel())) {
150
            SamplerMetric samplerMetric = getSamplerMetric(sampleResult.getSampleLabel());
151
            samplerMetric.add(sampleResult);
152
        }
153
        SamplerMetric cumulatedMetrics = getSamplerMetric(CUMULATED_METRICS);
154
        cumulatedMetrics.add(sampleResult);
155
    }
156
157
    private SamplerMetric getSamplerMetric(String sampleLabel) {
158
        SamplerMetric samplerMetric = metricsPerSampler.get(sampleLabel);
159
        if(samplerMetric == null) {
160
            samplerMetric = new SamplerMetric();
161
            SamplerMetric oldValue = metricsPerSampler.putIfAbsent(sampleLabel, samplerMetric);
162
            if(oldValue != null ){
163
                samplerMetric = oldValue;
164
            }
165
        }
166
        return samplerMetric;
167
    }
168
169
    @Override
170
    public void clearData() {
171
        // NOOP
172
    }
173
174
    @Override
175
    public void sampleStarted(SampleEvent e) {
176
        // NOOP   
177
    }
178
179
    @Override
180
    public void sampleStopped(SampleEvent e) {
181
        // NOOP
182
    }
183
184
    @Override
185
    public void run() {
186
        // Need to convert millis to seconds for Graphite
187
        long timestamp = TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
188
        for (Map.Entry<String, SamplerMetric> entry : metricsPerSampler.entrySet()) {
189
            SamplerMetric metric = entry.getValue();
190
            if(entry.getKey().equals(CUMULATED_METRICS)) {
191
                addMetrics(timestamp, CUMULATED_CONTEXT_NAME, metric);
192
            } else {
193
                addMetrics(timestamp, PickleMetricsManager.sanitizeString(entry.getKey()), metric);                
194
            }
195
            // We are computing on interval basis so cleanup
196
            metric.resetForTimeInterval();
197
        }
198
        
199
        ThreadCounts tc = JMeterContextService.getThreadCounts();
200
        pickleMetricsManager.addMetric(timestamp, CUMULATED_CONTEXT_NAME, METRIC_ACTIVE_THREADS, Integer.toString(tc.activeThreads));
201
        pickleMetricsManager.addMetric(timestamp, CUMULATED_CONTEXT_NAME, METRIC_STARTED_THREADS, Integer.toString(tc.startedThreads));
202
        pickleMetricsManager.addMetric(timestamp, CUMULATED_CONTEXT_NAME, METRIC_STOPPED_THREADS, Integer.toString(tc.finishedThreads));
203
204
        pickleMetricsManager.writeAndSendMetrics();
205
    }
206
207
    /**
208
     * @param timestamp
209
     * @param contextName
210
     * @param metric
211
     */
212
    private void addMetrics(long timestamp, String contextName, SamplerMetric metric) {
213
        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_FAILED_REQUESTS, Integer.toString(metric.getFailure()));
214
        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_SUCCESSFUL_REQUESTS, Integer.toString(metric.getSuccess()));
215
        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_TOTAL_REQUESTS, Integer.toString(metric.getTotal()));
216
        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_MIN_RESPONSE_TIME, Long.toString(metric.getMinTime()));
217
        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_MAX_RESPONSE_TIME, Long.toString(metric.getMaxTime()));
218
        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_PERCENTILE90_RESPONSE_TIME, Double.toString(metric.getPercentile(90)));
219
        pickleMetricsManager.addMetric(timestamp, contextName, METRIC_PERCENTILE95_RESPONSE_TIME, Double.toString(metric.getPercentile(95)));
220
    }
221
222
    /**
223
     * @return the summaryOnly
224
     */
225
    public String getSummaryOnly() {
226
        return summaryOnly;
227
    }
228
229
    /**
230
     * @param summaryOnly the summaryOnly to set
231
     */
232
    public void setSummaryOnly(String summaryOnly) {
233
        this.summaryOnly = summaryOnly;
234
    }
235
236
    @Override
237
    public void add(SampleResult sample) {
238
        // NOOP
239
    }
240
241
    @Override
242
    public boolean isStats() {
243
        return false;
244
    }
245
246
    /**
247
     * @return the graphiteHost
248
     */
249
    public String getGraphiteHost() {
250
        return graphiteHost;
251
    }
252
253
    /**
254
     * @param graphiteHost the graphiteHost to set
255
     */
256
    public void setGraphiteHost(String graphiteHost) {
257
        this.graphiteHost = graphiteHost;
258
    }
259
260
    /**
261
     * @return the graphitePort
262
     */
263
    public int getGraphitePort() {
264
        return graphitePort;
265
    }
266
267
    /**
268
     * @param graphitePort the graphitePort to set
269
     */
270
    public void setGraphitePort(int graphitePort) {
271
        this.graphitePort = graphitePort;
272
    }
273
274
    /**
275
     * @return the samplersList
276
     */
277
    public String getSamplersList() {
278
        return samplersList;
279
    }
280
281
    /**
282
     * @param samplersList the samplersList to set
283
     */
284
    public void setSamplersList(String samplersList) {
285
        this.samplersList = samplersList;
286
    }
287
}
(-)src/components/org/apache/jmeter/reporters/graphite/GraphiteListenerBeanInfo.java (+72 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
19
package org.apache.jmeter.reporters.graphite;
20
21
import java.beans.PropertyDescriptor;
22
23
import org.apache.jmeter.testbeans.BeanInfoSupport;
24
25
/**
26
 * BeanInfoSupport for {@link GraphiteListener}
27
 * @since 2.10.1
28
 */
29
public class GraphiteListenerBeanInfo extends BeanInfoSupport {
30
    
31
    private static final String CONNECTIONS_INFOS_GROUP = "connection_infos";
32
    private static final String HOST = "graphiteHost";
33
    private static final String PORT = "graphitePort";
34
    
35
    private static final String DATAS_INFOS = "data_infos";
36
    private static final String SAMPLERS_LIST = "samplersList";
37
    private static final String SUMMARY_ONLY = "summaryOnly";
38
    private static final int DEFAULT_PICKLE_PORT = 2004;
39
40
    /**
41
     * Constructor
42
     */
43
    public GraphiteListenerBeanInfo() {
44
        super(GraphiteListener.class);
45
46
        createPropertyGroup(CONNECTIONS_INFOS_GROUP, new String[] { 
47
                HOST, PORT });
48
        createPropertyGroup(DATAS_INFOS, new String[] { 
49
                SAMPLERS_LIST, SUMMARY_ONLY });
50
51
        PropertyDescriptor p = property(HOST);
52
        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
53
        p.setValue(DEFAULT, ""); // $NON-NLS-1$
54
55
        p = property(PORT);
56
        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
57
        // Default port for Pickle format
58
        p.setValue(DEFAULT, DEFAULT_PICKLE_PORT); // $NON-NLS-1$
59
        
60
61
        p = property(SAMPLERS_LIST);
62
        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
63
        p.setValue(DEFAULT, ""); // $NON-NLS-1$
64
65
        p = property(SUMMARY_ONLY);
66
        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
67
        p.setValue(DEFAULT, "true"); // $NON-NLS-1$
68
        p.setValue(NOT_EXPRESSION, Boolean.TRUE);
69
        p.setValue(NOT_OTHER, Boolean.TRUE);
70
        p.setValue(TAGS, new String[]{"True", "False"}); // $NON-NLS-1$ $NON-NLS-2$
71
    }
72
}
(-)src/components/org/apache/jmeter/reporters/graphite/GraphiteListenerResources.properties (+30 lines)
Line 0 Link Here
1
#   Licensed to the Apache Software Foundation (ASF) under one or more
2
#   contributor license agreements.  See the NOTICE file distributed with
3
#   this work for additional information regarding copyright ownership.
4
#   The ASF licenses this file to You under the Apache License, Version 2.0
5
#   (the "License"); you may not use this file except in compliance with
6
#   the License.  You may obtain a copy of the License at
7
# 
8
#       http://www.apache.org/licenses/LICENSE-2.0
9
# 
10
#   Unless required by applicable law or agreed to in writing, software
11
#   distributed under the License is distributed on an "AS IS" BASIS,
12
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
#   See the License for the specific language governing permissions and
14
#   limitations under the License.
15
16
displayName=Graphite Listener
17
# Groups
18
connection_infos.displayName=Graphite Connection Informations
19
data_infos.displayName=Graphite Datas Informations
20
21
# fields
22
host.displayName=Host
23
host.shortDescription=Host
24
port.displayName=Port
25
port.shortDescription=Port
26
27
samplersList.displayName=Comma separated list of Samplers to send
28
samplersList.shortDescription=Comma separated list of Samplers to send
29
summary_only.displayName=Send only cumulated datas
30
summary_only.shortDescription=Send only cumulated datas
(-)src/components/org/apache/jmeter/reporters/graphite/GraphiteListenerResources_fr.properties (+30 lines)
Line 0 Link Here
1
#   Licensed to the Apache Software Foundation (ASF) under one or more
2
#   contributor license agreements.  See the NOTICE file distributed with
3
#   this work for additional information regarding copyright ownership.
4
#   The ASF licenses this file to You under the Apache License, Version 2.0
5
#   (the "License"); you may not use this file except in compliance with
6
#   the License.  You may obtain a copy of the License at
7
# 
8
#       http://www.apache.org/licenses/LICENSE-2.0
9
# 
10
#   Unless required by applicable law or agreed to in writing, software
11
#   distributed under the License is distributed on an "AS IS" BASIS,
12
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
#   See the License for the specific language governing permissions and
14
#   limitations under the License.
15
16
displayName=Graphite Listener
17
# Groups
18
connection_infos.displayName=Informations de connexion \u00E0 Graphite
19
data_infos.displayName=Donn\u00E9es envoy\u00E9es \u00E0 Graphite 
20
21
# fields
22
host.displayName=Hôte
23
host.shortDescription=Hôte
24
port.displayName=Port
25
port.shortDescription=Port
26
27
samplersList.displayName=Liste d'\u00E9chantillons \u00E0 envoyer, s\u00E9par\u00E9s par une virgule
28
samplersList.shortDescription=Liste d'\u00E9chantillons \u00E0 envoyer, s\u00E9par\u00E9s par une virgule
29
summary_only.displayName=N'envoyer que le total
30
summary_only.shortDescription=N'envoyer que le total
(-)src/components/org/apache/jmeter/reporters/graphite/PickleMetricsManager.java (+211 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
19
package org.apache.jmeter.reporters.graphite;
20
21
import java.io.OutputStreamWriter;
22
import java.io.Writer;
23
import java.nio.ByteBuffer;
24
import java.util.LinkedList;
25
import java.util.List;
26
import java.util.concurrent.TimeUnit;
27
28
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
29
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
30
import org.apache.jorphan.logging.LoggingManager;
31
import org.apache.log.Logger;
32
33
/**
34
 * Partly based on https://github.com/BrightcoveOS/metrics-graphite-pickle/blob/master/src/main/java/com/brightcove/metrics/reporting/GraphitePickleReporter.java 
35
 * as per license https://github.com/BrightcoveOS/metrics-graphite-pickle/blob/master/LICENSE.txt
36
 * @since 2.10.1
37
 */
38
class PickleMetricsManager {
39
    private static final Logger LOG = LoggingManager.getLoggerForClass();    
40
41
    private static final int SOCKET_CONNECT_TIMEOUT_MS = 1000;
42
43
    private static final String CHARSET_NAME = "ISO-8859-1"; //$NON-NLS-1$
44
    /**
45
     * Pickle opcodes needed for implementation
46
     */
47
    private static final char APPEND = 'a';
48
    private static final char LIST = 'l';
49
    private static final char LONG = 'L';
50
    private static final char MARK = '(';
51
    private static final char STOP = '.';
52
    private static final char STRING = 'S';
53
    private static final char TUPLE = 't';
54
    
55
    
56
    private static final class MetricTuple {
57
        String name;
58
        long timestamp;
59
        String value;
60
        MetricTuple(String name, long timestamp, String value) {
61
            this.name = name;
62
            this.timestamp = timestamp;
63
            this.value = value;
64
        }
65
    }
66
        
67
    private String prefix;
68
69
    // graphite expects a python-pickled list of nested tuples.
70
    private List<MetricTuple> metrics = new LinkedList<MetricTuple>();
71
72
    private GenericKeyedObjectPool<SocketConnectionInfos, SocketOutputStream> socketOutputStreamPool;
73
74
    private SocketConnectionInfos socketConnectionInfos;
75
76
    /**
77
     * @param graphiteHost Graphite Host
78
     * @param graphitePort Graphite Port
79
     * @param prefix Common Metrics prefix
80
     */
81
    PickleMetricsManager(String graphiteHost, int graphitePort, String prefix) {
82
        this.prefix = prefix;
83
        this.socketConnectionInfos = new SocketConnectionInfos(graphiteHost, graphitePort);
84
        
85
        GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
86
        config.setTestOnBorrow(true);
87
        config.setTestWhileIdle(true);
88
        config.setMaxTotalPerKey(-1);
89
        config.setMaxTotal(-1);
90
        config.setMaxIdlePerKey(-1);
91
        config.setMinEvictableIdleTimeMillis(TimeUnit.MINUTES.toMillis(3));
92
        config.setTimeBetweenEvictionRunsMillis(TimeUnit.MINUTES.toMillis(3));
93
94
        socketOutputStreamPool = new GenericKeyedObjectPool<SocketConnectionInfos, SocketOutputStream>(
95
                new SocketOutputStreamPoolFactory(SOCKET_CONNECT_TIMEOUT_MS), config);
96
97
        if(LOG.isDebugEnabled()) {
98
            LOG.debug("Created metric pickler with prefix "+prefix);
99
        }
100
    }
101
    
102
    static final String sanitizeString(String s) {
103
        return s.replace(' ', '-');
104
    }
105
    
106
    /**
107
     * Convert the metric to a python tuple of the form:
108
     *      (timestamp, (prefix.contextName.metricName, metricValue))
109
     * And add it to the list of metrics. 
110
     * @param timestamp in Seconds from 1970
111
     * @param contextName
112
     * @param metricName
113
     * @param metricValue
114
     */
115
    public void addMetric(long timestamp, String contextName, String metricName, String metricValue) {
116
        StringBuilder sb = new StringBuilder(50);
117
        sb
118
            .append(prefix)
119
            .append(contextName)
120
            .append(".")
121
            .append(metricName);
122
        metrics.add(new MetricTuple(sb.toString(), timestamp, metricValue));
123
    }
124
125
    /**
126
     * Write metrics to Pickle Format and send to Graphite
127
     */
128
    public void writeAndSendMetrics() {        
129
        if (metrics.size()>0) {
130
            SocketOutputStream out = null;
131
            try {
132
                String payload = convertMetricsToPickleFormat(metrics);
133
134
                int length = payload.length();
135
                byte[] header = ByteBuffer.allocate(4).putInt(length).array();
136
137
                out = socketOutputStreamPool.borrowObject(socketConnectionInfos);
138
                out.write(header);
139
                Writer pickleWriter = new OutputStreamWriter(out, CHARSET_NAME);
140
                pickleWriter.write(payload);
141
                pickleWriter.flush();
142
                socketOutputStreamPool.returnObject(socketConnectionInfos, out);
143
            } catch (Exception e) {
144
                if(out != null) {
145
                    try {
146
                        socketOutputStreamPool.invalidateObject(socketConnectionInfos, out);
147
                    } catch (Exception e1) {
148
                        LOG.warn("Exception invalidating socketOutputStream connected to graphite server '"+socketConnectionInfos.getHost()+"':"+socketConnectionInfos.getPort(), e1);
149
                    }
150
                }
151
                if (LOG.isDebugEnabled()) {
152
                    LOG.debug("Error writing to Graphite", e);
153
                } else {
154
                    LOG.warn("Error writing to Graphite:"+e.getMessage());
155
                }
156
            }
157
            
158
            // if there was an error, we might miss some data. for now, drop those on the floor and
159
            // try to keep going.
160
            if(LOG.isDebugEnabled()) {
161
                LOG.debug("Wrote "+ metrics.size() +" metrics");
162
            }
163
            metrics.clear();
164
        }
165
    }
166
167
    /**
168
     * 
169
     */
170
    public void destroy() {
171
        socketOutputStreamPool.close();
172
    }
173
174
    /**
175
     * See: http://readthedocs.org/docs/graphite/en/1.0/feeding-carbon.html
176
     */
177
    private static final String convertMetricsToPickleFormat(List<MetricTuple> metrics) {
178
        StringBuilder pickled = new StringBuilder(metrics.size()*25);
179
        pickled.append(MARK).append(LIST);
180
181
        for (MetricTuple tuple : metrics) {
182
            // begin outer tuple
183
            pickled.append(MARK);
184
185
            // the metric name is a string.
186
            pickled.append(STRING)
187
            // the single quotes are to match python's repr("abcd")
188
                .append('\'').append(tuple.name).append('\'').append('\n');
189
190
            // begin the inner tuple
191
            pickled.append(MARK);
192
193
            // timestamp is a long
194
            pickled.append(LONG).append(tuple.timestamp)
195
            // the trailing L is to match python's repr(long(1234))             
196
                .append('L').append('\n');
197
198
            // and the value is a string.
199
            pickled.append(STRING).append('\'').append(tuple.value).append('\'').append('\n');
200
201
            pickled.append(TUPLE) // end inner tuple
202
                .append(TUPLE); // end outer tuple
203
204
            pickled.append(APPEND);
205
        }
206
207
        // every pickle ends with STOP
208
        pickled.append(STOP);
209
        return pickled.toString();
210
    }
211
}
(-)src/components/org/apache/jmeter/reporters/graphite/SamplerMetric.java (+114 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
19
package org.apache.jmeter.reporters.graphite;
20
21
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
22
import org.apache.jmeter.samplers.SampleResult;
23
24
/**
25
 * Sampler metric
26
 * @since 2.10.1
27
 */
28
public class SamplerMetric {
29
    private DescriptiveStatistics stats = new DescriptiveStatistics();
30
    private static final int MAX_RECENT_VALUES = 100;
31
    private int success;
32
    private int failure;
33
    private long maxTime=0L;
34
    private long minTime=Long.MAX_VALUE;
35
36
    /**
37
     * 
38
     */
39
    public SamplerMetric() {
40
        // see http://commons.apache.org/proper/commons-math/userguide/stat.html
41
        stats.setWindowSize(MAX_RECENT_VALUES);
42
    }
43
44
    /**
45
     * 
46
     * @param result SampleResult
47
     */
48
    public synchronized void add(SampleResult result) {
49
        if(result.isSuccessful()) {
50
            success++;
51
        } else {
52
            failure++;
53
        }
54
        maxTime = Math.max(result.getTime(), maxTime);
55
        minTime = Math.min(result.getTime(), minTime); 
56
        stats.addValue(result.getTime());
57
    }
58
    
59
    /**
60
     * Reset metric except for percentile related datas
61
     */
62
    public synchronized void resetForTimeInterval() {
63
        // We don't clear stats as it will slide as per my understanding of 
64
        // http://commons.apache.org/proper/commons-math/userguide/stat.html
65
        success = 0;
66
        failure = 0;
67
        maxTime=0L;
68
        minTime=Long.MAX_VALUE;
69
    }
70
71
    /**
72
     * @param percentile
73
     * @return
74
     */
75
    public double getPercentile(double percentile) {
76
        return stats.getPercentile(percentile);
77
    }
78
79
    /**
80
     * 
81
     * @return total request
82
     */
83
    public int getTotal() {
84
        return success+failure;
85
    }
86
    
87
    /**
88
     * @return the success
89
     */
90
    public int getSuccess() {
91
        return success;
92
    }
93
94
    /**
95
     * @return the failure
96
     */
97
    public int getFailure() {
98
        return failure;
99
    }
100
101
    /**
102
     * @return the maxTime
103
     */
104
    public long getMaxTime() {
105
        return maxTime;
106
    }
107
108
    /**
109
     * @return the minTime
110
     */
111
    public long getMinTime() {
112
        return minTime;
113
    }
114
}
(-)src/components/org/apache/jmeter/reporters/graphite/SocketConnectionInfos.java (+62 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
19
package org.apache.jmeter.reporters.graphite;
20
21
/**
22
 * @since 2.10.1
23
 */
24
public class SocketConnectionInfos {
25
    private String host;
26
    private int port;
27
    
28
    /**
29
     * @param host
30
     * @param port
31
     */
32
    public SocketConnectionInfos(String host, int port) {
33
        super();
34
        this.host = host;
35
        this.port = port;
36
    }
37
    
38
    /**
39
     * @return the host
40
     */
41
    public String getHost() {
42
        return host;
43
    }
44
    /**
45
     * @param host the host to set
46
     */
47
    public void setHost(String host) {
48
        this.host = host;
49
    }
50
    /**
51
     * @return the port
52
     */
53
    public int getPort() {
54
        return port;
55
    }
56
    /**
57
     * @param port the port to set
58
     */
59
    public void setPort(int port) {
60
        this.port = port;
61
    }
62
}
(-)src/components/org/apache/jmeter/reporters/graphite/SocketOutputStream.java (+57 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
19
package org.apache.jmeter.reporters.graphite;
20
21
import java.io.FilterOutputStream;
22
import java.io.IOException;
23
import java.net.InetSocketAddress;
24
import java.net.Socket;
25
26
/**
27
 * Convenience class for writing bytes to a {@linkplain java.net.Socket}.
28
 * @since 2.10.1
29
 */
30
public class SocketOutputStream extends FilterOutputStream {
31
32
    private final Socket socket;
33
34
    public SocketOutputStream(InetSocketAddress inetSocketAddress) throws IOException {
35
        this(new Socket(inetSocketAddress.getAddress(), inetSocketAddress.getPort()));
36
    }
37
38
    public SocketOutputStream(Socket socket) throws IOException {
39
        super(socket.getOutputStream());
40
        this.socket = socket;
41
    }
42
43
    /**
44
     * Return the underlying Socket
45
     */
46
    public Socket getSocket() {
47
        return socket;
48
    }
49
50
    @Override
51
    public String toString() {
52
        return "SocketOutputStream{" +
53
                "socket=" + socket +
54
                '}';
55
    }
56
57
}
(-)src/components/org/apache/jmeter/reporters/graphite/SocketOutputStreamPoolFactory.java (+82 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
19
package org.apache.jmeter.reporters.graphite;
20
21
import java.net.InetSocketAddress;
22
import java.net.Socket;
23
24
import org.apache.commons.pool2.BaseKeyedPooledObjectFactory;
25
import org.apache.commons.pool2.KeyedPooledObjectFactory;
26
import org.apache.commons.pool2.PooledObject;
27
import org.apache.commons.pool2.impl.DefaultPooledObject;
28
29
/**
30
 * Pool Factory of {@link SocketOutputStream}
31
 * @since 2.10.1
32
 */
33
public class SocketOutputStreamPoolFactory 
34
    extends BaseKeyedPooledObjectFactory<SocketConnectionInfos, SocketOutputStream> 
35
    implements KeyedPooledObjectFactory<SocketConnectionInfos, SocketOutputStream> {
36
37
    private final int socketConnectTimeoutInMillis;
38
39
    public SocketOutputStreamPoolFactory(int socketConnectTimeoutInMillis) {
40
        this.socketConnectTimeoutInMillis = socketConnectTimeoutInMillis;
41
    }
42
43
    @Override
44
    public PooledObject<SocketOutputStream> makeObject(SocketConnectionInfos connectionInfos) throws Exception {
45
        return wrap(create(connectionInfos));
46
    }
47
48
    @Override
49
    public void destroyObject(SocketConnectionInfos socketConnectionInfos, PooledObject<SocketOutputStream> socketOutputStream) throws Exception {
50
        super.destroyObject(socketConnectionInfos, socketOutputStream);
51
        SocketOutputStream outputStream = socketOutputStream.getObject();
52
        outputStream.close();
53
        outputStream.getSocket().close();
54
    }
55
56
    /**
57
     */
58
    @Override
59
    public boolean validateObject(SocketConnectionInfos HostAndPort, PooledObject<SocketOutputStream> socketOutputStream) {
60
        Socket socket = socketOutputStream.getObject().getSocket();
61
        return socket.isConnected()
62
                && socket.isBound()
63
                && !socket.isClosed()
64
                && !socket.isInputShutdown()
65
                && !socket.isOutputShutdown();
66
    }
67
68
    @Override
69
    public SocketOutputStream create(SocketConnectionInfos connectionInfos)
70
            throws Exception {
71
        Socket socket = new Socket();
72
        socket.setKeepAlive(true);
73
        socket.connect(new InetSocketAddress(connectionInfos.getHost(), connectionInfos.getPort()), socketConnectTimeoutInMillis);
74
75
        return new SocketOutputStream(socket);
76
    }
77
78
    @Override
79
    public PooledObject<SocketOutputStream> wrap(SocketOutputStream outputStream) {
80
        return new DefaultPooledObject<SocketOutputStream>(outputStream);
81
    }
82
}
(-)eclipse.classpath (+2 lines)
Lines 55-61 Link Here
55
	<classpathentry kind="lib" path="lib/commons-jexl-2.1.1.jar"/>
55
	<classpathentry kind="lib" path="lib/commons-jexl-2.1.1.jar"/>
56
	<classpathentry kind="lib" path="lib/commons-lang3-3.1.jar"/>
56
	<classpathentry kind="lib" path="lib/commons-lang3-3.1.jar"/>
57
	<classpathentry kind="lib" path="lib/commons-logging-1.1.3.jar"/>
57
	<classpathentry kind="lib" path="lib/commons-logging-1.1.3.jar"/>
58
	<classpathentry kind="lib" path="lib/commons-math3-3.2.jar"/>
58
	<classpathentry kind="lib" path="lib/commons-net-3.3.jar"/>
59
	<classpathentry kind="lib" path="lib/commons-net-3.3.jar"/>
60
	<classpathentry kind="lib" path="lib/commons-pool2-2.0.jar"/>
59
	<classpathentry kind="lib" path="lib/excalibur-datasource-1.1.1.jar"/>
61
	<classpathentry kind="lib" path="lib/excalibur-datasource-1.1.1.jar"/>
60
	<classpathentry kind="lib" path="lib/excalibur-instrument-1.0.jar"/>
62
	<classpathentry kind="lib" path="lib/excalibur-instrument-1.0.jar"/>
61
	<classpathentry kind="lib" path="lib/excalibur-logger-1.1.jar"/>
63
	<classpathentry kind="lib" path="lib/excalibur-logger-1.1.jar"/>
(-)build.xml (+6 lines)
Lines 365-371 Link Here
365
    <include name="${lib.dir}/${commons-jexl2.jar}"/>
365
    <include name="${lib.dir}/${commons-jexl2.jar}"/>
366
    <include name="${lib.dir}/${commons-lang3.jar}"/>
366
    <include name="${lib.dir}/${commons-lang3.jar}"/>
367
    <include name="${lib.dir}/${commons-logging.jar}"/>
367
    <include name="${lib.dir}/${commons-logging.jar}"/>
368
    <include name="${lib.dir}/${commons-math3.jar}"/>
368
    <include name="${lib.dir}/${commons-net.jar}"/>
369
    <include name="${lib.dir}/${commons-net.jar}"/>
370
    <include name="${lib.dir}/${commons-pool2.jar}"/>
369
    <include name="${lib.dir}/${excalibur-datasource.jar}"/>
371
    <include name="${lib.dir}/${excalibur-datasource.jar}"/>
370
    <include name="${lib.dir}/${excalibur-instrument.jar}"/>
372
    <include name="${lib.dir}/${excalibur-instrument.jar}"/>
371
    <include name="${lib.dir}/${excalibur-logger.jar}"/>
373
    <include name="${lib.dir}/${excalibur-logger.jar}"/>
Lines 434-440 Link Here
434
    <pathelement location="${lib.dir}/${commons-jexl2.jar}"/>
436
    <pathelement location="${lib.dir}/${commons-jexl2.jar}"/>
435
    <pathelement location="${lib.dir}/${commons-lang3.jar}"/>
437
    <pathelement location="${lib.dir}/${commons-lang3.jar}"/>
436
    <pathelement location="${lib.dir}/${commons-logging.jar}"/>
438
    <pathelement location="${lib.dir}/${commons-logging.jar}"/>
439
  	<pathelement location="${lib.dir}/${commons-math3.jar}"/>
437
    <pathelement location="${lib.dir}/${commons-net.jar}"/>
440
    <pathelement location="${lib.dir}/${commons-net.jar}"/>
441
  	<pathelement location="${lib.dir}/${commons-pool2.jar}"/>
438
    <pathelement location="${lib.dir}/${excalibur-datasource.jar}"/>
442
    <pathelement location="${lib.dir}/${excalibur-datasource.jar}"/>
439
    <pathelement location="${lib.dir}/${excalibur-instrument.jar}"/>
443
    <pathelement location="${lib.dir}/${excalibur-instrument.jar}"/>
440
    <pathelement location="${lib.dir}/${excalibur-logger.jar}"/>
444
    <pathelement location="${lib.dir}/${excalibur-logger.jar}"/>
Lines 2888-2894 Link Here
2888
        <process_jarfile jarname="commons-jexl2"/>
2892
        <process_jarfile jarname="commons-jexl2"/>
2889
        <process_jarfile jarname="commons-lang3"/>
2893
        <process_jarfile jarname="commons-lang3"/>
2890
        <process_jarfile jarname="commons-logging"/>
2894
        <process_jarfile jarname="commons-logging"/>
2895
        <process_jarfile jarname="commons-math3"/>
2891
        <process_jarfile jarname="commons-net"/>
2896
        <process_jarfile jarname="commons-net"/>
2897
    	<process_jarfile jarname="commons-pool2"/>
2892
        <process_jarfile jarname="excalibur-datasource"/>
2898
        <process_jarfile jarname="excalibur-datasource"/>
2893
        <process_jarfile jarname="excalibur-instrument"/>
2899
        <process_jarfile jarname="excalibur-instrument"/>
2894
        <process_jarfile jarname="excalibur-logger"/>
2900
        <process_jarfile jarname="excalibur-logger"/>

Return to bug 55932