From 0ab2f2d9dafd2a0cb12738d35e29ae501fe16035 Mon Sep 17 00:00:00 2001
From: Sean Freitag Upon instantiation, this client will establish a socket connection to a StatsD instance
+ * running on the specified host and port. Metrics are then sent over this connection as they are
+ * received by the client.
+ * Three key methods are provided for the submission of data-points for the application under
+ * scrutiny:
+ *
+ *
+ * From the perspective of the application, these methods are non-blocking, with the resulting
+ * IO operations being carried out in a separate thread. Furthermore, these methods are guaranteed
+ * not to throw an exception which may disrupt application execution.
+ *
As part of a clean system shutdown, the {@link #stop()} method should be invoked + * on any StatsD clients.
+ * + * @author Tom Denley + * + */ +public class BlockingStatsDClient implements StatsDClient { + + private static final int PACKET_SIZE_BYTES = 1500; + + /** + * Because NumberFormat is not thread-safe we cannot share instances across threads. Use a ThreadLocal to + * create one pre thread as this seems to offer a significant performance improvement over creating one per-thread: + * http://stackoverflow.com/a/1285297/2648 + * https://github.com/indeedeng/java-dogstatsd-client/issues/4 + */ + private static final ThreadLocalThis method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the counter to adjust + * @param delta + * the amount to adjust the counter by + * @param tags + * array of tags to be added to the data + */ + @Override + public void count(final String aspect, final long delta, final String... tags) { + send(String.format("%s%s:%d|c%s", prefix, aspect, delta, tagString(tags))); + } + + /** + * Increments the specified counter by one. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the counter to increment + * @param tags + * array of tags to be added to the data + */ + @Override + public void incrementCounter(final String aspect, final String... tags) { + count(aspect, 1, tags); + } + + /** + * Convenience method equivalent to {@link #incrementCounter(String, String[])}. + */ + @Override + public void increment(final String aspect, final String... tags) { + incrementCounter(aspect, tags); + } + + /** + * Decrements the specified counter by one. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the counter to decrement + * @param tags + * array of tags to be added to the data + */ + @Override + public void decrementCounter(final String aspect, final String... tags) { + count(aspect, -1, tags); + } + + /** + * Convenience method equivalent to {@link #decrementCounter(String, String[])}. + */ + @Override + public void decrement(final String aspect, final String... tags) { + decrementCounter(aspect, tags); + } + + /** + * Records the latest fixed value for the specified named gauge. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the gauge + * @param value + * the new reading of the gauge + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordGaugeValue(final String aspect, final double value, final String... tags) { + /* Intentionally using %s rather than %f here to avoid + * padding with extra 0s to represent precision */ + send(String.format("%s%s:%s|g%s", prefix, aspect, NUMBER_FORMATTERS.get().format(value), tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordGaugeValue(String, double, String[])}. + */ + @Override + public void gauge(final String aspect, final double value, final String... tags) { + recordGaugeValue(aspect, value, tags); + } + + + /** + * Records the latest fixed value for the specified named gauge. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the gauge + * @param value + * the new reading of the gauge + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordGaugeValue(final String aspect, final long value, final String... tags) { + send(String.format("%s%s:%d|g%s", prefix, aspect, value, tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordGaugeValue(String, long, String[])}. + */ + @Override + public void gauge(final String aspect, final long value, final String... tags) { + recordGaugeValue(aspect, value, tags); + } + + /** + * Records an execution time in milliseconds for the specified named operation. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the timed operation + * @param timeInMs + * the time in milliseconds + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordExecutionTime(final String aspect, final long timeInMs, final String... tags) { + send(String.format("%s%s:%d|ms%s", prefix, aspect, timeInMs, tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordExecutionTime(String, long, String[])}. + */ + @Override + public void time(final String aspect, final long value, final String... tags) { + recordExecutionTime(aspect, value, tags); + } + + /** + * Records a value for the specified named histogram. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the histogram + * @param value + * the value to be incorporated in the histogram + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordHistogramValue(final String aspect, final double value, final String... tags) { + /* Intentionally using %s rather than %f here to avoid + * padding with extra 0s to represent precision */ + send(String.format("%s%s:%s|h%s", prefix, aspect, NUMBER_FORMATTERS.get().format(value), tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordHistogramValue(String, double, String[])}. + */ + @Override + public void histogram(final String aspect, final double value, final String... tags) { + recordHistogramValue(aspect, value, tags); + } + + /** + * Records a value for the specified named histogram. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the histogram + * @param value + * the value to be incorporated in the histogram + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordHistogramValue(final String aspect, final long value, final String... tags) { + send(String.format("%s%s:%d|h%s", prefix, aspect, value, tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordHistogramValue(String, long, String[])}. + */ + @Override + public void histogram(final String aspect, final long value, final String... tags) { + recordHistogramValue(aspect, value, tags); + } + + private String eventMap(final Event event) { + final StringBuilder res = new StringBuilder(""); + + final long millisSinceEpoch = event.getMillisSinceEpoch(); + if (millisSinceEpoch != -1) { + res.append("|d:").append(millisSinceEpoch / 1000); + } + + final String hostname = event.getHostname(); + if (hostname != null) { + res.append("|h:").append(hostname); + } + + final String aggregationKey = event.getAggregationKey(); + if (aggregationKey != null) { + res.append("|k:").append(aggregationKey); + } + + final String priority = event.getPriority(); + if (priority != null) { + res.append("|p:").append(priority); + } + + final String alertType = event.getAlertType(); + if (alertType != null) { + res.append("|t:").append(alertType); + } + + return res.toString(); + } + + /** + * Records an event + * + *This method is a DataDog extension, and may not work with other servers.
+ * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param event + * The event to record + * @param tags + * array of tags to be added to the data + * + * @see http://docs.datadoghq.com/guides/dogstatsd/#events-1 + */ + @Override + public void recordEvent(final Event event, final String... tags) { + final String title = prefix + event.getTitle(); + final String text = event.getText(); + send(String.format("_e{%d,%d}:%s|%s%s%s", + title.length(), text.length(), title, text, eventMap(event), tagString(tags))); + } + + /** + * Records a run status for the specified named service check. + * + *This method is a DataDog extension, and may not work with other servers.
+ * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param sc + * the service check object + */ + @Override + public void recordServiceCheckRun(final ServiceCheck sc) { + send(toStatsDString(sc)); + } + + private String toStatsDString(final ServiceCheck sc) { + // see http://docs.datadoghq.com/guides/dogstatsd/#service-checks + final StringBuilder sb = new StringBuilder(); + sb.append(String.format("_sc|%s|%d", sc.getName(), sc.getStatus())); + if (sc.getTimestamp() > 0) { + sb.append(String.format("|d:%d", sc.getTimestamp())); + } + if (sc.getHostname() != null) { + sb.append(String.format("|h:%s", sc.getHostname())); + } + sb.append(tagString(sc.getTags())); + if (sc.getMessage() != null) { + sb.append(String.format("|m:%s", sc.getEscapedMessage())); + } + return sb.toString(); + } + + /** + * Convenience method equivalent to {@link #recordServiceCheckRun(ServiceCheck sc)}. + */ + @Override + public void serviceCheck(final ServiceCheck sc) { + recordServiceCheckRun(sc); + } + + + /** + * Records a value for the specified set. + * + * Sets are used to count the number of unique elements in a group. If you want to track the number of + * unique visitor to your site, sets are a great way to do that. + * + *This method is a DataDog extension, and may not work with other servers.
+ * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the set + * @param value + * the value to track + * @param tags + * array of tags to be added to the data + * + * @see http://docs.datadoghq.com/guides/dogstatsd/#sets + */ + @Override + public void recordSetValue(final String aspect, final String value, final String... tags) { + // documentation is light, but looking at dogstatsd source, we can send string values + // here instead of numbers + send(String.format("%s%s:%s|s%s", prefix, aspect, value, tagString(tags))); + } + + + public static final Charset MESSAGE_CHARSET = Charset.forName("UTF-8"); + private final ByteBuffer sendBuffer = ByteBuffer.allocate(PACKET_SIZE_BYTES); + + protected void send(String message) { + if (message == null) return; + + try { + final InetSocketAddress address = addressLookup.call(); + final byte[] data = message.getBytes(MESSAGE_CHARSET); + + sendBuffer.put(data); + + final int sizeOfBuffer = sendBuffer.position(); + sendBuffer.flip(); + + final int sentBytes = clientChannel.send(sendBuffer, address); + sendBuffer.limit(sendBuffer.capacity()); + sendBuffer.rewind(); + + if (sizeOfBuffer != sentBytes) { + handler.handle( + new IOException( + String.format( + "Could not send entirely stat %s to host %s:%d. Only sent %d bytes out of %d bytes", + sendBuffer.toString(), + address.getHostName(), + address.getPort(), + sentBytes, + sizeOfBuffer))); + } + } catch (Exception e) { + handler.handle(e); + } + } + + /** + * Create dynamic lookup for the given host name and port. + * + * @param hostname the host name of the targeted StatsD server + * @param port the port of the targeted StatsD server + * @return a function to perform the lookup + * @see BlockingStatsDClient#BlockingStatsDClient(String, String[], StatsDClientErrorHandler, Callable) + */ + public static CallableThis method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the counter to adjust - * @param delta - * the amount to adjust the counter by - * @param tags - * array of tags to be added to the data - */ - @Override - public void count(final String aspect, final long delta, final String... tags) { - send(String.format("%s%s:%d|c%s", prefix, aspect, delta, tagString(tags))); - } - - /** - * Increments the specified counter by one. - * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the counter to increment - * @param tags - * array of tags to be added to the data - */ - @Override - public void incrementCounter(final String aspect, final String... tags) { - count(aspect, 1, tags); - } - - /** - * Convenience method equivalent to {@link #incrementCounter(String, String[])}. - */ - @Override - public void increment(final String aspect, final String... tags) { - incrementCounter(aspect, tags); - } - - /** - * Decrements the specified counter by one. - * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the counter to decrement - * @param tags - * array of tags to be added to the data - */ - @Override - public void decrementCounter(final String aspect, final String... tags) { - count(aspect, -1, tags); - } - - /** - * Convenience method equivalent to {@link #decrementCounter(String, String[])}. - */ - @Override - public void decrement(final String aspect, final String... tags) { - decrementCounter(aspect, tags); - } - - /** - * Records the latest fixed value for the specified named gauge. - * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the gauge - * @param value - * the new reading of the gauge - * @param tags - * array of tags to be added to the data - */ - @Override - public void recordGaugeValue(final String aspect, final double value, final String... tags) { - /* Intentionally using %s rather than %f here to avoid - * padding with extra 0s to represent precision */ - send(String.format("%s%s:%s|g%s", prefix, aspect, NUMBER_FORMATTERS.get().format(value), tagString(tags))); - } - - /** - * Convenience method equivalent to {@link #recordGaugeValue(String, double, String[])}. - */ - @Override - public void gauge(final String aspect, final double value, final String... tags) { - recordGaugeValue(aspect, value, tags); - } - - - /** - * Records the latest fixed value for the specified named gauge. - * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the gauge - * @param value - * the new reading of the gauge - * @param tags - * array of tags to be added to the data - */ - @Override - public void recordGaugeValue(final String aspect, final long value, final String... tags) { - send(String.format("%s%s:%d|g%s", prefix, aspect, value, tagString(tags))); - } - - /** - * Convenience method equivalent to {@link #recordGaugeValue(String, long, String[])}. - */ - @Override - public void gauge(final String aspect, final long value, final String... tags) { - recordGaugeValue(aspect, value, tags); - } - - /** - * Records an execution time in milliseconds for the specified named operation. - * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the timed operation - * @param timeInMs - * the time in milliseconds - * @param tags - * array of tags to be added to the data - */ - @Override - public void recordExecutionTime(final String aspect, final long timeInMs, final String... tags) { - send(String.format("%s%s:%d|ms%s", prefix, aspect, timeInMs, tagString(tags))); - } - - /** - * Convenience method equivalent to {@link #recordExecutionTime(String, long, String[])}. - */ - @Override - public void time(final String aspect, final long value, final String... tags) { - recordExecutionTime(aspect, value, tags); - } - - /** - * Records a value for the specified named histogram. - * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the histogram - * @param value - * the value to be incorporated in the histogram - * @param tags - * array of tags to be added to the data - */ - @Override - public void recordHistogramValue(final String aspect, final double value, final String... tags) { - /* Intentionally using %s rather than %f here to avoid - * padding with extra 0s to represent precision */ - send(String.format("%s%s:%s|h%s", prefix, aspect, NUMBER_FORMATTERS.get().format(value), tagString(tags))); - } - - /** - * Convenience method equivalent to {@link #recordHistogramValue(String, double, String[])}. - */ - @Override - public void histogram(final String aspect, final double value, final String... tags) { - recordHistogramValue(aspect, value, tags); - } - - /** - * Records a value for the specified named histogram. - * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the histogram - * @param value - * the value to be incorporated in the histogram - * @param tags - * array of tags to be added to the data - */ - @Override - public void recordHistogramValue(final String aspect, final long value, final String... tags) { - send(String.format("%s%s:%d|h%s", prefix, aspect, value, tagString(tags))); - } - - /** - * Convenience method equivalent to {@link #recordHistogramValue(String, long, String[])}. - */ - @Override - public void histogram(final String aspect, final long value, final String... tags) { - recordHistogramValue(aspect, value, tags); - } - - private String eventMap(final Event event) { - final StringBuilder res = new StringBuilder(""); - - final long millisSinceEpoch = event.getMillisSinceEpoch(); - if (millisSinceEpoch != -1) { - res.append("|d:").append(millisSinceEpoch / 1000); - } - - final String hostname = event.getHostname(); - if (hostname != null) { - res.append("|h:").append(hostname); - } - - final String aggregationKey = event.getAggregationKey(); - if (aggregationKey != null) { - res.append("|k:").append(aggregationKey); - } - - final String priority = event.getPriority(); - if (priority != null) { - res.append("|p:").append(priority); - } - - final String alertType = event.getAlertType(); - if (alertType != null) { - res.append("|t:").append(alertType); - } - - return res.toString(); } - /** - * Records an event - * - *This method is a DataDog extension, and may not work with other servers.
- * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param event - * The event to record - * @param tags - * array of tags to be added to the data - * - * @see http://docs.datadoghq.com/guides/dogstatsd/#events-1 - */ - @Override - public void recordEvent(final Event event, final String... tags) { - final String title = prefix + event.getTitle(); - final String text = event.getText(); - send(String.format("_e{%d,%d}:%s|%s%s%s", - title.length(), text.length(), title, text, eventMap(event), tagString(tags))); - } - - /** - * Records a run status for the specified named service check. - * - *This method is a DataDog extension, and may not work with other servers.
- * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param sc - * the service check object - */ - @Override - public void recordServiceCheckRun(final ServiceCheck sc) { - send(toStatsDString(sc)); - } - - private String toStatsDString(final ServiceCheck sc) { - // see http://docs.datadoghq.com/guides/dogstatsd/#service-checks - final StringBuilder sb = new StringBuilder(); - sb.append(String.format("_sc|%s|%d", sc.getName(), sc.getStatus())); - if (sc.getTimestamp() > 0) { - sb.append(String.format("|d:%d", sc.getTimestamp())); - } - if (sc.getHostname() != null) { - sb.append(String.format("|h:%s", sc.getHostname())); - } - sb.append(tagString(sc.getTags())); - if (sc.getMessage() != null) { - sb.append(String.format("|m:%s", sc.getEscapedMessage())); - } - return sb.toString(); - } - - /** - * Convenience method equivalent to {@link #recordServiceCheckRun(ServiceCheck sc)}. - */ - @Override - public void serviceCheck(final ServiceCheck sc) { - recordServiceCheckRun(sc); - } - - - /** - * Records a value for the specified set. - * - * Sets are used to count the number of unique elements in a group. If you want to track the number of - * unique visitor to your site, sets are a great way to do that. - * - *This method is a DataDog extension, and may not work with other servers.
- * - *This method is non-blocking and is guaranteed not to throw an exception.
- * - * @param aspect - * the name of the set - * @param value - * the value to track - * @param tags - * array of tags to be added to the data - * - * @see http://docs.datadoghq.com/guides/dogstatsd/#sets - */ - @Override - public void recordSetValue(final String aspect, final String value, final String... tags) { - // documentation is light, but looking at dogstatsd source, we can send string values - // here instead of numbers - send(String.format("%s%s:%s|s%s", prefix, aspect, value, tagString(tags))); - } - - private void send(final String message) { + protected void send(String message) { queue.offer(message); } - public static final Charset MESSAGE_CHARSET = Charset.forName("UTF-8"); - + private void blockingSend(String message) { + super.send(message); + } private class QueueConsumer implements Runnable { - private final ByteBuffer sendBuffer = ByteBuffer.allocate(PACKET_SIZE_BYTES); - - private final CallableThis method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param aspect * the name of the counter to adjust @@ -282,7 +282,7 @@ public void count(final String aspect, final long delta, final String... tags) { /** * Increments the specified counter by one. * - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param aspect * the name of the counter to increment @@ -305,7 +305,7 @@ public void increment(final String aspect, final String... tags) { /** * Decrements the specified counter by one. * - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param aspect * the name of the counter to decrement @@ -328,7 +328,7 @@ public void decrement(final String aspect, final String... tags) { /** * Records the latest fixed value for the specified named gauge. * - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param aspect * the name of the gauge @@ -356,7 +356,7 @@ public void gauge(final String aspect, final double value, final String... tags) /** * Records the latest fixed value for the specified named gauge. * - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param aspect * the name of the gauge @@ -381,7 +381,7 @@ public void gauge(final String aspect, final long value, final String... tags) { /** * Records an execution time in milliseconds for the specified named operation. * - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param aspect * the name of the timed operation @@ -406,7 +406,7 @@ public void time(final String aspect, final long value, final String... tags) { /** * Records a value for the specified named histogram. * - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param aspect * the name of the histogram @@ -433,7 +433,7 @@ public void histogram(final String aspect, final double value, final String... t /** * Records a value for the specified named histogram. * - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param aspect * the name of the histogram @@ -491,7 +491,7 @@ private String eventMap(final Event event) { * *This method is a DataDog extension, and may not work with other servers.
* - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param event * The event to record @@ -513,7 +513,7 @@ public void recordEvent(final Event event, final String... tags) { * *This method is a DataDog extension, and may not work with other servers.
* - *This method is non-blocking and is guaranteed not to throw an exception.
+ *This method is guaranteed not to throw an exception.
* * @param sc * the service check object diff --git a/src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java b/src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java index d38f8d3..d67a520 100644 --- a/src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java +++ b/src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java @@ -48,7 +48,7 @@ public final class NonBlockingStatsDClient extends BlockingStatsDClient { /** * Create a new StatsD client communicating with a StatsD instance on the - * specified host and port. All messages send via this client will have + * specified host and port. All messages sent via this client will have * their keys prefixed with the specified string. The new client will * attempt to open a connection to the StatsD server immediately upon * instantiation, and may throw an exception if that a connection cannot @@ -71,7 +71,7 @@ public NonBlockingStatsDClient(final String prefix, final String hostname, final /** * Create a new StatsD client communicating with a StatsD instance on the - * specified host and port. All messages send via this client will have + * specified host and port. All messages sent via this client will have * their keys prefixed with the specified string. The new client will * attempt to open a connection to the StatsD server immediately upon * instantiation, and may throw an exception if that a connection cannot @@ -96,7 +96,7 @@ public NonBlockingStatsDClient(final String prefix, final String hostname, final /** * Create a new StatsD client communicating with a StatsD instance on the - * specified host and port. All messages send via this client will have + * specified host and port. All messages sent via this client will have * their keys prefixed with the specified string. The new client will * attempt to open a connection to the StatsD server immediately upon * instantiation, and may throw an exception if that a connection cannot From 1e75b99e49bd871afafa1c595f91354bc997bc5d Mon Sep 17 00:00:00 2001 From: Sean FreitagThis method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the counter to adjust + * @param delta + * the amount to adjust the counter by + * @param tags + * array of tags to be added to the data + */ + @Override + public void count(final String aspect, final long delta, final String... tags) { + send(String.format("%s%s:%d|c%s", prefix, aspect, delta, tagString(tags))); + } + + /** + * Increments the specified counter by one. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the counter to increment + * @param tags + * array of tags to be added to the data + */ + @Override + public void incrementCounter(final String aspect, final String... tags) { + count(aspect, 1, tags); + } + + /** + * Convenience method equivalent to {@link #incrementCounter(String, String[])}. + */ + @Override + public void increment(final String aspect, final String... tags) { + incrementCounter(aspect, tags); + } + + /** + * Decrements the specified counter by one. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the counter to decrement + * @param tags + * array of tags to be added to the data + */ + @Override + public void decrementCounter(final String aspect, final String... tags) { + count(aspect, -1, tags); + } + + /** + * Convenience method equivalent to {@link #decrementCounter(String, String[])}. + */ + @Override + public void decrement(final String aspect, final String... tags) { + decrementCounter(aspect, tags); + } + + /** + * Records the latest fixed value for the specified named gauge. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the gauge + * @param value + * the new reading of the gauge + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordGaugeValue(final String aspect, final double value, final String... tags) { + /* Intentionally using %s rather than %f here to avoid + * padding with extra 0s to represent precision */ + send(String.format("%s%s:%s|g%s", prefix, aspect, NUMBER_FORMATTERS.get().format(value), tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordGaugeValue(String, double, String[])}. + */ + @Override + public void gauge(final String aspect, final double value, final String... tags) { + recordGaugeValue(aspect, value, tags); + } + + + /** + * Records the latest fixed value for the specified named gauge. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the gauge + * @param value + * the new reading of the gauge + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordGaugeValue(final String aspect, final long value, final String... tags) { + send(String.format("%s%s:%d|g%s", prefix, aspect, value, tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordGaugeValue(String, long, String[])}. + */ + @Override + public void gauge(final String aspect, final long value, final String... tags) { + recordGaugeValue(aspect, value, tags); + } + + /** + * Records an execution time in milliseconds for the specified named operation. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the timed operation + * @param timeInMs + * the time in milliseconds + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordExecutionTime(final String aspect, final long timeInMs, final String... tags) { + send(String.format("%s%s:%d|ms%s", prefix, aspect, timeInMs, tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordExecutionTime(String, long, String[])}. + */ + @Override + public void time(final String aspect, final long value, final String... tags) { + recordExecutionTime(aspect, value, tags); + } + + /** + * Records a value for the specified named histogram. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the histogram + * @param value + * the value to be incorporated in the histogram + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordHistogramValue(final String aspect, final double value, final String... tags) { + /* Intentionally using %s rather than %f here to avoid + * padding with extra 0s to represent precision */ + send(String.format("%s%s:%s|h%s", prefix, aspect, NUMBER_FORMATTERS.get().format(value), tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordHistogramValue(String, double, String[])}. + */ + @Override + public void histogram(final String aspect, final double value, final String... tags) { + recordHistogramValue(aspect, value, tags); + } + + /** + * Records a value for the specified named histogram. + * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the histogram + * @param value + * the value to be incorporated in the histogram + * @param tags + * array of tags to be added to the data + */ + @Override + public void recordHistogramValue(final String aspect, final long value, final String... tags) { + send(String.format("%s%s:%d|h%s", prefix, aspect, value, tagString(tags))); + } + + /** + * Convenience method equivalent to {@link #recordHistogramValue(String, long, String[])}. + */ + @Override + public void histogram(final String aspect, final long value, final String... tags) { + recordHistogramValue(aspect, value, tags); + } + + private String eventMap(final Event event) { + final StringBuilder res = new StringBuilder(""); + + final long millisSinceEpoch = event.getMillisSinceEpoch(); + if (millisSinceEpoch != -1) { + res.append("|d:").append(millisSinceEpoch / 1000); + } + + final String hostname = event.getHostname(); + if (hostname != null) { + res.append("|h:").append(hostname); + } + + final String aggregationKey = event.getAggregationKey(); + if (aggregationKey != null) { + res.append("|k:").append(aggregationKey); + } + + final String priority = event.getPriority(); + if (priority != null) { + res.append("|p:").append(priority); + } + + final String alertType = event.getAlertType(); + if (alertType != null) { + res.append("|t:").append(alertType); + } + + return res.toString(); + } + + /** + * Records an event + * + *This method is a DataDog extension, and may not work with other servers.
+ * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param event + * The event to record + * @param tags + * array of tags to be added to the data + * + * @see http://docs.datadoghq.com/guides/dogstatsd/#events-1 + */ + @Override + public void recordEvent(final Event event, final String... tags) { + final String title = prefix + event.getTitle(); + final String text = event.getText(); + send(String.format("_e{%d,%d}:%s|%s%s%s", + title.length(), text.length(), title, text, eventMap(event), tagString(tags))); + } + + /** + * Records a run status for the specified named service check. + * + *This method is a DataDog extension, and may not work with other servers.
+ * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param sc + * the service check object + */ + @Override + public void recordServiceCheckRun(final ServiceCheck sc) { + send(toStatsDString(sc)); + } + + private String toStatsDString(final ServiceCheck sc) { + // see http://docs.datadoghq.com/guides/dogstatsd/#service-checks + final StringBuilder sb = new StringBuilder(); + sb.append(String.format("_sc|%s|%d", sc.getName(), sc.getStatus())); + if (sc.getTimestamp() > 0) { + sb.append(String.format("|d:%d", sc.getTimestamp())); + } + if (sc.getHostname() != null) { + sb.append(String.format("|h:%s", sc.getHostname())); + } + sb.append(tagString(sc.getTags())); + if (sc.getMessage() != null) { + sb.append(String.format("|m:%s", sc.getEscapedMessage())); + } + return sb.toString(); + } + + /** + * Convenience method equivalent to {@link #recordServiceCheckRun(ServiceCheck sc)}. + */ + @Override + public void serviceCheck(final ServiceCheck sc) { + recordServiceCheckRun(sc); + } + + + /** + * Records a value for the specified set. + * + * Sets are used to count the number of unique elements in a group. If you want to track the number of + * unique visitor to your site, sets are a great way to do that. + * + *This method is a DataDog extension, and may not work with other servers.
+ * + *This method is non-blocking and is guaranteed not to throw an exception.
+ * + * @param aspect + * the name of the set + * @param value + * the value to track + * @param tags + * array of tags to be added to the data + * + * @see http://docs.datadoghq.com/guides/dogstatsd/#sets + */ + @Override + public void recordSetValue(final String aspect, final String value, final String... tags) { + // documentation is light, but looking at dogstatsd source, we can send string values + // here instead of numbers + send(String.format("%s%s:%s|s%s", prefix, aspect, value, tagString(tags))); } - private void blockingSend(String message) { - super.send(message); + private void send(final String message) { + queue.offer(message); } + public static final Charset MESSAGE_CHARSET = Charset.forName("UTF-8"); + + private class QueueConsumer implements Runnable { - private boolean canAddChunk(String message, String chunk) { - return message.getBytes(MESSAGE_CHARSET).length + chunk.getBytes(MESSAGE_CHARSET).length + "\n".getBytes(MESSAGE_CHARSET).length < PACKET_SIZE_BYTES; + private final ByteBuffer sendBuffer = ByteBuffer.allocate(PACKET_SIZE_BYTES); + + private final Callable