├── .gitignore ├── README.md ├── pom.xml └── src ├── main └── java │ └── org │ └── opentsdb │ └── client │ ├── Client.java │ ├── ExpectResponse.java │ ├── HttpClient.java │ ├── HttpClientImpl.java │ ├── PoolingHttpClient.java │ ├── builder │ ├── DataFormatException.java │ ├── Metric.java │ └── MetricBuilder.java │ ├── response │ ├── ErrorDetail.java │ ├── Response.java │ └── SimpleHttpResponse.java │ └── util │ └── Preconditions.java └── test ├── java └── org │ └── opentsdb │ └── client │ ├── HttpClientTest.java │ ├── PoolingHttpClientTest.java │ └── builder │ ├── MetricBuilderTest.java │ └── MetricTest.java └── resources └── multiple_metrics.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.ipr 2 | *.iws 3 | *.iml 4 | var/* 5 | target/* 6 | build/* 7 | */target/* 8 | .gradle/* 9 | out/* 10 | dependency.txt 11 | .tablesawcache 12 | .idea 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenTSDB Java Client 2 | ==================== 3 | 4 | The OpenTSDB client is a Java library that makes sending metrics and querying the OpenTSDB server simple. 5 | The HttpClientImpl class is used to push metrics or query the OpenTSDB server. The library uses the builder pattern to 6 | simplify the task of creating the JSON that is used by the client. Also it use the PoolingHttpClientConnectionManager to 7 | manager the http connection. 8 | 9 | **NOTE** I only implements the send metrics interface. Please feel free to fork and implements for your needs. 10 | 11 | ## Sending Metrics 12 | 13 | Sending metrics is done by using the MetricBuilder. You simply add a metric, the tags associated with the metric, and 14 | the data points. 15 | 16 | HttpClient client = new HttpClientImpl("http://localhost:8242"); 17 | 18 | MetricBuilder builder = MetricBuilder.getInstance(); 19 | 20 | builder.addMetric("metric1").setDataPoint(2, 30L) 21 | .addTag("tag1", "tab1value").addTag("tag2", "tab2value"); 22 | 23 | builder.addMetric("metric2").setDataPoint(2, 232.34) 24 | .addTag("tag3", "tab3value"); 25 | 26 | try { 27 | Response response = client.pushMetrics(builder, 28 | ExpectResponse.SUMMARY); 29 | System.out.println(response); 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | } 33 | 34 | 35 | ## Copyright and License 36 | 37 | Copyright 2015 Proofpoint Inc. 38 | 39 | Licensed under the Apache License, Version 2.0 (the "License"); 40 | you may not use this file except in compliance with the License. 41 | You may obtain a copy of the License at 42 | 43 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 44 | 45 | Unless required by applicable law or agreed to in writing, software 46 | distributed under the License is distributed on an "AS IS" BASIS, 47 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 48 | See the License for the specific language governing permissions and 49 | limitations under the License. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.opentsdb 6 | java-client 7 | 2.1.0 8 | jar 9 | 10 | opentsdbclient 11 | Java client for pushing and querying data to/from OpenTSDB 12 | http://opentsdb.org 13 | 14 | 15 | 16 | com.google.guava 17 | guava 18 | 14.0 19 | 20 | 21 | org.hamcrest 22 | hamcrest-all 23 | 1.1 24 | test 25 | 26 | 27 | org.apache.httpcomponents 28 | httpclient 29 | 4.3.3 30 | 31 | 32 | commons-lang 33 | commons-lang 34 | 2.6 35 | 36 | 37 | commons-io 38 | commons-io 39 | 2.2 40 | 41 | 42 | com.google.code.findbugs 43 | jsr305 44 | 2.0.0 45 | 46 | 47 | 48 | log4j 49 | log4j 50 | 1.2.16 51 | 52 | 53 | 54 | org.slf4j 55 | slf4j-log4j12 56 | 1.7.2 57 | 58 | 59 | 60 | org.slf4j 61 | slf4j-api 62 | 1.7.2 63 | 64 | 65 | 66 | junit 67 | junit 68 | 4.11 69 | test 70 | 71 | 72 | com.google.code.gson 73 | gson 74 | 2.2.4 75 | 76 | 77 | org.mockito 78 | mockito-core 79 | 1.9.5 80 | test 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-compiler-plugin 89 | 2.5.1 90 | 91 | 1.6 92 | 1.6 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/Client.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client; 2 | 3 | import java.io.IOException; 4 | 5 | import org.opentsdb.client.builder.MetricBuilder; 6 | import org.opentsdb.client.response.Response; 7 | 8 | public interface Client { 9 | 10 | public final static String POST_API = "/api/put"; 11 | 12 | /** 13 | * Sends metrics from the builder to the KairosDB server. 14 | * 15 | * @param builder 16 | * metrics builder 17 | * @return response from the server 18 | * @throws IOException 19 | * problem occurred sending to the server 20 | */ 21 | Response pushMetrics(MetricBuilder builder) throws IOException; 22 | 23 | } -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/ExpectResponse.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client; 2 | 3 | /** 4 | *
 5 |  * expect response.
 6 |  * 
 7 |  * 
 8 |  * 
9 | * 10 | * @author argan 11 | * 12 | */ 13 | public enum ExpectResponse { 14 | STATUS_CODE, SUMMARY, DETAIL 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/HttpClient.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client; 2 | 3 | import java.io.IOException; 4 | 5 | import org.opentsdb.client.builder.MetricBuilder; 6 | import org.opentsdb.client.response.Response; 7 | 8 | public interface HttpClient extends Client { 9 | 10 | public Response pushMetrics(MetricBuilder builder, 11 | ExpectResponse exceptResponse) throws IOException; 12 | } -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/HttpClientImpl.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client; 2 | 3 | import static com.google.common.base.Preconditions.checkNotNull; 4 | 5 | import java.io.IOException; 6 | 7 | import org.apache.commons.lang.StringUtils; 8 | import org.apache.log4j.Logger; 9 | import org.opentsdb.client.builder.MetricBuilder; 10 | import org.opentsdb.client.response.ErrorDetail; 11 | import org.opentsdb.client.response.Response; 12 | import org.opentsdb.client.response.SimpleHttpResponse; 13 | 14 | import com.google.gson.Gson; 15 | import com.google.gson.GsonBuilder; 16 | 17 | /** 18 | * HTTP implementation of a client. 19 | */ 20 | public class HttpClientImpl implements HttpClient { 21 | 22 | private static Logger logger = Logger.getLogger(HttpClientImpl.class); 23 | 24 | private String serviceUrl; 25 | 26 | private Gson mapper; 27 | 28 | PoolingHttpClient httpClient = new PoolingHttpClient(); 29 | 30 | public HttpClientImpl(String serviceUrl) { 31 | this.serviceUrl = serviceUrl; 32 | 33 | GsonBuilder builder = new GsonBuilder(); 34 | mapper = builder.create(); 35 | } 36 | 37 | @Override 38 | public Response pushMetrics(MetricBuilder builder) throws IOException { 39 | return pushMetrics(builder, ExpectResponse.STATUS_CODE); 40 | 41 | } 42 | 43 | @Override 44 | public Response pushMetrics(MetricBuilder builder, 45 | ExpectResponse expectResponse) throws IOException { 46 | checkNotNull(builder); 47 | 48 | // TODO 错误处理,比如IOException或者failed>0,写到队列或者文件后续重试。 49 | SimpleHttpResponse response = httpClient 50 | .doPost(buildUrl(serviceUrl, POST_API, expectResponse), 51 | builder.build()); 52 | 53 | return getResponse(response); 54 | } 55 | 56 | private String buildUrl(String serviceUrl, String postApiEndPoint, 57 | ExpectResponse expectResponse) { 58 | String url = serviceUrl + postApiEndPoint; 59 | 60 | switch (expectResponse) { 61 | case SUMMARY: 62 | url += "?summary"; 63 | break; 64 | case DETAIL: 65 | url += "?details"; 66 | break; 67 | default: 68 | break; 69 | } 70 | 71 | return url; 72 | } 73 | 74 | private Response getResponse(SimpleHttpResponse httpResponse) { 75 | Response response = new Response(httpResponse.getStatusCode()); 76 | String content = httpResponse.getContent(); 77 | if (StringUtils.isNotEmpty(content)) { 78 | if (response.isSuccess()) { 79 | ErrorDetail errorDetail = mapper.fromJson(content, 80 | ErrorDetail.class); 81 | response.setErrorDetail(errorDetail); 82 | } else { 83 | logger.error("request failed!" + httpResponse); 84 | } 85 | } 86 | return response; 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/PoolingHttpClient.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client; 2 | 3 | import static com.google.common.base.Preconditions.checkArgument; 4 | 5 | import java.io.IOException; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import org.apache.commons.lang.StringUtils; 9 | import org.apache.http.HeaderElement; 10 | import org.apache.http.HeaderElementIterator; 11 | import org.apache.http.HttpEntity; 12 | import org.apache.http.HttpResponse; 13 | import org.apache.http.client.config.RequestConfig; 14 | import org.apache.http.client.methods.HttpPost; 15 | import org.apache.http.client.methods.HttpUriRequest; 16 | import org.apache.http.conn.ConnectionKeepAliveStrategy; 17 | import org.apache.http.conn.HttpClientConnectionManager; 18 | import org.apache.http.entity.StringEntity; 19 | import org.apache.http.impl.client.CloseableHttpClient; 20 | import org.apache.http.impl.client.HttpClients; 21 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 22 | import org.apache.http.message.BasicHeaderElementIterator; 23 | import org.apache.http.protocol.HTTP; 24 | import org.apache.http.protocol.HttpContext; 25 | import org.apache.http.util.EntityUtils; 26 | import org.opentsdb.client.response.SimpleHttpResponse; 27 | 28 | /** 29 | * 30 | * @author arganzheng 31 | * 32 | */ 33 | public class PoolingHttpClient { 34 | 35 | private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 200; 36 | 37 | private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = DEFAULT_MAX_TOTAL_CONNECTIONS; 38 | 39 | private static final int DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS = (10 * 1000); 40 | private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (10 * 1000); 41 | private static final int DEFAULT_WAIT_TIMEOUT_MILLISECONDS = (10 * 1000); 42 | 43 | private static final int DEFAULT_KEEP_ALIVE_MILLISECONDS = (5 * 60 * 1000); 44 | 45 | private static final String DEFAULT_CHARSET = "UTF-8"; 46 | 47 | private static final int DEFAULT_RETRY_COUNT = 2; 48 | 49 | private int keepAlive = DEFAULT_KEEP_ALIVE_MILLISECONDS; 50 | 51 | private int maxTotalConnections = DEFAULT_MAX_TOTAL_CONNECTIONS; 52 | private int maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE; 53 | 54 | private int connectTimeout = DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS; 55 | private int readTimeout = DEFAULT_READ_TIMEOUT_MILLISECONDS; 56 | private int waitTimeout = DEFAULT_WAIT_TIMEOUT_MILLISECONDS; 57 | 58 | private int retries = DEFAULT_RETRY_COUNT; 59 | 60 | private PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); 61 | 62 | private CloseableHttpClient httpClient = null; 63 | 64 | private ConnectionKeepAliveStrategy keepAliveStrategy = new ConnectionKeepAliveStrategy() { 65 | @Override 66 | public long getKeepAliveDuration(HttpResponse response, 67 | HttpContext context) { 68 | HeaderElementIterator it = new BasicHeaderElementIterator( 69 | response.headerIterator(HTTP.CONN_KEEP_ALIVE)); 70 | while (it.hasNext()) { 71 | HeaderElement he = it.nextElement(); 72 | String param = he.getName(); 73 | String value = he.getValue(); 74 | if (value != null && param.equalsIgnoreCase("timeout")) { 75 | return Long.parseLong(value) * 1000; 76 | } 77 | } 78 | return keepAlive; 79 | } 80 | }; 81 | 82 | public PoolingHttpClient() { 83 | // Increase max total connection 84 | connManager.setMaxTotal(maxTotalConnections); 85 | // Increase default max connection per route 86 | connManager.setDefaultMaxPerRoute(maxConnectionsPerRoute); 87 | 88 | // config timeout 89 | RequestConfig config = RequestConfig.custom() 90 | .setConnectTimeout(connectTimeout) 91 | .setConnectionRequestTimeout(waitTimeout) 92 | .setSocketTimeout(readTimeout).build(); 93 | 94 | httpClient = HttpClients.custom() 95 | .setKeepAliveStrategy(keepAliveStrategy) 96 | .setConnectionManager(connManager) 97 | .setDefaultRequestConfig(config).build(); 98 | 99 | // detect idle and expired connections and close them 100 | IdleConnectionMonitorThread staleMonitor = new IdleConnectionMonitorThread( 101 | connManager); 102 | staleMonitor.start(); 103 | } 104 | 105 | public SimpleHttpResponse doPost(String url, String data) 106 | throws IOException { 107 | StringEntity requestEntity = new StringEntity(data); 108 | HttpPost postMethod = new HttpPost(url); 109 | postMethod.setEntity(requestEntity); 110 | 111 | HttpResponse response = execute(postMethod); 112 | int statusCode = response.getStatusLine().getStatusCode(); 113 | 114 | SimpleHttpResponse simpleResponse = new SimpleHttpResponse(); 115 | simpleResponse.setStatusCode(statusCode); 116 | 117 | HttpEntity entity = response.getEntity(); 118 | if (entity != null) { 119 | // should return: application/json; charset=UTF-8 120 | String ctype = entity.getContentType().getValue(); 121 | String charset = getResponseCharset(ctype); 122 | String content = EntityUtils.toString(entity, charset); 123 | simpleResponse.setContent(content); 124 | } 125 | 126 | return simpleResponse; 127 | } 128 | 129 | private static String getResponseCharset(String ctype) { 130 | String charset = DEFAULT_CHARSET; 131 | 132 | if (!StringUtils.isEmpty(ctype)) { 133 | String[] params = ctype.split(";"); 134 | for (String param : params) { 135 | param = param.trim(); 136 | if (param.startsWith("charset")) { 137 | String[] pair = param.split("=", 2); 138 | if (pair.length == 2) { 139 | if (!StringUtils.isEmpty(pair[1])) { 140 | charset = pair[1].trim(); 141 | } 142 | } 143 | break; 144 | } 145 | } 146 | } 147 | 148 | return charset; 149 | } 150 | 151 | public HttpResponse execute(HttpUriRequest request) throws IOException { 152 | HttpResponse response; 153 | 154 | int tries = ++retries; 155 | while (true) { 156 | tries--; 157 | try { 158 | response = httpClient.execute(request); 159 | break; 160 | } catch (IOException e) { 161 | if (tries < 1) 162 | throw e; 163 | } 164 | } 165 | 166 | return response; 167 | } 168 | 169 | public void shutdown() throws IOException { 170 | httpClient.close(); 171 | } 172 | 173 | public int getKeepAlive() { 174 | return keepAlive; 175 | } 176 | 177 | public void setKeepAlive(int keepAlive) { 178 | this.keepAlive = keepAlive; 179 | } 180 | 181 | public int getMaxTotalConnections() { 182 | return maxTotalConnections; 183 | } 184 | 185 | public void setMaxTotalConnections(int maxTotalConnections) { 186 | this.maxTotalConnections = maxTotalConnections; 187 | } 188 | 189 | public int getMaxConnectionsPerRoute() { 190 | return maxConnectionsPerRoute; 191 | } 192 | 193 | public void setMaxConnectionsPerRoute(int maxConnectionsPerRoute) { 194 | this.maxConnectionsPerRoute = maxConnectionsPerRoute; 195 | } 196 | 197 | public int getConnectTimeout() { 198 | return connectTimeout; 199 | } 200 | 201 | public void setConnectTimeout(int connectTimeout) { 202 | this.connectTimeout = connectTimeout; 203 | } 204 | 205 | public int getReadTimeout() { 206 | return readTimeout; 207 | } 208 | 209 | public void setReadTimeout(int readTimeout) { 210 | this.readTimeout = readTimeout; 211 | } 212 | 213 | public int getWaitTimeout() { 214 | return waitTimeout; 215 | } 216 | 217 | public void setWaitTimeout(int waitTimeout) { 218 | this.waitTimeout = waitTimeout; 219 | } 220 | 221 | public PoolingHttpClientConnectionManager getConnManager() { 222 | return connManager; 223 | } 224 | 225 | public void setConnManager(PoolingHttpClientConnectionManager connManager) { 226 | this.connManager = connManager; 227 | } 228 | 229 | public int getRetryCount() { 230 | return retries; 231 | } 232 | 233 | public void setRetryCount(int retries) { 234 | checkArgument(retries >= 0); 235 | this.retries = retries; 236 | } 237 | 238 | public static class IdleConnectionMonitorThread extends Thread { 239 | 240 | private final HttpClientConnectionManager connMgr; 241 | private volatile boolean shutdown; 242 | 243 | public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) { 244 | super(); 245 | this.connMgr = connMgr; 246 | } 247 | 248 | @Override 249 | public void run() { 250 | try { 251 | while (!shutdown) { 252 | synchronized (this) { 253 | wait(5000); 254 | // Close expired connections 255 | connMgr.closeExpiredConnections(); 256 | // Optionally, close connections 257 | // that have been idle longer than 60 sec 258 | connMgr.closeIdleConnections(60, TimeUnit.SECONDS); 259 | } 260 | } 261 | } catch (InterruptedException ex) { 262 | // terminate 263 | shutdown(); 264 | } 265 | } 266 | 267 | public void shutdown() { 268 | shutdown = true; 269 | synchronized (this) { 270 | notifyAll(); 271 | } 272 | } 273 | 274 | } 275 | } -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/builder/DataFormatException.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client.builder; 2 | 3 | public class DataFormatException extends Exception 4 | { 5 | public DataFormatException() 6 | { 7 | super(); 8 | } 9 | 10 | public DataFormatException(String s) 11 | { 12 | super(s); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/builder/Metric.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Proofpoint Inc. 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 | package org.opentsdb.client.builder; 17 | 18 | import static com.google.common.base.Preconditions.checkArgument; 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | import static org.opentsdb.client.util.Preconditions.checkNotNullOrEmpty; 21 | 22 | import java.util.Collections; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import com.google.gson.annotations.SerializedName; 27 | 28 | /** 29 | * A metric contains measurements or data points. Each data point has a time 30 | * stamp of when the measurement occurred and a value that is either a long or 31 | * double and optionally contains tags. Tags are labels that can be added to 32 | * better identify the metric. For example, if the measurement was done on 33 | * server1 then you might add a tag named "host" with a value of "server1". Note 34 | * that a metric must have at least one tag. 35 | */ 36 | public class Metric { 37 | 38 | @SerializedName("metric") 39 | private String name; 40 | 41 | private long timestamp; 42 | 43 | private Object value; 44 | 45 | private Map tags = new HashMap(); 46 | 47 | protected Metric(String name) { 48 | this.name = checkNotNullOrEmpty(name); 49 | } 50 | 51 | /** 52 | * Adds a tag to the data point. 53 | * 54 | * @param name 55 | * tag identifier 56 | * @param value 57 | * tag value 58 | * @return the metric the tag was added to 59 | */ 60 | public Metric addTag(String name, String value) { 61 | checkNotNullOrEmpty(name); 62 | checkNotNullOrEmpty(value); 63 | tags.put(name, value); 64 | 65 | return this; 66 | } 67 | 68 | /** 69 | * Adds tags to the data point. 70 | * 71 | * @param tags 72 | * map of tags 73 | * @return the metric the tags were added to 74 | */ 75 | public Metric addTags(Map tags) { 76 | checkNotNull(tags); 77 | this.tags.putAll(tags); 78 | 79 | return this; 80 | } 81 | 82 | /** 83 | * set the data point for the metric. 84 | * 85 | * @param timestamp 86 | * when the measurement occurred 87 | * @param value 88 | * the measurement value 89 | * @return the metric 90 | */ 91 | protected Metric innerAddDataPoint(long timestamp, Object value) { 92 | checkArgument(timestamp > 0); 93 | this.timestamp = timestamp; 94 | this.value = checkNotNull(value); 95 | 96 | return this; 97 | } 98 | 99 | /** 100 | * Adds the data point to the metric with a timestamp of now. 101 | * 102 | * @param value 103 | * the measurement value 104 | * @return the metric 105 | */ 106 | public Metric setDataPoint(long value) { 107 | return innerAddDataPoint(System.currentTimeMillis(), value); 108 | } 109 | 110 | public Metric setDataPoint(long timestamp, long value) { 111 | return innerAddDataPoint(timestamp, value); 112 | } 113 | 114 | /** 115 | * Adds the data point to the metric. 116 | * 117 | * @param timestamp 118 | * when the measurement occurred 119 | * @param value 120 | * the measurement value 121 | * @return the metric 122 | */ 123 | public Metric setDataPoint(long timestamp, double value) { 124 | return innerAddDataPoint(timestamp, value); 125 | } 126 | 127 | /** 128 | * Adds the data point to the metric with a timestamp of now. 129 | * 130 | * @param value 131 | * the measurement value 132 | * @return the metric 133 | */ 134 | public Metric setDataPoint(double value) { 135 | return innerAddDataPoint(System.currentTimeMillis(), value); 136 | } 137 | 138 | /** 139 | * Time when the data point was measured. 140 | * 141 | * @return time when the data point was measured 142 | */ 143 | public long getTimestamp() { 144 | return timestamp; 145 | } 146 | 147 | public Object getValue() { 148 | return value; 149 | } 150 | 151 | public String stringValue() throws DataFormatException { 152 | return value.toString(); 153 | } 154 | 155 | public long longValue() throws DataFormatException { 156 | try { 157 | return ((Number) value).longValue(); 158 | } catch (Exception e) { 159 | throw new DataFormatException("Value is not a long"); 160 | } 161 | } 162 | 163 | public double doubleValue() throws DataFormatException { 164 | try { 165 | return ((Number) value).doubleValue(); 166 | } catch (Exception e) { 167 | throw new DataFormatException("Value is not a double"); 168 | } 169 | } 170 | 171 | public boolean isDoubleValue() { 172 | return !(((Number) value).doubleValue() == Math.floor(((Number) value) 173 | .doubleValue())); 174 | } 175 | 176 | public boolean isIntegerValue() { 177 | return ((Number) value).doubleValue() == Math.floor(((Number) value) 178 | .doubleValue()); 179 | } 180 | 181 | /** 182 | * Returns the metric name. 183 | * 184 | * @return metric name 185 | */ 186 | public String getName() { 187 | return name; 188 | } 189 | 190 | /** 191 | * Returns the tags associated with the data point. 192 | * 193 | * @return tag for the data point 194 | */ 195 | public Map getTags() { 196 | return Collections.unmodifiableMap(tags); 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/builder/MetricBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Proofpoint Inc. 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 | package org.opentsdb.client.builder; 17 | 18 | import static com.google.common.base.Preconditions.checkState; 19 | 20 | import java.io.IOException; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import com.google.gson.Gson; 25 | import com.google.gson.GsonBuilder; 26 | 27 | /** 28 | * Builder used to create the JSON to push metrics to KairosDB. 29 | */ 30 | public class MetricBuilder { 31 | private List metrics = new ArrayList(); 32 | private transient Gson mapper; 33 | 34 | private MetricBuilder() { 35 | GsonBuilder builder = new GsonBuilder(); 36 | mapper = builder.create(); 37 | } 38 | 39 | /** 40 | * Returns a new metric builder. 41 | * 42 | * @return metric builder 43 | */ 44 | public static MetricBuilder getInstance() { 45 | return new MetricBuilder(); 46 | } 47 | 48 | /** 49 | * Adds a metric to the builder. 50 | * 51 | * @param metricName 52 | * metric name 53 | * @return the new metric 54 | */ 55 | public Metric addMetric(String metricName) { 56 | Metric metric = new Metric(metricName); 57 | metrics.add(metric); 58 | return metric; 59 | } 60 | 61 | /** 62 | * Returns a list of metrics added to the builder. 63 | * 64 | * @return list of metrics 65 | */ 66 | public List getMetrics() { 67 | return metrics; 68 | } 69 | 70 | /** 71 | * Returns the JSON string built by the builder. This is the JSON that can 72 | * be used by the client add metrics. 73 | * 74 | * @return JSON 75 | * @throws IOException 76 | * if metrics cannot be converted to JSON 77 | */ 78 | public String build() throws IOException { 79 | for (Metric metric : metrics) { 80 | // verify that there is at least one tag for each metric 81 | checkState(metric.getTags().size() > 0, metric.getName() 82 | + " must contain at least one tag."); 83 | } 84 | return mapper.toJson(metrics); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/response/ErrorDetail.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client.response; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * List of errors returned by OpenTSDB. 10 | */ 11 | public class ErrorDetail { 12 | private List errors; 13 | 14 | private Integer success; 15 | private Integer failed; 16 | 17 | public ErrorDetail(List errors) { 18 | this.errors = errors; 19 | } 20 | 21 | public ErrorDetail(Integer success, Integer failed) { 22 | this.success = success; 23 | this.failed = failed; 24 | } 25 | 26 | public ErrorDetail(Integer success, Integer failed, 27 | List errors) { 28 | this.success = success; 29 | this.failed = failed; 30 | 31 | this.errors = errors; 32 | } 33 | 34 | public ErrorDetail(ErrorDetailEntity error) { 35 | errors = Collections.singletonList(error); 36 | } 37 | 38 | public List getErrors() { 39 | return (errors); 40 | } 41 | 42 | public Integer getSuccess() { 43 | return success; 44 | } 45 | 46 | public void setSuccess(Integer success) { 47 | this.success = success; 48 | } 49 | 50 | public Integer getFailed() { 51 | return failed; 52 | } 53 | 54 | public void setFailed(Integer failed) { 55 | this.failed = failed; 56 | } 57 | 58 | public static class ErrorDetailEntity { 59 | private DataPoint datapoint; 60 | private String error; 61 | 62 | public DataPoint getDatapoint() { 63 | return datapoint; 64 | } 65 | 66 | public void setDatapoint(DataPoint datapoint) { 67 | this.datapoint = datapoint; 68 | } 69 | 70 | public String getError() { 71 | return error; 72 | } 73 | 74 | public void setError(String error) { 75 | this.error = error; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "ErrorDetailEntity [datapoint=" + datapoint + ", error=" 81 | + error + "]"; 82 | } 83 | 84 | } 85 | 86 | public static class DataPoint { 87 | private String metric; 88 | private long timestamp; 89 | 90 | private Object value; 91 | 92 | private Map tags = new HashMap(); 93 | 94 | public String getMetric() { 95 | return metric; 96 | } 97 | 98 | public void setMetric(String metric) { 99 | this.metric = metric; 100 | } 101 | 102 | public long getTimestamp() { 103 | return timestamp; 104 | } 105 | 106 | public void setTimestamp(long timestamp) { 107 | this.timestamp = timestamp; 108 | } 109 | 110 | public Object getValue() { 111 | return value; 112 | } 113 | 114 | public void setValue(Object value) { 115 | this.value = value; 116 | } 117 | 118 | public Map getTags() { 119 | return tags; 120 | } 121 | 122 | public void setTags(Map tags) { 123 | this.tags = tags; 124 | } 125 | 126 | } 127 | 128 | @Override 129 | public String toString() { 130 | return "ErrorDetail [" + "success=" + success + ", failed=" + failed 131 | + ", errors=" + errors + "]"; 132 | } 133 | } -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/response/Response.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client.response; 2 | 3 | /** 4 | * Response returned by the OpenTSDB server. 5 | */ 6 | public class Response { 7 | private int statusCode; 8 | private ErrorDetail errorDetail; 9 | 10 | public Response() { 11 | } 12 | 13 | public boolean isSuccess() { 14 | return statusCode == 200 || statusCode == 204; 15 | } 16 | 17 | public Response(int statusCode) { 18 | this.statusCode = statusCode; 19 | } 20 | 21 | public int getStatusCode() { 22 | return statusCode; 23 | } 24 | 25 | public void setStatusCode(int statusCode) { 26 | this.statusCode = statusCode; 27 | } 28 | 29 | public ErrorDetail getErrorDetail() { 30 | return errorDetail; 31 | } 32 | 33 | public void setErrorDetail(ErrorDetail errorDetail) { 34 | this.errorDetail = errorDetail; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "Response [statusCode=" + statusCode + ", errorDetail=" 40 | + errorDetail + "]"; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/response/SimpleHttpResponse.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client.response; 2 | 3 | /** 4 | * @author argan 5 | * 6 | */ 7 | public class SimpleHttpResponse { 8 | private int statusCode; 9 | private String content; 10 | 11 | public boolean isSuccess() { 12 | return statusCode == 200 || statusCode == 204; 13 | } 14 | 15 | public int getStatusCode() { 16 | return statusCode; 17 | } 18 | 19 | public void setStatusCode(int statusCode) { 20 | this.statusCode = statusCode; 21 | } 22 | 23 | public String getContent() { 24 | return content; 25 | } 26 | 27 | public void setContent(String content) { 28 | this.content = content; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/opentsdb/client/util/Preconditions.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client.util; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import javax.annotation.Nullable; 5 | 6 | public class Preconditions { 7 | public static String checkNotNullOrEmpty(String reference) { 8 | com.google.common.base.Preconditions.checkNotNull(reference); 9 | if (reference.isEmpty()) { 10 | throw new IllegalArgumentException(); 11 | } 12 | return reference; 13 | } 14 | 15 | public static String checkNotNullOrEmpty(String reference, 16 | @Nullable String errorMessageTemplate, 17 | @Nullable Object... errorMessageArgs) { 18 | com.google.common.base.Preconditions.checkNotNull(reference, 19 | errorMessageTemplate, errorMessageArgs); 20 | if (reference.isEmpty()) { 21 | throw new IllegalArgumentException(format(errorMessageTemplate, 22 | errorMessageArgs)); 23 | 24 | } 25 | return reference; 26 | } 27 | 28 | /** 29 | * Copied from Google's Precondition class because it is package protected. 30 | * 31 | * Substitutes each {@code %s} in {@code template} with an argument. These 32 | * are matched by position - the first {@code %s} gets {@code args[0]}, etc. 33 | * If there are more arguments than placeholders, the unmatched arguments 34 | * will be appended to the end of the formatted message in square braces. 35 | * 36 | * @param template 37 | * a non-null string containing 0 or more {@code %s} 38 | * placeholders. 39 | * @param args 40 | * the arguments to be substituted into the message template. 41 | * Arguments are converted to strings using 42 | * {@link String#valueOf(Object)}. Arguments can be null. 43 | */ 44 | @VisibleForTesting 45 | static String format(String template, @Nullable Object... args) { 46 | template = String.valueOf(template); // null -> "null" 47 | 48 | // start substituting the arguments into the '%s' placeholders 49 | StringBuilder builder = new StringBuilder(template.length() + 16 50 | * args.length); 51 | int templateStart = 0; 52 | int i = 0; 53 | while (i < args.length) { 54 | int placeholderStart = template.indexOf("%s", templateStart); 55 | if (placeholderStart == -1) { 56 | break; 57 | } 58 | builder.append(template.substring(templateStart, placeholderStart)); 59 | builder.append(args[i++]); 60 | templateStart = placeholderStart + 2; 61 | } 62 | builder.append(template.substring(templateStart)); 63 | 64 | // if we run out of placeholders, append the extra args in square braces 65 | if (i < args.length) { 66 | builder.append(" ["); 67 | builder.append(args[i++]); 68 | while (i < args.length) { 69 | builder.append(", "); 70 | builder.append(args[i++]); 71 | } 72 | builder.append(']'); 73 | } 74 | 75 | return builder.toString(); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/test/java/org/opentsdb/client/HttpClientTest.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client; 2 | 3 | import java.io.IOException; 4 | 5 | import org.junit.Test; 6 | import org.opentsdb.client.builder.MetricBuilder; 7 | import org.opentsdb.client.response.Response; 8 | 9 | public class HttpClientTest { 10 | 11 | @Test 12 | public void test_pushMetrics_DefaultRetries() { 13 | HttpClientImpl client = new HttpClientImpl("http://localhost:8242"); 14 | 15 | MetricBuilder builder = MetricBuilder.getInstance(); 16 | 17 | builder.addMetric("metric1").setDataPoint(2, 30L) 18 | .addTag("tag1", "tab1value").addTag("tag2", "tab2value"); 19 | 20 | builder.addMetric("metric2").setDataPoint(2, 232.34) 21 | .addTag("tag3", "tab3value"); 22 | 23 | try { 24 | Response response = client.pushMetrics(builder, 25 | ExpectResponse.SUMMARY); 26 | System.out.println(response); 27 | } catch (IOException e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/java/org/opentsdb/client/PoolingHttpClientTest.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client; 2 | 3 | import java.io.IOException; 4 | 5 | import org.junit.Test; 6 | import org.opentsdb.client.builder.MetricBuilder; 7 | import org.opentsdb.client.response.SimpleHttpResponse; 8 | 9 | public class PoolingHttpClientTest { 10 | 11 | @Test 12 | public void test_postJson_DefaultRetries() throws InterruptedException { 13 | PoolingHttpClient client = new PoolingHttpClient(); 14 | 15 | try { 16 | for (int i = 0; i < 10; i++) { 17 | MetricBuilder builder = MetricBuilder.getInstance(); 18 | 19 | builder.addMetric("metric" + i).setDataPoint(2, 30L) 20 | .addTag("tag1", "tab1value") 21 | .addTag("tag2", "tab2value"); 22 | 23 | SimpleHttpResponse response = client.doPost( 24 | "http://localhost:8242/api/put/?details", 25 | builder.build()); 26 | System.out.println(response.getStatusCode()); 27 | System.out.println(response.getContent()); 28 | } 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/test/java/org/opentsdb/client/builder/MetricBuilderTest.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client.builder; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.google.common.io.Resources; 5 | 6 | import org.junit.Test; 7 | import org.opentsdb.client.builder.MetricBuilder; 8 | 9 | import java.io.IOException; 10 | 11 | import static org.hamcrest.CoreMatchers.equalTo; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class MetricBuilderTest { 15 | @Test 16 | public void test() throws IOException { 17 | String json = Resources.toString( 18 | Resources.getResource("multiple_metrics.json"), Charsets.UTF_8); 19 | 20 | MetricBuilder builder = MetricBuilder.getInstance(); 21 | 22 | builder.addMetric("metric1").setDataPoint(2, 30L) 23 | .addTag("tag1", "tab1value").addTag("tag2", "tab2value"); 24 | 25 | builder.addMetric("metric2").setDataPoint(2, 232.34) 26 | .addTag("tag3", "tab3value"); 27 | 28 | assertThat(builder.build(), equalTo(json)); 29 | } 30 | 31 | @Test(expected = IllegalStateException.class) 32 | public void test_metricContainsTags() throws IOException { 33 | MetricBuilder builder = MetricBuilder.getInstance(); 34 | builder.addMetric("metric1"); 35 | builder.addMetric("metric2").addTag("tag", "value"); 36 | 37 | builder.build(); 38 | } 39 | 40 | public static class MetricTest { 41 | @Test(expected = NullPointerException.class) 42 | public void test_nullMetricName_invalid() { 43 | MetricBuilder builder = MetricBuilder.getInstance(); 44 | 45 | builder.addMetric(null); 46 | } 47 | 48 | @Test(expected = IllegalArgumentException.class) 49 | public void test_emptyMetricName_invalid() { 50 | MetricBuilder builder = MetricBuilder.getInstance(); 51 | 52 | builder.addMetric(""); 53 | } 54 | 55 | @Test(expected = NullPointerException.class) 56 | public void test_nullTagName_invalid() { 57 | MetricBuilder builder = MetricBuilder.getInstance(); 58 | 59 | builder.addMetric("metric1").addTag(null, "value"); 60 | } 61 | 62 | @Test(expected = IllegalArgumentException.class) 63 | public void test_emptyTagName_invalid() { 64 | MetricBuilder builder = MetricBuilder.getInstance(); 65 | 66 | builder.addMetric("metric1").addTag("", "value"); 67 | } 68 | 69 | @Test(expected = NullPointerException.class) 70 | public void test_nullTagValue_invalid() { 71 | MetricBuilder builder = MetricBuilder.getInstance(); 72 | 73 | builder.addMetric("metric1").addTag("tag", null); 74 | } 75 | 76 | @Test(expected = IllegalArgumentException.class) 77 | public void test_emptyTagValue_invalid() { 78 | MetricBuilder builder = MetricBuilder.getInstance(); 79 | 80 | builder.addMetric("metric1").addTag("tag", ""); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/test/java/org/opentsdb/client/builder/MetricTest.java: -------------------------------------------------------------------------------- 1 | package org.opentsdb.client.builder; 2 | 3 | import org.junit.Test; 4 | import org.opentsdb.client.builder.MetricBuilder; 5 | 6 | public class MetricTest { 7 | @Test(expected = NullPointerException.class) 8 | public void test_nullMetricName_invalid() { 9 | MetricBuilder builder = MetricBuilder.getInstance(); 10 | 11 | builder.addMetric(null); 12 | } 13 | 14 | @Test(expected = IllegalArgumentException.class) 15 | public void test_emptyMetricName_invalid() { 16 | MetricBuilder builder = MetricBuilder.getInstance(); 17 | 18 | builder.addMetric(""); 19 | } 20 | 21 | @Test(expected = NullPointerException.class) 22 | public void test_nullTagName_invalid() { 23 | MetricBuilder builder = MetricBuilder.getInstance(); 24 | 25 | builder.addMetric("metric1").addTag(null, "value"); 26 | } 27 | 28 | @Test(expected = IllegalArgumentException.class) 29 | public void test_emptyTagName_invalid() { 30 | MetricBuilder builder = MetricBuilder.getInstance(); 31 | 32 | builder.addMetric("metric1").addTag("", "value"); 33 | } 34 | 35 | @Test(expected = NullPointerException.class) 36 | public void test_nullTagValue_invalid() { 37 | MetricBuilder builder = MetricBuilder.getInstance(); 38 | 39 | builder.addMetric("metric1").addTag("tag", null); 40 | } 41 | 42 | @Test(expected = IllegalArgumentException.class) 43 | public void test_emptyTagValue_invalid() { 44 | MetricBuilder builder = MetricBuilder.getInstance(); 45 | 46 | builder.addMetric("metric1").addTag("tag", ""); 47 | } 48 | } -------------------------------------------------------------------------------- /src/test/resources/multiple_metrics.json: -------------------------------------------------------------------------------- 1 | [{"name":"metric1","tags":{"tag2":"tab2value","tag1":"tab1value"},"datapoints":[[1,10],[2,30]]},{"name":"metric2","tags":{"tag3":"tab3value"},"datapoints":[[2,30],[3,2.3]]}] --------------------------------------------------------------------------------