/*
* Seldon -- open source prediction engine
* =======================================
*
* Copyright 2011-2015 Seldon Technologies Ltd and Rummble Ltd (http://www.seldon.io/)
*
* ********************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ********************************************************************************************
*/
package io.seldon.api.statsd;
/**
* StatsdClient.java
*
* (C) 2011 Meetup, Inc.
* Author: Andrew Gwozdziewycz <andrew@meetup.com>, @apgwoz
*
*
*
* Example usage:
*
* StatsdClient client = new StatsdClient("statsd.example.com", 8125);
* // increment by 1
* client.increment("foo.bar.baz");
* // increment by 10
* client.increment("foo.bar.baz", 10);
* // sample rate
* client.increment("foo.bar.baz", 10, .1);
* // increment multiple keys by 1
* client.increment("foo.bar.baz", "foo.bar.boo", "foo.baz.bar");
* // increment multiple keys by 10 -- yeah, it's "backwards"
* client.increment(10, "foo.bar.baz", "foo.bar.boo", "foo.baz.bar");
* // multiple keys with a sample rate
* client.increment(10, .1, "foo.bar.baz", "foo.bar.boo", "foo.baz.bar");
*
* Note: For best results, and greater availability, you'll probably want to
* create a wrapper class which creates a static client and proxies to it.
*
* You know... the "Java way."
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Locale;
import java.util.Random;
import org.apache.log4j.Logger;
public class StatsdClient {
//private static final Random RNG = new Random();
private static final ThreadLocal<Random> safeRNG = new ThreadLocal<Random>() {
@Override protected Random initialValue() {
return new Random();
}
};
private static final Logger log = Logger.getLogger(StatsdClient.class.getName());
private final InetSocketAddress _address;
private final DatagramChannel _channel;
public StatsdClient(String host, int port) throws UnknownHostException, IOException {
this(InetAddress.getByName(host), port);
}
public StatsdClient(InetAddress host, int port) throws IOException {
_address = new InetSocketAddress(host, port);
_channel = DatagramChannel.open();
}
public boolean timing(String key, int value) {
return timing(key, value, 1.0);
}
public boolean timing(String key, int value, double sampleRate) {
return send(sampleRate, String.format(Locale.ENGLISH, "%s:%d|ms", key, value));
}
public boolean decrement(String key) {
return increment(key, -1, 1.0);
}
public boolean decrement(String key, int magnitude) {
return decrement(key, magnitude, 1.0);
}
public boolean decrement(String key, int magnitude, double sampleRate) {
magnitude = magnitude < 0 ? magnitude : -magnitude;
return increment(key, magnitude, sampleRate);
}
public boolean decrement(String... keys) {
return increment(-1, 1.0, keys);
}
public boolean decrement(int magnitude, String... keys) {
magnitude = magnitude < 0 ? magnitude : -magnitude;
return increment(magnitude, 1.0, keys);
}
public boolean decrement(int magnitude, double sampleRate, String... keys) {
magnitude = magnitude < 0 ? magnitude : -magnitude;
return increment(magnitude, sampleRate, keys);
}
public boolean increment(String key) {
return increment(key, 1, 1.0);
}
public boolean increment(String key, int magnitude) {
return increment(key, magnitude, 1.0);
}
public boolean increment(String key, int magnitude, double sampleRate) {
String stat = String.format(Locale.ENGLISH, "%s:%s|c", key, magnitude);
return send(sampleRate, stat);
}
public boolean increment(int magnitude, double sampleRate, String... keys) {
String[] stats = new String[keys.length];
for (int i = 0; i < keys.length; i++) {
stats[i] = String.format(Locale.ENGLISH, "%s:%s|c", keys[i], magnitude);
}
return send(sampleRate, stats);
}
public boolean gauge(String key, double magnitude){
return gauge(key, magnitude, 1.0);
}
public boolean gauge(String key, double magnitude, double sampleRate){
final String stat = String.format(Locale.ENGLISH, "%s:%s|g", key, magnitude);
return send(sampleRate, stat);
}
private boolean send(double sampleRate, String... stats) {
boolean retval = false; // didn't send anything
if (sampleRate < 1.0) {
for (String stat : stats) {
if (safeRNG.get().nextDouble() <= sampleRate) {
stat = String.format(Locale.ENGLISH, "%s|@%f", stat, sampleRate);
if (doSend(stat)) {
retval = true;
}
}
}
} else {
for (String stat : stats) {
if (doSend(stat)) {
retval = true;
}
}
}
return retval;
}
private boolean doSend(final String stat) {
try {
final byte[] data = stat.getBytes("utf-8");
final ByteBuffer buff = ByteBuffer.wrap(data);
final int nbSentBytes = _channel.send(buff, _address);
if (data.length == nbSentBytes) {
return true;
} else {
log.error(String.format(
"Could not send entirely stat %s to host %s:%d. Only sent %d bytes out of %d bytes", stat,
_address.getHostName(), _address.getPort(), nbSentBytes, data.length));
return false;
}
} catch (IOException e) {
log.error(
String.format("Could not send stat %s to host %s:%d", stat, _address.getHostName(),
_address.getPort()), e);
return false;
}
}
}