All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.timgroup.statsd.NonBlockingStatsDClient Maven / Gradle / Ivy

Go to download

A tiny library allowing Java applications to communicate with statsd instances easily.

There is a newer version: 3.1.0
Show newest version
package com.timgroup.statsd;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Locale;
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 NonBlockingStatsDClient extends ConvenienceMethodProvidingStatsDClient { private static final Charset STATS_D_ENCODING = Charset.forName("UTF-8"); 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()); result.setDaemon(true); 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 (can be null or empty for no prefix) * @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 NonBlockingStatsDClient(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 (can be null or empty for no prefix) * @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 NonBlockingStatsDClient(String prefix, String hostname, int port, StatsDClientErrorHandler errorHandler) throws StatsDClientException { this.prefix = (prefix == null || prefix.trim().isEmpty()) ? "" : (prefix.trim() + "."); 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. */ @Override 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 * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would tell StatsD that this counter is being sent * sampled every 1/10th of the time. */ @Override public void count(String aspect, long delta, double sampleRate) { send(messageFor(aspect, delta, "c", sampleRate)); } /** * 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 */ @Override public void recordGaugeValue(String aspect, long value) { String message = messageFor(aspect, value, "g"); if (value < 0) { message = messageFor(aspect, 0, "g") + "\n" + message; } send(message); } public void recordGaugeDelta(String aspect, long value) { send(messageFor(aspect, (value < 0) ? value : ("+" + value), "g")); } /** * StatsD supports counting unique occurrences of events between flushes, Call this method to records an occurrence * of the specified named event. * *

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

* * @param aspect * the name of the set * @param eventName * the value to be added to the set */ @Override public void recordSetEvent(String aspect, String eventName) { send(messageFor(aspect, eventName, "s")); } /** * 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 */ @Override public void recordExecutionTime(String aspect, long timeInMs, double sampleRate) { send(messageFor(aspect, timeInMs, "ms", sampleRate)); } private String messageFor(String aspect, Object value, String type) { return messageFor(aspect, value, type, 1.0); } private String messageFor(String aspect, Object value, String type, double sampleRate) { final String messageFormat = (sampleRate == 1.0) ? "%s%s:%s|%s" : "%s%s:%s|%s@%f"; return String.format((Locale)null, messageFormat, prefix, aspect, value, type, sampleRate); } 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(STATS_D_ENCODING); final DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length); clientSocket.send(sendPacket); } catch (Exception e) { handler.handle(e); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy