Skip to content

Commit 60a4943

Browse files
dsyerwilkinsona
authored andcommitted
Add support for metric export to OpenTSDB
1 parent 18928a6 commit 60a4943

File tree

21 files changed

+794
-4
lines changed

21 files changed

+794
-4
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricRepositoryAutoConfiguration.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public GaugeBuffers gaugeBuffers() {
134134
}
135135

136136
@Bean
137+
@Primary
137138
@ConditionalOnMissingBean
138139
public BufferMetricReader metricReader(CounterBuffers counters,
139140
GaugeBuffers gauges) {
@@ -181,7 +182,7 @@ static class DefaultMetricsExporterConfiguration {
181182
@Bean
182183
@ConditionalOnMissingBean
183184
@ConditionalOnBean(MetricWriter.class)
184-
public MetricCopyExporter messageChannelMetricExporter(MetricReader reader) {
185+
public MetricCopyExporter metricWritersMetricExporter(MetricReader reader) {
185186
List<MetricWriter> writers = new ArrayList<MetricWriter>(this.writers);
186187
if (this.actuatorMetricRepository != null
187188
&& writers.contains(this.actuatorMetricRepository)) {

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/AbstractMetricExporter.java

+18
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.Date;
2323
import java.util.concurrent.atomic.AtomicBoolean;
2424

25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
2527
import org.springframework.boot.actuate.metrics.Metric;
2628
import org.springframework.util.StringUtils;
2729

@@ -34,6 +36,8 @@
3436
*/
3537
public abstract class AbstractMetricExporter implements Exporter {
3638

39+
private static final Log logger = LogFactory.getLog(AbstractMetricExporter.class);
40+
3741
private volatile AtomicBoolean processing = new AtomicBoolean(false);
3842

3943
private Date earliestTimestamp = new Date();
@@ -86,11 +90,25 @@ public void export() {
8690
}
8791
}
8892
}
93+
catch (Exception e) {
94+
logger.warn("Could not write to MetricWriter: " + e.getClass() + ": "
95+
+ e.getMessage());
96+
}
8997
finally {
98+
try {
99+
flush();
100+
}
101+
catch (Exception e) {
102+
logger.warn("Could not flush MetricWriter: " + e.getClass() + ": "
103+
+ e.getMessage());
104+
}
90105
this.processing.set(false);
91106
}
92107
}
93108

109+
public void flush() {
110+
}
111+
94112
/**
95113
* Generate a group of metrics to iterate over in the form of a set of Strings (e.g.
96114
* prefixes). If the metrics to be exported partition into groups identified by a

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/MetricCopyExporter.java

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.actuate.metrics.Metric;
2323
import org.springframework.boot.actuate.metrics.reader.MetricReader;
2424
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
25+
import org.springframework.boot.actuate.metrics.writer.WriterUtils;
2526
import org.springframework.util.PatternMatchUtils;
2627

2728
/**
@@ -79,6 +80,11 @@ protected void write(String group, Collection<Metric<?>> values) {
7980
}
8081
}
8182

83+
@Override
84+
public void flush() {
85+
WriterUtils.flush(this.writer);
86+
}
87+
8288
private class PatternMatchingIterator implements Iterator<Metric<?>> {
8389

8490
private Metric<?> buffer = null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* 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+
17+
package org.springframework.boot.actuate.metrics.opentsdb;
18+
19+
import java.util.HashMap;
20+
import java.util.LinkedHashMap;
21+
import java.util.Map;
22+
23+
import org.springframework.util.ObjectUtils;
24+
25+
/**
26+
* A naming strategy that just passes through the metric name, together with tags from a
27+
* set of static values. Open TSDB requires at least one tag, so one is always added for
28+
* you: the {@value #PREFIX_KEY} key is added with a unique value "spring.X" where X is an
29+
* object hash code ID for this (the naming stategy). In most cases this will be unique
30+
* enough to allow aggregation of the underlying metrics in Open TSDB, but normally it is
31+
* best to provide your own tags, including a prefix if you know one (overwriting the
32+
* default).
33+
*
34+
* @author Dave Syer
35+
*/
36+
public class DefaultOpenTsdbNamingStrategy implements OpenTsdbNamingStrategy {
37+
38+
public static final String PREFIX_KEY = "prefix";
39+
40+
/**
41+
* Tags to apply to every metric. Open TSDB requires at least one tag, so a "prefix"
42+
* tag is added for you by default.
43+
*/
44+
private Map<String, String> tags = new LinkedHashMap<String, String>();
45+
46+
private Map<String, OpenTsdbName> cache = new HashMap<String, OpenTsdbName>();
47+
48+
public DefaultOpenTsdbNamingStrategy() {
49+
this.tags.put(PREFIX_KEY,
50+
"spring." + ObjectUtils.getIdentityHexString(this));
51+
}
52+
53+
public void setTags(Map<String, String> staticTags) {
54+
this.tags.putAll(staticTags);
55+
}
56+
57+
@Override
58+
public OpenTsdbName getName(String name) {
59+
if (this.cache.containsKey(name)) {
60+
return this.cache.get(name);
61+
}
62+
OpenTsdbName value = new OpenTsdbName(name);
63+
value.setTags(this.tags);
64+
this.cache.put(name, value);
65+
return value;
66+
}
67+
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* 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+
17+
package org.springframework.boot.actuate.metrics.opentsdb;
18+
19+
import java.util.Map;
20+
21+
/**
22+
* @author Dave Syer
23+
*/
24+
public class OpenTsdbData {
25+
26+
private OpenTsdbName name;
27+
28+
private Long timestamp;
29+
30+
private Number value;
31+
32+
protected OpenTsdbData() {
33+
this.name = new OpenTsdbName();
34+
}
35+
36+
public OpenTsdbData(String metric, Number value) {
37+
this(metric, value, System.currentTimeMillis());
38+
}
39+
40+
public OpenTsdbData(String metric, Number value, Long timestamp) {
41+
this(new OpenTsdbName(metric), value, timestamp);
42+
}
43+
44+
public OpenTsdbData(OpenTsdbName name, Number value, Long timestamp) {
45+
this.name = name;
46+
this.value = value;
47+
this.timestamp = timestamp;
48+
}
49+
50+
public String getMetric() {
51+
return this.name.getMetric();
52+
}
53+
54+
public void setMetric(String metric) {
55+
this.name.setMetric(metric);
56+
}
57+
58+
public Long getTimestamp() {
59+
return this.timestamp;
60+
}
61+
62+
public void setTimestamp(Long timestamp) {
63+
this.timestamp = timestamp;
64+
}
65+
66+
public Number getValue() {
67+
return this.value;
68+
}
69+
70+
public void setValue(Number value) {
71+
this.value = value;
72+
}
73+
74+
public Map<String, String> getTags() {
75+
return this.name.getTags();
76+
}
77+
78+
public void setTags(Map<String, String> tags) {
79+
this.name.setTags(tags);
80+
}
81+
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* 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+
17+
package org.springframework.boot.actuate.metrics.opentsdb;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
import org.apache.commons.logging.Log;
25+
import org.apache.commons.logging.LogFactory;
26+
import org.springframework.boot.actuate.metrics.Metric;
27+
import org.springframework.boot.actuate.metrics.writer.Delta;
28+
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
29+
import org.springframework.http.HttpEntity;
30+
import org.springframework.http.HttpHeaders;
31+
import org.springframework.http.MediaType;
32+
import org.springframework.http.ResponseEntity;
33+
import org.springframework.scheduling.annotation.Scheduled;
34+
import org.springframework.web.client.RestTemplate;
35+
36+
/**
37+
* A {@link MetricWriter} for the Open TSDB database (version 2.0), writing metrics to the
38+
* HTTP endpoint provided by the server. Data are buffered according to the
39+
* {@link #setBufferSize(int) bufferSize} property, and only flushed automatically when
40+
* the buffer size is reached. Users should either manually {@link #flush()} after writing
41+
* a batch of data if that makes sense, or consider adding a {@link Scheduled
42+
* <code>@Scheduled</code>} task to flush periodically.
43+
*
44+
* @author Dave Syer
45+
*/
46+
public class OpenTsdbHttpMetricWriter implements MetricWriter {
47+
48+
private static final Log logger = LogFactory.getLog(OpenTsdbHttpMetricWriter.class);
49+
50+
private RestTemplate restTemplate = new RestTemplate();
51+
52+
/**
53+
* URL for POSTing data. Defaults to http://localhost:4242/api/put.
54+
*/
55+
private String url = "http://localhost:4242/api/put";
56+
57+
/**
58+
* Buffer size to fill before posting data to server.
59+
*/
60+
private int bufferSize = 64;
61+
62+
/**
63+
* The media type to use to serialize and accept responses from the server. Defaults
64+
* to "application/json".
65+
*/
66+
private MediaType mediaType = MediaType.APPLICATION_JSON;
67+
68+
private List<OpenTsdbData> buffer = new ArrayList<OpenTsdbData>(this.bufferSize);
69+
70+
private OpenTsdbNamingStrategy namingStrategy = new DefaultOpenTsdbNamingStrategy();
71+
72+
public RestTemplate getRestTemplate() {
73+
return this.restTemplate;
74+
}
75+
76+
public void setRestTemplate(RestTemplate restTemplate) {
77+
this.restTemplate = restTemplate;
78+
}
79+
80+
public void setUrl(String url) {
81+
this.url = url;
82+
}
83+
84+
public void setBufferSize(int bufferSize) {
85+
this.bufferSize = bufferSize;
86+
}
87+
88+
public void setMediaType(MediaType mediaType) {
89+
this.mediaType = mediaType;
90+
}
91+
92+
public void setNamingStrategy(OpenTsdbNamingStrategy namingStrategy) {
93+
this.namingStrategy = namingStrategy;
94+
}
95+
96+
@Override
97+
public void increment(Delta<?> delta) {
98+
throw new UnsupportedOperationException("Counters not supported via increment");
99+
}
100+
101+
@Override
102+
public void set(Metric<?> value) {
103+
OpenTsdbData data = new OpenTsdbData(
104+
this.namingStrategy.getName(value.getName()), value.getValue(), value
105+
.getTimestamp().getTime());
106+
this.buffer.add(data);
107+
if (this.buffer.size() >= this.bufferSize) {
108+
flush();
109+
}
110+
}
111+
112+
/**
113+
* Flush the buffer without waiting for it to fill any further.
114+
*/
115+
public void flush() {
116+
if (this.buffer.isEmpty()) {
117+
return;
118+
}
119+
List<OpenTsdbData> temp = new ArrayList<OpenTsdbData>();
120+
synchronized (this.buffer) {
121+
temp.addAll(this.buffer);
122+
this.buffer.clear();
123+
}
124+
HttpHeaders headers = new HttpHeaders();
125+
headers.setAccept(Arrays.asList(this.mediaType));
126+
headers.setContentType(this.mediaType);
127+
HttpEntity<List<OpenTsdbData>> request = new HttpEntity<List<OpenTsdbData>>(temp,
128+
headers);
129+
@SuppressWarnings("rawtypes")
130+
ResponseEntity<Map> response = this.restTemplate.postForEntity(this.url, request,
131+
Map.class);
132+
if (!response.getStatusCode().is2xxSuccessful()) {
133+
logger.warn("Cannot write metrics (discarded " + temp.size() + " values): "
134+
+ response.getBody());
135+
}
136+
}
137+
138+
@Override
139+
public void reset(String metricName) {
140+
set(new Metric<Long>(metricName, 0L));
141+
}
142+
143+
}

0 commit comments

Comments
 (0)