package io.dropwizard.metrics.collectd; import io.dropwizard.metrics.MetricName; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; 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.nio.charset.StandardCharsets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A client to a Collectd server */ public class Collectd { private static final Logger LOGGER = LoggerFactory.getLogger(Collectd.class); private final String collectdHostname; private final int collectdPort; private String hostname; private InetSocketAddress address; private DatagramChannel datagramChannel = null; /** * Creates a new client which sends data to given address using UDP * * @param hostname * The hostname of the l server * @param port * The port of the Collectd server */ public Collectd(String hostname, int port) { this.collectdHostname = hostname; this.collectdPort = port; this.address = null; } /** * Creates a new client which sends data to given address using UDP * * @param address * the address of the Collectd server */ public Collectd(InetSocketAddress address) { this.collectdHostname = null; this.collectdPort = -1; this.address = address; } public void connect() throws IllegalStateException, IOException { // Only open the channel the first time... if (isConnected()) { throw new IllegalStateException("Already connected"); } try { hostname = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { throw new RuntimeException(e); } if (datagramChannel != null) { datagramChannel.close(); } // Resolve hostname if (collectdHostname != null) { address = new InetSocketAddress(collectdHostname, collectdPort); } datagramChannel = DatagramChannel.open(); } public boolean isConnected() { return datagramChannel != null && !datagramChannel.socket().isClosed(); } public void send(MetricName name, long value, long timestamp, DataType type, long period) { int commaIndex = name.getKey().lastIndexOf(MetricName.SEPARATOR); if (commaIndex == -1) { send(name.getKey(), "0", value, timestamp, type, period); } else { send(name.getKey().substring(0, commaIndex), name.getKey().substring(commaIndex + 1), value, timestamp, type, period); } } public void send(MetricName name, double value, long timestamp, DataType type, long period) { int commaIndex = name.getKey().lastIndexOf(MetricName.SEPARATOR); if (commaIndex == -1) { send(name.getKey(), "0", value, timestamp, type, period); } else { send(name.getKey().substring(0, commaIndex), name.getKey().substring(commaIndex + 1), value, timestamp, type, period); } } public void send(MetricName name, Number value, long timestamp, DataType type, long period) { if ((value instanceof Double) || (value instanceof Float)) { send(name, value.doubleValue(), timestamp, type, period); } else { send(name, value.longValue(), timestamp, type, period); } } public void send(MetricName name, String typeInstance, long value, long timestamp, DataType type, long period) { send(name.getKey(), typeInstance, value, timestamp, type, period); } public void send(MetricName name, String typeInstance, double value, long timestamp, DataType type, long period) { send(name.getKey(), typeInstance, value, timestamp, type, period); } private void send(String name, String typeInstance, long value, long timestamp, DataType dataType, long period) { send(name, typeInstance, value, true, timestamp, dataType, period); } private void send(String name, String typeInstance, double value, long timestamp, DataType dataType, long period) { send(name, typeInstance, Double.doubleToRawLongBits(value), false, timestamp, dataType, period); } private void send(String name, String typeInstance, long value, boolean bigEndian, long timestamp, DataType dataType, long period) { final String pluginInstance; final String plugin; int typeIndex = name.lastIndexOf(MetricName.SEPARATOR); if (typeIndex == -1) { pluginInstance = "0"; plugin = name; } else { pluginInstance = name.substring(typeIndex + 1); plugin = name.substring(0, typeIndex); } ByteArrayOutputStream baos = new ByteArrayOutputStream(Network.BUFFER_SIZE); DataOutputStream buffer = new DataOutputStream(baos); try { writeString(buffer, Network.TYPE_HOST, hostname); writeNumber(buffer, Network.TYPE_TIME, timestamp / 1000); writeString(buffer, Network.TYPE_PLUGIN, plugin); writeString(buffer, Network.TYPE_PLUGIN_INSTANCE, pluginInstance); writeString(buffer, Network.TYPE_TYPE, dataType.name().toLowerCase()); writeString(buffer, Network.TYPE_TYPE_INSTANCE, typeInstance); writeNumber(buffer, Network.TYPE_INTERVAL, period); int len = Network.HEADER_LEN + Network.UINT16_LEN + Network.UINT8_LEN + Network.UINT64_LEN; writeHeader(buffer, Network.TYPE_VALUES, len); buffer.writeShort((short) 1); buffer.write(dataType.getCode()); } catch (IOException e) { LOGGER.error("unable to write bytearray", e); return; } try { if (bigEndian) { buffer.writeLong(value); } else { // copy-paste from java.nio.Bits.putLong: // do not create intermediate ByteBuffer => less GC and memory // footprint buffer.write(long7(value)); buffer.write(long6(value)); buffer.write(long5(value)); buffer.write(long4(value)); buffer.write(long3(value)); buffer.write(long2(value)); buffer.write(long1(value)); buffer.write(long0(value)); } buffer.close(); } catch (IOException e) { LOGGER.error("unable to write bytearray", e); } if (buffer.size() > Network.BUFFER_SIZE) { LOGGER.warn("discard metric: {} size exceed: {}", name, buffer.size()); return; } ByteBuffer byteBuffer = ByteBuffer.wrap(baos.toByteArray()); try { datagramChannel.send(byteBuffer, address); } catch (IOException e) { LOGGER.error("unable to write", e); } } private static byte long7(long x) { return (byte) (x >> 56); } private static byte long6(long x) { return (byte) (x >> 48); } private static byte long5(long x) { return (byte) (x >> 40); } private static byte long4(long x) { return (byte) (x >> 32); } private static byte long3(long x) { return (byte) (x >> 24); } private static byte long2(long x) { return (byte) (x >> 16); } private static byte long1(long x) { return (byte) (x >> 8); } private static byte long0(long x) { return (byte) (x); } private static void writeString(DataOutputStream buffer, int type, String val) throws IOException { if (val == null || val.length() == 0) { return; } int len = Network.HEADER_LEN + val.length() + 1; writeHeader(buffer, type, len); buffer.write(val.getBytes(StandardCharsets.US_ASCII)); buffer.write((byte) '\0'); } private static void writeNumber(DataOutputStream buffer, int type, long val) throws IOException { int len = Network.HEADER_LEN + Network.UINT64_LEN; writeHeader(buffer, type, len); buffer.writeLong(val); } private static void writeHeader(DataOutputStream buffer, int type, int len) throws IOException { buffer.writeShort((short) type); buffer.writeShort((short) len); } }