├── .gitignore ├── .travis.yml ├── vendor ├── buildlib │ ├── junit-dep-4.10.jar │ ├── hamcrest-core-1.3.0RC2.jar │ └── hamcrest-library-1.3.0RC2.jar └── src │ └── junit-dep-4.10-sources.jar ├── .project ├── src ├── main │ └── java │ │ └── com │ │ └── timgroup │ │ └── statsd │ │ ├── StatsDClientErrorHandler.java │ │ ├── StatsDClientException.java │ │ ├── NoOpStatsDClient.java │ │ ├── NonBlockingUdpSender.java │ │ ├── ConvenienceMethodProvidingStatsDClient.java │ │ ├── StatsDClient.java │ │ └── NonBlockingStatsDClient.java └── test │ └── java │ └── com │ └── timgroup │ └── statsd │ └── NonBlockingStatsDClientTest.java ├── .classpath ├── .settings └── org.eclipse.jdt.core.prefs ├── LICENSE ├── java-statsd-client.pom └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | build 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | - openjdk7 5 | - openjdk6 6 | -------------------------------------------------------------------------------- /vendor/buildlib/junit-dep-4.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-group/java-statsd-client/HEAD/vendor/buildlib/junit-dep-4.10.jar -------------------------------------------------------------------------------- /vendor/src/junit-dep-4.10-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-group/java-statsd-client/HEAD/vendor/src/junit-dep-4.10-sources.jar -------------------------------------------------------------------------------- /vendor/buildlib/hamcrest-core-1.3.0RC2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-group/java-statsd-client/HEAD/vendor/buildlib/hamcrest-core-1.3.0RC2.jar -------------------------------------------------------------------------------- /vendor/buildlib/hamcrest-library-1.3.0RC2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tim-group/java-statsd-client/HEAD/vendor/buildlib/hamcrest-library-1.3.0RC2.jar -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | java-statsd-client 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/com/timgroup/statsd/StatsDClientErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | /** 4 | * Describes a handler capable of processing exceptions that occur during StatsD client operations. 5 | * 6 | * @author Tom Denley 7 | * 8 | */ 9 | public interface StatsDClientErrorHandler { 10 | 11 | /** 12 | * Handle the given exception, which occurred during a StatsD client operation. 13 | * 14 | * @param exception 15 | * the {@link Exception} that occurred 16 | */ 17 | void handle(Exception exception); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/timgroup/statsd/StatsDClientException.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | 4 | /** 5 | * Signals that an exception has occurred when trying to start the 6 | * StatsD client 7 | * 8 | * @author Tom Denley 9 | * 10 | */ 11 | public final class StatsDClientException extends RuntimeException { 12 | 13 | private static final long serialVersionUID = 3186887620964773839L; 14 | 15 | public StatsDClientException() { 16 | super(); 17 | } 18 | 19 | public StatsDClientException(String message, Throwable cause) { 20 | super(message, cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.6 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.6 12 | -------------------------------------------------------------------------------- /src/main/java/com/timgroup/statsd/NoOpStatsDClient.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | /** 4 | * A No-Op StatsDClient, which can be substituted in when metrics are not 5 | * required. 6 | * 7 | * @author Tom Denley 8 | * 9 | */ 10 | public final class NoOpStatsDClient extends ConvenienceMethodProvidingStatsDClient { 11 | @Override public void stop() { } 12 | @Override public void count(String aspect, long delta, double sampleRate) { } 13 | @Override public void recordGaugeValue(String aspect, long value) { } 14 | @Override public void recordGaugeValue(String aspect, double value) { } 15 | @Override public void recordGaugeDelta(String aspect, long delta) { } 16 | @Override public void recordGaugeDelta(String aspect, double delta) { } 17 | @Override public void recordSetEvent(String aspect, String value) { } 18 | @Override public void recordExecutionTime(String aspect, long timeInMs, double sampleRate) { } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | java-statsd-client. Copyright (C) 2014 youDevise, Ltd. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /java-statsd-client.pom: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.timgroup 4 | java-statsd-client 5 | jar 6 | java-statsd-client 7 | @VERSION@ 8 | A tiny library allowing Java applications to communicate with statsd instances easily. 9 | http://github.com/tim-group/java-statsd-client 10 | 11 | 12 | The MIT License (MIT) 13 | http://opensource.org/licenses/MIT 14 | repo 15 | 16 | 17 | 18 | http://github.com/tim-group/java-statsd-client 19 | scm:git:git://github.com/tim-group/java-statsd-client.git 20 | scm:git:git@github.com:tim-group/java-statsd-client.git 21 | 22 | 23 | 24 | scarytom 25 | Tom Denley 26 | tom.denley@timgroup.com 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | java-statsd-client 2 | ================== 3 | 4 | [![Build Status](https://travis-ci.org/tim-group/java-statsd-client.svg?branch=master)](https://travis-ci.org/tim-group/java-statsd-client) 5 | 6 | A statsd client library implemented in Java. Allows for Java applications to easily communicate with statsd. 7 | 8 | Downloads 9 | --------- 10 | The client jar is distributed via maven central, and can be downloaded [here](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.timgroup%20a%3Ajava-statsd-client). 11 | 12 | ```xml 13 | 14 | com.timgroup 15 | java-statsd-client 16 | 3.0.1 17 | 18 | ``` 19 | 20 | Usage 21 | ----- 22 | ```java 23 | import com.timgroup.statsd.StatsDClient; 24 | import com.timgroup.statsd.NonBlockingStatsDClient; 25 | 26 | public class Foo { 27 | private static final StatsDClient statsd = new NonBlockingStatsDClient("my.prefix", "statsd-host", 8125); 28 | 29 | public static final void main(String[] args) { 30 | statsd.incrementCounter("bar"); 31 | statsd.recordGaugeValue("baz", 100); 32 | statsd.recordExecutionTime("bag", 25); 33 | statsd.recordSetEvent("qux", "one"); 34 | } 35 | } 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /src/main/java/com/timgroup/statsd/NonBlockingUdpSender.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.nio.ByteBuffer; 6 | import java.nio.channels.DatagramChannel; 7 | import java.nio.charset.Charset; 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.ThreadFactory; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public final class NonBlockingUdpSender { 14 | private final Charset encoding; 15 | private final DatagramChannel clientSocket; 16 | private final ExecutorService executor; 17 | private StatsDClientErrorHandler handler; 18 | 19 | public NonBlockingUdpSender(String hostname, int port, Charset encoding, StatsDClientErrorHandler handler) throws IOException { 20 | this.encoding = encoding; 21 | this.handler = handler; 22 | this.clientSocket = DatagramChannel.open(); 23 | this.clientSocket.connect(new InetSocketAddress(hostname, port)); 24 | 25 | this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() { 26 | final ThreadFactory delegate = Executors.defaultThreadFactory(); 27 | @Override public Thread newThread(Runnable r) { 28 | Thread result = delegate.newThread(r); 29 | result.setName("StatsD-" + result.getName()); 30 | result.setDaemon(true); 31 | return result; 32 | } 33 | }); 34 | } 35 | 36 | public void stop() { 37 | try { 38 | executor.shutdown(); 39 | executor.awaitTermination(30, TimeUnit.SECONDS); 40 | } 41 | catch (Exception e) { 42 | handler.handle(e); 43 | } 44 | finally { 45 | if (clientSocket != null) { 46 | try { 47 | clientSocket.close(); 48 | } 49 | catch (Exception e) { 50 | handler.handle(e); 51 | } 52 | } 53 | } 54 | } 55 | 56 | public void send(final String message) { 57 | try { 58 | executor.execute(new Runnable() { 59 | @Override public void run() { 60 | blockingSend(message); 61 | } 62 | }); 63 | } 64 | catch (Exception e) { 65 | handler.handle(e); 66 | } 67 | } 68 | 69 | private void blockingSend(String message) { 70 | try { 71 | final byte[] sendData = message.getBytes(encoding); 72 | clientSocket.write(ByteBuffer.wrap(sendData)); 73 | } catch (Exception e) { 74 | handler.handle(e); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/com/timgroup/statsd/ConvenienceMethodProvidingStatsDClient.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | public abstract class ConvenienceMethodProvidingStatsDClient implements StatsDClient { 4 | 5 | public ConvenienceMethodProvidingStatsDClient() { 6 | super(); 7 | } 8 | 9 | @Override 10 | public final void count(String aspect, long delta) { 11 | count(aspect, delta, 1.0); 12 | } 13 | 14 | /** 15 | * Convenience method equivalent to {@link #count(String, long)} with a value of 1. 16 | */ 17 | @Override 18 | public final void incrementCounter(String aspect) { 19 | count(aspect, 1); 20 | } 21 | 22 | /** 23 | * Convenience method equivalent to {@link #incrementCounter(String)}. 24 | */ 25 | @Override 26 | public final void increment(String aspect) { 27 | incrementCounter(aspect); 28 | } 29 | 30 | /** 31 | * Convenience method equivalent to {@link #count(String, long)} with a value of -1. 32 | */ 33 | @Override 34 | public final void decrementCounter(String aspect) { 35 | count(aspect, -1); 36 | } 37 | 38 | /** 39 | * Convenience method equivalent to {@link #decrementCounter(String)}. 40 | */ 41 | @Override 42 | public final void decrement(String aspect) { 43 | decrementCounter(aspect); 44 | } 45 | 46 | /** 47 | * Convenience method equivalent to {@link #recordGaugeValue(String, long)}. 48 | */ 49 | @Override 50 | public final void gauge(String aspect, long value) { 51 | recordGaugeValue(aspect, value); 52 | } 53 | 54 | /** 55 | * Convenience method equivalent to {@link #recordGaugeValue(String, double)}. 56 | */ 57 | @Override 58 | public final void gauge(String aspect, double value) { 59 | recordGaugeValue(aspect, value); 60 | } 61 | 62 | /** 63 | * Convenience method equivalent to {@link #recordSetEvent(String, String)}. 64 | */ 65 | @Override 66 | public final void set(String aspect, String eventName) { 67 | recordSetEvent(aspect, eventName); 68 | } 69 | 70 | /** 71 | * Convenience method equivalent to {@link #recordExecutionTime(String, long)}. 72 | */ 73 | @Override 74 | public final void time(String aspect, long timeInMs) { 75 | recordExecutionTime(aspect, timeInMs); 76 | } 77 | 78 | @Override 79 | public final void recordExecutionTime(String aspect, long timeInMs) { 80 | recordExecutionTime(aspect, timeInMs, 1.0); 81 | } 82 | 83 | @Override 84 | public void recordExecutionTimeToNow(String aspect, long systemTimeMillisAtStart) { 85 | time(aspect, Math.max(0, System.currentTimeMillis() - systemTimeMillisAtStart)); 86 | } 87 | } -------------------------------------------------------------------------------- /src/main/java/com/timgroup/statsd/StatsDClient.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | /** 4 | * Describes a client connection to a StatsD server, which may be used to post metrics 5 | * in the form of counters, timers, and gauges. 6 | * 7 | *

Four key methods are provided for the submission of data-points for the application under 8 | * scrutiny: 9 | *

15 | * 16 | * @author Tom Denley 17 | * 18 | */ 19 | public interface StatsDClient { 20 | 21 | /** 22 | * Cleanly shut down this StatsD client. This method may throw an exception if 23 | * the socket cannot be closed. 24 | */ 25 | void stop(); 26 | 27 | /** 28 | * Adjusts the specified counter by a given delta. 29 | * 30 | *

This method is non-blocking and is guaranteed not to throw an exception.

31 | * 32 | * @param aspect 33 | * the name of the counter to adjust 34 | * @param delta 35 | * the amount to adjust the counter by 36 | */ 37 | void count(String aspect, long delta); 38 | 39 | /** 40 | * Adjusts the specified counter by a given delta. 41 | * 42 | *

This method is non-blocking and is guaranteed not to throw an exception.

43 | * 44 | * @param aspect 45 | * the name of the counter to adjust 46 | * @param delta 47 | * the amount to adjust the counter by 48 | * @param sampleRate 49 | * the sampling rate being employed. For example, a rate of 0.1 would tell StatsD that this counter is being sent 50 | * sampled every 1/10th of the time. 51 | */ 52 | void count(String aspect, long delta, double sampleRate); 53 | 54 | /** 55 | * Increments the specified counter by one. 56 | * 57 | *

This method is non-blocking and is guaranteed not to throw an exception.

58 | * 59 | * @param aspect 60 | * the name of the counter to increment 61 | */ 62 | void incrementCounter(String aspect); 63 | 64 | /** 65 | * Convenience method equivalent to {@link #incrementCounter(String)}. 66 | */ 67 | void increment(String aspect); 68 | 69 | /** 70 | * Decrements the specified counter by one. 71 | * 72 | *

This method is non-blocking and is guaranteed not to throw an exception.

73 | * 74 | * @param aspect 75 | * the name of the counter to decrement 76 | */ 77 | void decrementCounter(String aspect); 78 | 79 | /** 80 | * Convenience method equivalent to {@link #decrementCounter(String)}. 81 | */ 82 | void decrement(String aspect); 83 | 84 | /** 85 | * Records the latest fixed value for the specified named gauge. 86 | * 87 | *

This method is non-blocking and is guaranteed not to throw an exception.

88 | * 89 | * @param aspect 90 | * the name of the gauge 91 | * @param value 92 | * the new reading of the gauge 93 | */ 94 | void recordGaugeValue(String aspect, long value); 95 | 96 | /** 97 | * Convenience method equivalent to {@link #recordGaugeValue(String, long)} but for double values. 98 | */ 99 | void recordGaugeValue(String aspect, double value); 100 | 101 | /** 102 | * Records a change in the value of the specified named gauge. 103 | * 104 | *

This method is non-blocking and is guaranteed not to throw an exception.

105 | * 106 | * @param aspect 107 | * the name of the gauge 108 | * @param delta 109 | * the +/- delta to apply to the gauge 110 | */ 111 | void recordGaugeDelta(String aspect, long delta); 112 | 113 | /** 114 | * Convenience method equivalent to {@link #recordGaugeDelta(String, long)} but for double deltas. 115 | */ 116 | void recordGaugeDelta(String aspect, double delta); 117 | 118 | /** 119 | * Convenience method equivalent to {@link #recordGaugeValue(String, long)}. 120 | */ 121 | void gauge(String aspect, long value); 122 | 123 | /** 124 | * Convenience method equivalent to {@link #recordGaugeValue(String, double)}. 125 | */ 126 | void gauge(String aspect, double value); 127 | 128 | /** 129 | * StatsD supports counting unique occurrences of events between flushes, Call this method to records an occurrence 130 | * of the specified named event. 131 | * 132 | *

This method is non-blocking and is guaranteed not to throw an exception.

133 | * 134 | * @param aspect 135 | * the name of the set 136 | * @param eventName 137 | * the value to be added to the set 138 | */ 139 | void recordSetEvent(String aspect, String eventName); 140 | 141 | /** 142 | * Convenience method equivalent to {@link #recordSetEvent(String, String)}. 143 | */ 144 | void set(String aspect, String eventName); 145 | 146 | /** 147 | * Records an execution time in milliseconds for the specified named operation. 148 | * 149 | *

This method is non-blocking and is guaranteed not to throw an exception.

150 | * 151 | * @param aspect 152 | * the name of the timed operation 153 | * @param timeInMs 154 | * the time in milliseconds 155 | */ 156 | void recordExecutionTime(String aspect, long timeInMs); 157 | 158 | /** 159 | * Adjusts the specified counter by a given delta. 160 | * 161 | *

This method is non-blocking and is guaranteed not to throw an exception.

162 | * 163 | * @param aspect 164 | * the name of the counter to adjust 165 | * @param delta 166 | * the amount to adjust the counter by 167 | * @param sampleRate 168 | * the sampling rate being employed. For example, a rate of 0.1 would tell StatsD that this timer is being sent 169 | * sampled every 1/10th of the time, so that it updates its timer_counters appropriately. 170 | */ 171 | void recordExecutionTime(String aspect, long timeInMs, double sampleRate); 172 | 173 | /** 174 | * Records an execution time in milliseconds for the specified named operation. The execution 175 | * time is calculated as the delta between the specified start time and the current system 176 | * time (using {@link System#currentTimeMillis()}) 177 | * 178 | *

This method is non-blocking and is guaranteed not to throw an exception.

179 | * 180 | * @param aspect 181 | * the name of the timed operation 182 | * @param timeInMs 183 | * the system time, in millis, at the start of the operation that has just completed 184 | */ 185 | void recordExecutionTimeToNow(String aspect, long systemTimeMillisAtStart); 186 | 187 | /** 188 | * Convenience method equivalent to {@link #recordExecutionTime(String, long)}. 189 | */ 190 | void time(String aspect, long value); 191 | 192 | } -------------------------------------------------------------------------------- /src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | import java.nio.charset.Charset; 4 | import java.text.NumberFormat; 5 | import java.util.Locale; 6 | 7 | /** 8 | * A simple StatsD client implementation facilitating metrics recording. 9 | * 10 | *

Upon instantiation, this client will establish a socket connection to a StatsD instance 11 | * running on the specified host and port. Metrics are then sent over this connection as they are 12 | * received by the client. 13 | *

14 | * 15 | *

Three key methods are provided for the submission of data-points for the application under 16 | * scrutiny: 17 | *

22 | * From the perspective of the application, these methods are non-blocking, with the resulting 23 | * IO operations being carried out in a separate thread. Furthermore, these methods are guaranteed 24 | * not to throw an exception which may disrupt application execution. 25 | *

26 | * 27 | *

As part of a clean system shutdown, the {@link #stop()} method should be invoked 28 | * on any StatsD clients.

29 | * 30 | * @author Tom Denley 31 | * 32 | */ 33 | public final class NonBlockingStatsDClient extends ConvenienceMethodProvidingStatsDClient { 34 | 35 | private static final Charset STATS_D_ENCODING = Charset.forName("UTF-8"); 36 | 37 | private static final StatsDClientErrorHandler NO_OP_HANDLER = new StatsDClientErrorHandler() { 38 | @Override public void handle(Exception e) { /* No-op */ } 39 | }; 40 | 41 | private final String prefix; 42 | private final NonBlockingUdpSender sender; 43 | 44 | /** 45 | * Create a new StatsD client communicating with a StatsD instance on the 46 | * specified host and port. All messages send via this client will have 47 | * their keys prefixed with the specified string. The new client will 48 | * attempt to open a connection to the StatsD server immediately upon 49 | * instantiation, and may throw an exception if that a connection cannot 50 | * be established. Once a client has been instantiated in this way, all 51 | * exceptions thrown during subsequent usage are consumed, guaranteeing 52 | * that failures in metrics will not affect normal code execution. 53 | * 54 | * @param prefix 55 | * the prefix to apply to keys sent via this client (can be null or empty for no prefix) 56 | * @param hostname 57 | * the host name of the targeted StatsD server 58 | * @param port 59 | * the port of the targeted StatsD server 60 | * @throws StatsDClientException 61 | * if the client could not be started 62 | */ 63 | public NonBlockingStatsDClient(String prefix, String hostname, int port) throws StatsDClientException { 64 | this(prefix, hostname, port, NO_OP_HANDLER); 65 | } 66 | 67 | /** 68 | * Create a new StatsD client communicating with a StatsD instance on the 69 | * specified host and port. All messages send via this client will have 70 | * their keys prefixed with the specified string. The new client will 71 | * attempt to open a connection to the StatsD server immediately upon 72 | * instantiation, and may throw an exception if that a connection cannot 73 | * be established. Once a client has been instantiated in this way, all 74 | * exceptions thrown during subsequent usage are passed to the specified 75 | * handler and then consumed, guaranteeing that failures in metrics will 76 | * not affect normal code execution. 77 | * 78 | * @param prefix 79 | * the prefix to apply to keys sent via this client (can be null or empty for no prefix) 80 | * @param hostname 81 | * the host name of the targeted StatsD server 82 | * @param port 83 | * the port of the targeted StatsD server 84 | * @param errorHandler 85 | * handler to use when an exception occurs during usage 86 | * @throws StatsDClientException 87 | * if the client could not be started 88 | */ 89 | public NonBlockingStatsDClient(String prefix, String hostname, int port, StatsDClientErrorHandler errorHandler) throws StatsDClientException { 90 | this.prefix = (prefix == null || prefix.trim().isEmpty()) ? "" : (prefix.trim() + "."); 91 | 92 | try { 93 | this.sender = new NonBlockingUdpSender(hostname, port, STATS_D_ENCODING, errorHandler); 94 | } catch (Exception e) { 95 | throw new StatsDClientException("Failed to start StatsD client", e); 96 | } 97 | } 98 | 99 | /** 100 | * Cleanly shut down this StatsD client. This method may throw an exception if 101 | * the socket cannot be closed. 102 | */ 103 | @Override 104 | public void stop() { 105 | sender.stop(); 106 | } 107 | 108 | /** 109 | * Adjusts the specified counter by a given delta. 110 | * 111 | *

This method is non-blocking and is guaranteed not to throw an exception.

112 | * 113 | * @param aspect 114 | * the name of the counter to adjust 115 | * @param delta 116 | * the amount to adjust the counter by 117 | * @param sampleRate 118 | * the sampling rate being employed. For example, a rate of 0.1 would tell StatsD that this counter is being sent 119 | * sampled every 1/10th of the time. 120 | */ 121 | @Override 122 | public void count(String aspect, long delta, double sampleRate) { 123 | send(messageFor(aspect, Long.toString(delta), "c", sampleRate)); 124 | } 125 | 126 | /** 127 | * Records the latest fixed value for the specified named gauge. 128 | * 129 | *

This method is non-blocking and is guaranteed not to throw an exception.

130 | * 131 | * @param aspect 132 | * the name of the gauge 133 | * @param value 134 | * the new reading of the gauge 135 | */ 136 | @Override 137 | public void recordGaugeValue(String aspect, long value) { 138 | recordGaugeCommon(aspect, Long.toString(value), value < 0, false); 139 | } 140 | 141 | @Override 142 | public void recordGaugeValue(String aspect, double value) { 143 | recordGaugeCommon(aspect, stringValueOf(value), value < 0, false); 144 | } 145 | 146 | @Override 147 | public void recordGaugeDelta(String aspect, long value) { 148 | recordGaugeCommon(aspect, Long.toString(value), value < 0, true); 149 | } 150 | 151 | @Override 152 | public void recordGaugeDelta(String aspect, double value) { 153 | recordGaugeCommon(aspect, stringValueOf(value), value < 0, true); 154 | } 155 | 156 | private void recordGaugeCommon(String aspect, String value, boolean negative, boolean delta) { 157 | final StringBuilder message = new StringBuilder(); 158 | if (!delta && negative) { 159 | message.append(messageFor(aspect, "0", "g")).append('\n'); 160 | } 161 | message.append(messageFor(aspect, (delta && !negative) ? ("+" + value) : value, "g")); 162 | send(message.toString()); 163 | } 164 | 165 | /** 166 | * StatsD supports counting unique occurrences of events between flushes, Call this method to records an occurrence 167 | * of the specified named event. 168 | * 169 | *

This method is non-blocking and is guaranteed not to throw an exception.

170 | * 171 | * @param aspect 172 | * the name of the set 173 | * @param eventName 174 | * the value to be added to the set 175 | */ 176 | @Override 177 | public void recordSetEvent(String aspect, String eventName) { 178 | send(messageFor(aspect, eventName, "s")); 179 | } 180 | 181 | /** 182 | * Records an execution time in milliseconds for the specified named operation. 183 | * 184 | *

This method is non-blocking and is guaranteed not to throw an exception.

185 | * 186 | * @param aspect 187 | * the name of the timed operation 188 | * @param timeInMs 189 | * the time in milliseconds 190 | */ 191 | @Override 192 | public void recordExecutionTime(String aspect, long timeInMs, double sampleRate) { 193 | send(messageFor(aspect, Long.toString(timeInMs), "ms", sampleRate)); 194 | } 195 | 196 | private String messageFor(String aspect, String value, String type) { 197 | return messageFor(aspect, value, type, 1.0); 198 | } 199 | 200 | private String messageFor(String aspect, String value, String type, double sampleRate) { 201 | final String message = prefix + aspect + ':' + value + '|' + type; 202 | return (sampleRate == 1.0) 203 | ? message 204 | : (message + "|@" + stringValueOf(sampleRate)); 205 | } 206 | 207 | private void send(final String message) { 208 | sender.send(message); 209 | } 210 | 211 | private String stringValueOf(double value) { 212 | NumberFormat formatter = NumberFormat.getInstance(Locale.US); 213 | formatter.setGroupingUsed(false); 214 | formatter.setMaximumFractionDigits(19); 215 | return formatter.format(value); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/test/java/com/timgroup/statsd/NonBlockingStatsDClientTest.java: -------------------------------------------------------------------------------- 1 | package com.timgroup.statsd; 2 | 3 | import static java.lang.Long.valueOf; 4 | import static junit.framework.Assert.assertTrue; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.contains; 7 | import static org.hamcrest.Matchers.startsWith; 8 | 9 | import java.net.DatagramPacket; 10 | import java.net.DatagramSocket; 11 | import java.net.SocketException; 12 | import java.nio.charset.Charset; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | import org.hamcrest.Matchers; 19 | import org.junit.After; 20 | import org.junit.Test; 21 | 22 | public final class NonBlockingStatsDClientTest { 23 | 24 | private static final int STATSD_SERVER_PORT = 17254; 25 | 26 | private final NonBlockingStatsDClient client = new NonBlockingStatsDClient("my.prefix", "localhost", STATSD_SERVER_PORT); 27 | private final DummyStatsDServer server = new DummyStatsDServer(STATSD_SERVER_PORT); 28 | 29 | @After 30 | public void stop() throws Exception { 31 | client.stop(); 32 | server.stop(); 33 | } 34 | 35 | @Test(timeout=5000L) public void 36 | sends_counter_value_to_statsd() throws Exception { 37 | client.count("mycount", Long.MAX_VALUE); 38 | server.waitForMessage(); 39 | 40 | assertThat(server.messagesReceived(), contains("my.prefix.mycount:9223372036854775807|c")); 41 | } 42 | 43 | @Test(timeout=5000L) public void 44 | sends_counter_value_with_rate_to_statsd() throws Exception { 45 | client.count("mycount", Long.MAX_VALUE, 0.00024); 46 | server.waitForMessage(); 47 | 48 | assertThat(server.messagesReceived(), contains("my.prefix.mycount:9223372036854775807|c|@0.00024")); 49 | } 50 | 51 | @Test(timeout=5000L) public void 52 | sends_counter_increment_to_statsd() throws Exception { 53 | client.incrementCounter("myinc"); 54 | server.waitForMessage(); 55 | 56 | assertThat(server.messagesReceived(), contains("my.prefix.myinc:1|c")); 57 | } 58 | 59 | @Test(timeout=5000L) public void 60 | sends_counter_decrement_to_statsd() throws Exception { 61 | client.decrementCounter("mydec"); 62 | server.waitForMessage(); 63 | 64 | assertThat(server.messagesReceived(), contains("my.prefix.mydec:-1|c")); 65 | } 66 | 67 | @Test(timeout=5000L) public void 68 | sends_gauge_to_statsd() throws Exception { 69 | client.recordGaugeValue("mygauge", Long.MAX_VALUE); 70 | server.waitForMessage(); 71 | 72 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:9223372036854775807|g")); 73 | } 74 | 75 | @Test(timeout=5000L) public void 76 | sends_fractional_gauge_to_statsd() throws Exception { 77 | client.recordGaugeValue("mygauge", 423.123456789d); 78 | server.waitForMessage(); 79 | 80 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:423.123456789|g")); 81 | } 82 | 83 | @Test(timeout=5000L) public void 84 | sends_large_fractional_gauge_to_statsd() throws Exception { 85 | client.recordGaugeValue("mygauge", 423423423.9d); 86 | server.waitForMessage(); 87 | 88 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:423423423.9|g")); 89 | } 90 | 91 | @Test(timeout=5000L) public void 92 | sends_zero_gauge_to_statsd() throws Exception { 93 | client.recordGaugeValue("mygauge", 0L); 94 | server.waitForMessage(); 95 | 96 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:0|g")); 97 | } 98 | 99 | @Test(timeout=5000L) public void 100 | sends_negagive_gauge_to_statsd_by_resetting_to_zero_first() throws Exception { 101 | client.recordGaugeValue("mygauge", -423L); 102 | server.waitForMessage(); 103 | 104 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:0|g\nmy.prefix.mygauge:-423|g")); 105 | } 106 | 107 | @Test(timeout=5000L) public void 108 | sends_gauge_positive_delta_to_statsd() throws Exception { 109 | client.recordGaugeDelta("mygauge", 423L); 110 | server.waitForMessage(); 111 | 112 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:+423|g")); 113 | } 114 | 115 | @Test(timeout=5000L) public void 116 | sends_gauge_negative_delta_to_statsd() throws Exception { 117 | client.recordGaugeDelta("mygauge", -423L); 118 | server.waitForMessage(); 119 | 120 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:-423|g")); 121 | } 122 | 123 | @Test(timeout=5000L) public void 124 | sends_gauge_zero_delta_to_statsd() throws Exception { 125 | client.recordGaugeDelta("mygauge", 0L); 126 | server.waitForMessage(); 127 | 128 | assertThat(server.messagesReceived(), contains("my.prefix.mygauge:+0|g")); 129 | } 130 | 131 | @Test(timeout=5000L) public void 132 | sends_set_to_statsd() throws Exception { 133 | client.recordSetEvent("myset", "test"); 134 | server.waitForMessage(); 135 | 136 | assertThat(server.messagesReceived(), contains("my.prefix.myset:test|s")); 137 | } 138 | 139 | @Test(timeout=5000L) public void 140 | sends_timer_to_statsd() throws Exception { 141 | client.recordExecutionTime("mytime", 123L); 142 | server.waitForMessage(); 143 | 144 | assertThat(server.messagesReceived(), contains("my.prefix.mytime:123|ms")); 145 | } 146 | 147 | @Test(timeout=5000L) public void 148 | sends_timer_with_rate_to_statsd() throws Exception { 149 | client.recordExecutionTime("mytime", 123L, 0.000123); 150 | server.waitForMessage(); 151 | 152 | assertThat(server.messagesReceived(), contains("my.prefix.mytime:123|ms|@0.000123")); 153 | } 154 | 155 | @Test(timeout=5000L) public void 156 | sends_timer_to_statsd_based_on_specified_start_time_to_now() throws Exception { 157 | final long startTime = System.currentTimeMillis() - 1000L; 158 | 159 | final long beforeCompletionTime = System.currentTimeMillis(); 160 | client.recordExecutionTimeToNow("mytime", startTime); 161 | final long afterCompletionTime = System.currentTimeMillis(); 162 | 163 | server.waitForMessage(); 164 | 165 | long maxExpectedValue = afterCompletionTime - startTime; 166 | long minExpectedValue = beforeCompletionTime - startTime; 167 | final String messageReceived = server.messagesReceived().get(0); 168 | final Matcher resultMatcher = Pattern.compile(".*:(\\d+)\\|ms").matcher(messageReceived); 169 | assertTrue(messageReceived, resultMatcher.matches()); 170 | assertThat(valueOf(resultMatcher.group(1)), Matchers.greaterThanOrEqualTo(minExpectedValue)); 171 | assertThat(valueOf(resultMatcher.group(1)), Matchers.lessThanOrEqualTo(maxExpectedValue)); 172 | } 173 | 174 | @Test(timeout=5000L) public void 175 | sends_timer_of_zero_to_statsd_based_on_specified_start_time_in_the_future() throws Exception { 176 | client.recordExecutionTimeToNow("mytime", System.currentTimeMillis() + 100000L); 177 | server.waitForMessage(); 178 | 179 | assertThat(server.messagesReceived(), contains("my.prefix.mytime:0|ms")); 180 | } 181 | 182 | @Test(timeout=5000L) public void 183 | allows_empty_prefix() { 184 | final NonBlockingStatsDClient emptyPrefixClient = new NonBlockingStatsDClient(" ", "localhost", STATSD_SERVER_PORT); 185 | try { 186 | emptyPrefixClient.count("mycount", 24L); 187 | server.waitForMessage(); 188 | } finally { 189 | emptyPrefixClient.stop(); 190 | } 191 | assertThat(server.messagesReceived(), contains(startsWith("mycount:"))); 192 | } 193 | 194 | @Test(timeout=5000L) public void 195 | allows_null_prefix() { 196 | final NonBlockingStatsDClient nullPrefixClient = new NonBlockingStatsDClient(null, "localhost", STATSD_SERVER_PORT); 197 | try { 198 | nullPrefixClient.count("mycount", 24L); 199 | server.waitForMessage(); 200 | } finally { 201 | nullPrefixClient.stop(); 202 | } 203 | assertThat(server.messagesReceived(), contains(startsWith("mycount:"))); 204 | } 205 | 206 | private static final class DummyStatsDServer { 207 | private final List messagesReceived = new ArrayList(); 208 | private final DatagramSocket server; 209 | 210 | public DummyStatsDServer(int port) { 211 | try { 212 | server = new DatagramSocket(port); 213 | } catch (SocketException e) { 214 | throw new IllegalStateException(e); 215 | } 216 | new Thread(new Runnable() { 217 | @Override public void run() { 218 | try { 219 | final DatagramPacket packet = new DatagramPacket(new byte[256], 256); 220 | server.receive(packet); 221 | messagesReceived.add(new String(packet.getData(), Charset.forName("UTF-8")).trim()); 222 | } catch (Exception e) { } 223 | } 224 | }).start(); 225 | } 226 | 227 | public void stop() { 228 | server.close(); 229 | } 230 | 231 | public void waitForMessage() { 232 | while (messagesReceived.isEmpty()) { 233 | try { 234 | Thread.sleep(50L); 235 | } catch (InterruptedException e) {} 236 | } 237 | } 238 | 239 | public List messagesReceived() { 240 | return new ArrayList(messagesReceived); 241 | } 242 | } 243 | } --------------------------------------------------------------------------------