com.timgroup.statsd.StatsDClient Maven / Gradle / Ivy
Show all versions of java-statsd-client Show documentation
package com.timgroup.statsd;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
* A simple StatsD client implementation facilitating metrics recording.
*
* 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:
*
* - {@link #incrementCounter} - adds one to the value of the specified named counter
* - {@link #recordGaugeValue} - records the latest fixed value for the specified named gauge
* - {@link #recordExecutionTime} - records an execution time in milliseconds for the specified named operation
*
* 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 final class StatsDClient {
private static final StatsDClientErrorHandler NO_OP_HANDLER = new StatsDClientErrorHandler() {
@Override public void handle(Exception e) { /* No-op */ }
};
private final String prefix;
private final DatagramSocket clientSocket;
private final StatsDClientErrorHandler handler;
private final ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
final ThreadFactory delegate = Executors.defaultThreadFactory();
@Override public Thread newThread(Runnable r) {
Thread result = delegate.newThread(r);
result.setName("StatsD-" + result.getName());
return result;
}
});
/**
* Create a new StatsD client communicating with a StatsD instance on the
* specified host and port. All messages send 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
* be established. Once a client has been instantiated in this way, all
* exceptions thrown during subsequent usage are consumed, guaranteeing
* that failures in metrics will not affect normal code execution.
*
* @param prefix
* the prefix to apply to keys sent via this client
* @param hostname
* the host name of the targeted StatsD server
* @param port
* the port of the targeted StatsD server
* @throws StatsDClientException
* if the client could not be started
*/
public StatsDClient(String prefix, String hostname, int port) throws StatsDClientException {
this(prefix, hostname, port, NO_OP_HANDLER);
}
/**
* Create a new StatsD client communicating with a StatsD instance on the
* specified host and port. All messages send 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
* be established. Once a client has been instantiated in this way, all
* exceptions thrown during subsequent usage are passed to the specified
* handler and then consumed, guaranteeing that failures in metrics will
* not affect normal code execution.
*
* @param prefix
* the prefix to apply to keys sent via this client
* @param hostname
* the host name of the targeted StatsD server
* @param port
* the port of the targeted StatsD server
* @param errorHandler
* handler to use when an exception occurs during usage
* @throws StatsDClientException
* if the client could not be started
*/
public StatsDClient(String prefix, String hostname, int port, StatsDClientErrorHandler errorHandler) throws StatsDClientException {
this.prefix = prefix;
this.handler = errorHandler;
try {
this.clientSocket = new DatagramSocket();
this.clientSocket.connect(new InetSocketAddress(hostname, port));
} catch (Exception e) {
throw new StatsDClientException("Failed to start StatsD client", e);
}
}
/**
* Cleanly shut down this StatsD client. This method may throw an exception if
* the socket cannot be closed.
*/
public void stop() {
try {
executor.shutdown();
executor.awaitTermination(30, TimeUnit.SECONDS);
}
catch (Exception e) {
handler.handle(e);
}
finally {
if (clientSocket != null) {
clientSocket.close();
}
}
}
/**
* Adjusts the specified counter by a given delta.
*
* This 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
*/
public void count(String aspect, int delta) {
send(String.format("%s.%s:%d|c", prefix, aspect, delta));
}
/**
* 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
*/
public void incrementCounter(String aspect) {
count(aspect, 1);
}
/**
* Convenience method equivalent to {@link #incrementCounter(String)}.
*/
public void increment(String aspect) {
incrementCounter(aspect);
}
/**
* 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
*/
public void decrementCounter(String aspect) {
count(aspect, -1);
}
/**
* Convenience method equivalent to {@link #decrementCounter(String)}.
*/
public void decrement(String aspect) {
decrementCounter(aspect);
}
/**
* 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
*/
public void recordGaugeValue(String aspect, int value) {
send(String.format("%s.%s:%d|g", prefix, aspect, value));
}
/**
* Convenience method equivalent to {@link #recordGaugeValue(String)}.
*/
public void gauge(String aspect, int value) {
recordGaugeValue(aspect, value);
}
/**
* 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
*/
public void recordExecutionTime(String aspect, int timeInMs) {
send(String.format("%s.%s:%d|ms", prefix, aspect, timeInMs));
}
/**
* Convenience method equivalent to {@link #recordExecutionTime(String)}.
*/
public void time(String aspect, int value) {
recordExecutionTime(aspect, value);
}
private void send(final String message) {
try {
executor.execute(new Runnable() {
@Override public void run() {
blockingSend(message);
}
});
}
catch (Exception e) {
handler.handle(e);
}
}
private void blockingSend(String message) {
try {
final byte[] sendData = message.getBytes();
final DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length);
clientSocket.send(sendPacket);
} catch (Exception e) {
handler.handle(e);
}
}
}