├── .gitignore ├── src ├── main │ ├── java │ │ └── com │ │ │ ├── aphyr │ │ │ └── riemann │ │ │ │ └── client │ │ │ │ ├── MsgTooLargeException.java │ │ │ │ ├── Null.java │ │ │ │ ├── ServerError.java │ │ │ │ ├── RiemannUDPClient.java │ │ │ │ ├── RiemannClient.java │ │ │ │ ├── RiemannRetryingTcpClient.java │ │ │ │ ├── RiemannScheduler.java │ │ │ │ ├── RiemannTcpClient.java │ │ │ │ ├── EventDSL.java │ │ │ │ └── AbstractRiemannClient.java │ │ │ └── yammer │ │ │ └── metrics │ │ │ └── reporting │ │ │ └── RiemannReporter.java │ └── proto │ │ └── riemann │ │ └── proto.proto └── test │ └── java │ └── riemann │ └── java │ └── client │ └── tests │ ├── RiemannReporterConfigTest.java │ ├── NonReplyingTcpServerTest.java │ ├── TcpClientTest.java │ └── AbstractClientTest.java ├── README.markdown ├── riemann-java-client.iml ├── pom.xml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | bin 3 | .* 4 | -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/MsgTooLargeException.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | // Thrown when a message is too large to send over the specified transport. 4 | public class MsgTooLargeException extends Exception { 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/Null.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | // null has a type, but you can't use it! Since the JVM doesn't have Maybe[], we'll just make one up instead. 4 | // The *only* acceptable argument for f(Null n) is f(null). 5 | public class Null { 6 | private Null() {} 7 | } -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/ServerError.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | public class ServerError extends Exception { 4 | public final String message; 5 | 6 | public ServerError(String message) { 7 | this.message = message; 8 | } 9 | 10 | public String getMessage() { 11 | return message; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/proto/riemann/proto.proto: -------------------------------------------------------------------------------- 1 | option java_package = "com.aphyr.riemann"; 2 | option java_outer_classname = "Proto"; 3 | 4 | message State { 5 | optional int64 time = 1; 6 | optional string state = 2; 7 | optional string service = 3; 8 | optional string host = 4; 9 | optional string description = 5; 10 | optional bool once = 6; 11 | repeated string tags = 7; 12 | optional float ttl = 8; 13 | } 14 | 15 | message Event { 16 | optional int64 time = 1; 17 | optional string state = 2; 18 | optional string service = 3; 19 | optional string host = 4; 20 | optional string description = 5; 21 | repeated string tags = 7; 22 | optional float ttl = 8; 23 | 24 | optional sint64 metric_sint64 = 13; 25 | optional double metric_d = 14; 26 | optional float metric_f = 15; 27 | } 28 | 29 | message Query { 30 | optional string string = 1; 31 | } 32 | 33 | message Msg { 34 | optional bool ok = 2; 35 | optional string error = 3; 36 | repeated State states = 4; 37 | optional Query query = 5; 38 | repeated Event events = 6; 39 | } 40 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | This project encompasses: 2 | 3 | 1. A Java client for the Riemann protocol 4 | 2. The Riemann protocol buffer definition, and 5 | 3. Its corresponding (autogenerated) Java classes 6 | 7 | # Example 8 | 9 | ``` java 10 | RiemannClient c = new RiemannTcpClient(new InetSocketAddress("my.riemann.server", 5555)); 11 | c.event(). 12 | service("fridge"). 13 | state("running"). 14 | metric(5.3). 15 | tags("appliance", "cold"). 16 | send(); 17 | 18 | c.query("tagged \"cold\" and metric > 0"); // => List; 19 | ``` 20 | 21 | # Status 22 | 23 | The TCP client is in its early stages; it does not handle server error 24 | responses correctly and offers no pooling or retries. The top-level API for 25 | sending events and querying the index is functional and threadsafe. A small DSL 26 | is available to make sending events easier. 27 | 28 | 29 | # Next steps 30 | 31 | - Set host automatically. 32 | - Add error handling to TcpClient--raise exceptions when msg.getOk() is false. 33 | - Write a UDP client. 34 | - Write a hybrid client which dispatches to UDP and TCP clients as appropriate. 35 | 36 | # Hacking 37 | 38 | You'll need [protoc 2.3.0](http://code.google.com/p/protobuf/downloads/detail?name=protobuf-2.3.0.tar.bz2&can=2&q=). After that, `mvn package` should build a JAR, and `mvn install` will drop it in your local repository. 39 | -------------------------------------------------------------------------------- /src/test/java/riemann/java/client/tests/RiemannReporterConfigTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import com.yammer.metrics.reporting.RiemannReporter; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import static junit.framework.Assert.assertEquals; 10 | 11 | public class RiemannReporterConfigTest { 12 | 13 | private RiemannReporter.ConfigBuilder builder; 14 | 15 | @Before 16 | public void setUp() { 17 | builder = RiemannReporter.Config.newBuilder(); 18 | } 19 | 20 | @Test 21 | public void testDefaultConfigBuilding() { 22 | RiemannReporter.Config c = builder.build(); 23 | assertEquals(c.port, 5555); 24 | assertEquals(c.unit, TimeUnit.SECONDS); 25 | assertEquals(c.separator, " "); 26 | assertEquals(c.host, "localhost"); 27 | } 28 | 29 | @Test 30 | public void testConfigBuilding() { 31 | RiemannReporter.Config c = builder.host("127.0.0.1") 32 | .unit(TimeUnit.MILLISECONDS) 33 | .port(9999) 34 | .separator("#") 35 | .host("riemann") 36 | .build(); 37 | 38 | assertEquals(c.port, 9999); 39 | assertEquals(c.unit, TimeUnit.MILLISECONDS); 40 | assertEquals(c.separator, "#"); 41 | assertEquals(c.host, "riemann"); 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/riemann/java/client/tests/NonReplyingTcpServerTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import java.io.DataInputStream; 4 | import java.io.IOException; 5 | import java.net.InetAddress; 6 | import java.net.InetSocketAddress; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | import java.net.UnknownHostException; 10 | import java.util.concurrent.atomic.AtomicReference; 11 | 12 | import com.aphyr.riemann.Proto.Msg; 13 | import com.aphyr.riemann.client.AbstractRiemannClient; 14 | import com.aphyr.riemann.client.RiemannTcpClient; 15 | 16 | public class NonReplyingTcpServerTest extends AbstractClientTest { 17 | 18 | @Override 19 | Thread createServer(final int port, final AtomicReference received) { 20 | return new Thread() { 21 | @Override 22 | public void run() { 23 | try { 24 | final ServerSocket server = new ServerSocket(port); 25 | serverStarted(); 26 | final Socket socket = server.accept(); 27 | received.set(receive(socket)); 28 | serverReceived(); 29 | } catch (Exception e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | private Msg receive(final Socket socket) throws IOException { 35 | final DataInputStream input = new DataInputStream(socket.getInputStream()); 36 | final byte[] data = new byte[input.readInt()]; 37 | input.readFully(data); 38 | return Msg.parseFrom(data); 39 | } 40 | }; 41 | } 42 | 43 | @Override 44 | AbstractRiemannClient createClient(int port) throws UnknownHostException { 45 | return new RiemannTcpClient(new InetSocketAddress(InetAddress.getLocalHost(), port)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/riemann/java/client/tests/TcpClientTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import java.io.DataInputStream; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.net.InetAddress; 7 | import java.net.InetSocketAddress; 8 | import java.net.ServerSocket; 9 | import java.net.Socket; 10 | import java.net.UnknownHostException; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | 13 | import com.aphyr.riemann.Proto.Msg; 14 | import com.aphyr.riemann.client.AbstractRiemannClient; 15 | import com.aphyr.riemann.client.RiemannTcpClient; 16 | 17 | public class TcpClientTest extends AbstractClientTest { 18 | 19 | @Override 20 | Thread createServer(final int port, final AtomicReference received) { 21 | return new Thread() { 22 | @Override 23 | public void run() { 24 | try { 25 | final ServerSocket server = new ServerSocket(port); 26 | serverStarted(); 27 | final Socket socket = server.accept(); 28 | received.set(receive(socket)); 29 | serverReceived(); 30 | send(socket); 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | private void send(Socket socket) throws IOException { 37 | final OutputStream out = socket.getOutputStream(); 38 | Msg.newBuilder().build().writeTo(out); 39 | out.close(); 40 | } 41 | 42 | private Msg receive(final Socket socket) throws IOException { 43 | final DataInputStream input = new DataInputStream(socket.getInputStream()); 44 | final byte[] data = new byte[input.readInt()]; 45 | input.readFully(data); 46 | return Msg.parseFrom(data); 47 | } 48 | }; 49 | } 50 | 51 | @Override 52 | AbstractRiemannClient createClient(int port) throws UnknownHostException { 53 | return new RiemannTcpClient(new InetSocketAddress(InetAddress.getLocalHost(), port)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /riemann-java-client.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/RiemannUDPClient.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | import java.io.IOException; 4 | import java.net.*; 5 | 6 | import com.aphyr.riemann.Proto.Msg; 7 | import sun.reflect.generics.reflectiveObjects.NotImplementedException; 8 | 9 | public class RiemannUDPClient extends AbstractRiemannClient { 10 | protected static final int MAX_SIZE = 16384; 11 | 12 | public RiemannUDPClient() throws UnknownHostException { 13 | super(); 14 | } 15 | 16 | public RiemannUDPClient(int port) throws UnknownHostException { 17 | super(port); 18 | } 19 | 20 | public RiemannUDPClient(InetSocketAddress server) { 21 | super(server); 22 | } 23 | 24 | // Returns true if the message is small enough to be sent. 25 | public boolean canSendMessage(Msg message) { 26 | return message.getSerializedSize() <= MAX_SIZE; 27 | } 28 | 29 | @Override 30 | public void sendMessage(Msg message) throws IOException, MsgTooLargeException { 31 | if (message.getSerializedSize() > MAX_SIZE) { 32 | throw new MsgTooLargeException(); 33 | } 34 | 35 | byte[] buf = message.toByteArray(); 36 | DatagramSocket socket = new DatagramSocket(); 37 | socket.send(new DatagramPacket(buf, buf.length, server)); 38 | socket.close(); 39 | } 40 | 41 | @Override 42 | public Msg recvMessage() throws IOException { 43 | throw new NotImplementedException(); 44 | } 45 | 46 | @Override 47 | public Msg sendRecvMessage(Msg message) throws IOException { 48 | throw new NotImplementedException(); 49 | } 50 | 51 | @Override 52 | public Msg sendMaybeRecvMessage(Msg message) throws IOException, MsgTooLargeException { 53 | sendMessage(message); 54 | return null; 55 | } 56 | 57 | @Override 58 | public boolean isConnected() { 59 | return true; 60 | } 61 | 62 | @Override 63 | public void connect() throws IOException { 64 | // do nothing on UDP 65 | } 66 | 67 | @Override 68 | public void disconnect() throws IOException { 69 | // do nothing on UDP 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/RiemannClient.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.net.UnknownHostException; 7 | import com.aphyr.riemann.Proto.Msg; 8 | import sun.reflect.generics.reflectiveObjects.NotImplementedException; 9 | 10 | // A hybrid UDP/TCP client. 11 | public class RiemannClient extends AbstractRiemannClient { 12 | public final RiemannRetryingTcpClient tcp; 13 | public final RiemannUDPClient udp; 14 | 15 | // Singleton 16 | public static RiemannClient singletonClient; 17 | 18 | public static RiemannClient getClient() { 19 | return singletonClient; 20 | } 21 | 22 | public static void setClient(RiemannClient client) { 23 | singletonClient = client; 24 | } 25 | 26 | public RiemannClient(InetSocketAddress server) { 27 | super(server); 28 | udp = new RiemannUDPClient(server); 29 | tcp = new RiemannRetryingTcpClient(server); 30 | } 31 | 32 | public RiemannClient(final int port) throws UnknownHostException { 33 | this(new InetSocketAddress(InetAddress.getLocalHost(), port)); 34 | } 35 | 36 | public RiemannClient() throws UnknownHostException { 37 | this(new InetSocketAddress(InetAddress.getLocalHost(), DEFAULT_PORT)); 38 | } 39 | 40 | @Override 41 | public void sendMessage(Msg message) { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | @Override 46 | public Msg recvMessage() { 47 | throw new NotImplementedException(); 48 | } 49 | 50 | @Override 51 | public Msg sendRecvMessage(Msg message) throws IOException { 52 | return tcp.sendRecvMessage(message); 53 | } 54 | 55 | @Override 56 | // Attempts to dispatch the message quickly via UDP, then falls back to TCP. 57 | public Msg sendMaybeRecvMessage(Msg message) throws IOException { 58 | try { 59 | if (udp.canSendMessage(message)) { 60 | return udp.sendMaybeRecvMessage(message); 61 | } else { 62 | return tcp.sendMaybeRecvMessage(message); 63 | } 64 | } catch (MsgTooLargeException e) { 65 | return tcp.sendMaybeRecvMessage(message); 66 | } 67 | } 68 | 69 | @Override 70 | public boolean isConnected() { 71 | return(udp.isConnected() && tcp.isConnected()); 72 | } 73 | 74 | @Override 75 | public void connect() throws IOException { 76 | synchronized(this) { 77 | udp.connect(); 78 | tcp.connect(); 79 | } 80 | } 81 | 82 | @Override 83 | public void disconnect() throws IOException { 84 | synchronized(this) { 85 | udp.disconnect(); 86 | tcp.disconnect(); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/RiemannRetryingTcpClient.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | import com.aphyr.riemann.Proto.Msg; 4 | 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | import java.net.UnknownHostException; 8 | import java.util.Timer; 9 | 10 | // Like RiemannTCPClient, but automatically tries one reconnect/resubmit when an IO error occurs. 11 | public class RiemannRetryingTcpClient extends RiemannTcpClient { 12 | protected final Object reconnectionLock = new Object(); 13 | protected long lastReconnectionAttempt = 0l; // milliseconds 14 | public volatile long minimumReconnectInterval = 1; // seconds 15 | protected volatile boolean reconnecting = false; 16 | 17 | public RiemannRetryingTcpClient() throws UnknownHostException { 18 | super(); 19 | } 20 | 21 | public RiemannRetryingTcpClient(int port) throws UnknownHostException { 22 | super(port); 23 | } 24 | 25 | public RiemannRetryingTcpClient(InetSocketAddress server) { 26 | super(server); 27 | } 28 | 29 | @Override 30 | public Msg sendRecvMessage(Msg m) throws IOException { 31 | try { 32 | return super.sendRecvMessage(m); 33 | } catch (IOException e) { 34 | reconnect(); 35 | return super.sendRecvMessage(m); 36 | } 37 | } 38 | 39 | /** 40 | * @param interval reconnection time in SECONDS 41 | */ 42 | public void setMinimumReconnectInterval(long interval) { 43 | if (interval < 0) 44 | throw new IllegalArgumentException("Invalid interval time"); 45 | 46 | minimumReconnectInterval = interval * 1000; // receive in seconds, convert to ms 47 | } 48 | 49 | // Attempts to reconnect. Can be called many times; will only try reconnecting every few seconds. 50 | // If another thread is reconnecting, or a connection attempt was made too recently, returns immediately. 51 | public void reconnect() throws IOException { 52 | synchronized (reconnectionLock) { 53 | long latestAttempt = System.currentTimeMillis() - lastReconnectionAttempt; 54 | if (!reconnecting && latestAttempt > minimumReconnectInterval) { 55 | reconnecting = true; 56 | lastReconnectionAttempt = System.currentTimeMillis(); 57 | } else { 58 | // no time to reconnect yet or reconnection already in progress 59 | // release lock and get off here! :) 60 | return; 61 | } 62 | } 63 | 64 | try { 65 | synchronized (socketLock) { 66 | disconnect(); 67 | connect(); 68 | } 69 | } finally { 70 | synchronized (reconnectionLock) { 71 | reconnecting = false; 72 | } 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/RiemannScheduler.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | import java.util.concurrent.ScheduledFuture; 4 | import java.util.concurrent.ScheduledThreadPoolExecutor; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | // Supports periodic reporting of events. 8 | public class RiemannScheduler { 9 | public static abstract class Task { 10 | public abstract void run(AbstractRiemannClient r); 11 | } 12 | 13 | public final ScheduledThreadPoolExecutor pool; 14 | public final AbstractRiemannClient client; 15 | 16 | public RiemannScheduler(AbstractRiemannClient client) { 17 | this(client, 1); 18 | } 19 | 20 | // Finer control over threadpool 21 | public RiemannScheduler(AbstractRiemannClient client, int poolSize) { 22 | this.client = client; 23 | pool = new ScheduledThreadPoolExecutor(poolSize); 24 | pool.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); 25 | pool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); 26 | } 27 | 28 | public void shutdown() { 29 | pool.shutdown(); 30 | } 31 | 32 | // Converts a callback to a runnable by closing over this pool's client 33 | protected Runnable runnableCallback(final Task c) { 34 | return new Runnable() { 35 | @Override 36 | public void run() { 37 | c.run(client); 38 | } 39 | }; 40 | } 41 | 42 | // Schedule an arbitrary runnable to be run periodically; useful when 43 | // you already have a client handy. Interval is in ms. 44 | public ScheduledFuture every(long interval, Runnable f) { 45 | return every(interval, 0, f); 46 | } 47 | 48 | public ScheduledFuture every(long interval, Task c) { 49 | return every(interval, runnableCallback(c)); 50 | } 51 | 52 | // Schedule an arbitrary runnable to be run periodically. Adjustable 53 | // units. 54 | public ScheduledFuture every(long interval, TimeUnit unit, Runnable f) { 55 | return every(interval, 0, unit, f); 56 | } 57 | 58 | public ScheduledFuture every(long interval, TimeUnit unit, Task c) { 59 | return every(interval, unit, runnableCallback(c)); 60 | } 61 | 62 | // Schedule an arbitrary runnable to be run periodically, with initial 63 | // delay. Times in ms. 64 | public ScheduledFuture every(long interval, long delay, Runnable f) { 65 | return every(interval, delay, TimeUnit.MILLISECONDS, f); 66 | } 67 | 68 | public ScheduledFuture every(long interval, long delay, Task c) { 69 | return every(interval, delay, runnableCallback(c)); 70 | } 71 | 72 | // Schedule an arbitrary runnable to be run periodically, with initial 73 | // delay and adjustable units. 74 | public ScheduledFuture every(long interval, long delay, TimeUnit unit, Runnable f) { 75 | return pool.scheduleAtFixedRate(f, delay, interval, unit); 76 | } 77 | 78 | public ScheduledFuture every(long interval, long delay, TimeUnit unit, Task c) { 79 | return every(interval, delay, unit, runnableCallback(c)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/RiemannTcpClient.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | import com.aphyr.riemann.Proto.Msg; 4 | 5 | import java.io.DataInputStream; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | import java.net.InetSocketAddress; 9 | import java.net.Socket; 10 | import java.net.UnknownHostException; 11 | 12 | public class RiemannTcpClient extends AbstractRiemannClient { 13 | protected Socket socket; 14 | protected final Object socketLock = new Object(); 15 | protected DataOutputStream out; 16 | protected DataInputStream in; 17 | 18 | public static final int connectTimeout = 1000; 19 | public static final int readTimeout = 1000; 20 | 21 | public RiemannTcpClient() throws UnknownHostException { 22 | super(); 23 | } 24 | 25 | public RiemannTcpClient(int port) throws UnknownHostException { 26 | super(port); 27 | } 28 | 29 | public RiemannTcpClient(InetSocketAddress server) { 30 | super(server); 31 | } 32 | 33 | // This method is not synchronized. 34 | @Override 35 | public void sendMessage(Msg message) throws IOException { 36 | if (message == null) { 37 | throw new IllegalArgumentException("Null message"); 38 | } 39 | out.writeInt(message.getSerializedSize()); 40 | message.writeTo(out); 41 | out.flush(); 42 | } 43 | 44 | // This method is not synchronized. 45 | @Override 46 | public Msg recvMessage() throws IOException { 47 | // Get length header 48 | int len = in.readInt(); 49 | if (len < 0) { 50 | throw new IOException("FUCKED"); 51 | } 52 | 53 | // Get body 54 | byte[] body = new byte[len]; 55 | in.readFully(body); 56 | return Msg.parseFrom(body); 57 | } 58 | 59 | @Override 60 | public Msg sendRecvMessage(Msg message) throws IOException { 61 | synchronized (socketLock) { 62 | sendMessage(message); 63 | return recvMessage(); 64 | } 65 | } 66 | 67 | @Override 68 | public Msg sendMaybeRecvMessage(Msg message) throws IOException { 69 | return sendRecvMessage(message); 70 | } 71 | 72 | @Override 73 | public boolean isConnected() { 74 | synchronized (socketLock) { 75 | return this.socket != null && this.socket.isConnected(); 76 | } 77 | } 78 | 79 | @Override 80 | public void connect() throws IOException { 81 | synchronized (socketLock) { 82 | socket = new Socket(); 83 | socket.connect(super.server, connectTimeout); 84 | socket.setSoTimeout(readTimeout); 85 | socket.setTcpNoDelay(true); 86 | this.out = new DataOutputStream(this.socket.getOutputStream()); 87 | this.in = new DataInputStream(this.socket.getInputStream()); 88 | } 89 | } 90 | 91 | @Override 92 | public void disconnect() throws IOException { 93 | synchronized (socketLock) { 94 | this.out.close(); 95 | this.in.close(); 96 | this.socket.close(); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/test/java/riemann/java/client/tests/AbstractClientTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.fail; 5 | 6 | import java.io.IOException; 7 | import java.util.Arrays; 8 | import java.util.Random; 9 | import java.util.concurrent.CountDownLatch; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | 13 | import com.aphyr.riemann.client.AbstractRiemannClient; 14 | import org.junit.After; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | 18 | import com.aphyr.riemann.Proto.Event; 19 | import com.aphyr.riemann.Proto.Msg; 20 | 21 | public abstract class AbstractClientTest { 22 | 23 | private int port; 24 | private Thread server; 25 | private final AtomicReference received = new AtomicReference(); 26 | private CountDownLatch serverStart; 27 | private CountDownLatch serverReceived; 28 | 29 | @Before 30 | public void start() { 31 | this.received.set(null); 32 | this.serverStart = new CountDownLatch(1); 33 | this.serverReceived = new CountDownLatch(1); 34 | this.port = 9800 + new Random().nextInt(100); 35 | this.server = createServer(this.port, this.received); 36 | this.server.start(); 37 | } 38 | 39 | @After 40 | public void stop() { 41 | this.server.interrupt(); 42 | this.server = null; 43 | this.port = -1; 44 | this.received.set(null); 45 | this.serverStart = null; 46 | } 47 | 48 | protected void serverStarted() { 49 | this.serverStart.countDown(); 50 | } 51 | 52 | protected void serverReceived() { 53 | this.serverReceived.countDown(); 54 | } 55 | 56 | abstract Thread createServer(int port, AtomicReference received); 57 | 58 | abstract AbstractRiemannClient createClient(int port) throws IOException; 59 | 60 | @Test 61 | public void sendEvent() throws IOException, InterruptedException { 62 | final Event sent = createEvent(); 63 | final AbstractRiemannClient client = createClient(this.port); 64 | if (this.serverStart.await(500, TimeUnit.MILLISECONDS)) { 65 | client.connect(); 66 | client.sendEvents(sent); 67 | if (this.serverReceived.await(500, TimeUnit.MILLISECONDS)) { 68 | client.disconnect(); 69 | compareEvents(sent, this.received.get()); 70 | } else { 71 | fail("Timed out witing for server to receive message"); 72 | } 73 | } else { 74 | fail("Timed out waiting for server to start"); 75 | } 76 | } 77 | 78 | private static Event createEvent() { 79 | final Random random = new Random(); 80 | return Event.newBuilder() 81 | .setDescription("desc") 82 | .setHost("127.0.0.1") 83 | .setService("service") 84 | .setTime(random.nextInt(1000)) 85 | .setTtl(random.nextInt(1000)) 86 | .setMetricF(random.nextInt(1000)) 87 | .addAllTags(Arrays.asList("tag1", "tag2")) 88 | .build(); 89 | } 90 | 91 | private static void compareEvents(final Event sent, final Msg message) { 92 | assertEquals(1, message.getEventsCount()); 93 | final Event parsed = message.getEvents(0); 94 | assertEquals(sent.getHost(), parsed.getHost()); 95 | assertEquals(sent.getService(), parsed.getService()); 96 | assertEquals(sent.getState(), parsed.getState()); 97 | assertEquals(sent.getDescription(), parsed.getDescription()); 98 | assertEquals(sent.getMetricF(), parsed.getMetricF(), 0); 99 | assertEquals(sent.getTime(), parsed.getTime(), 0); 100 | assertEquals(sent.getTtl(), parsed.getTtl(), 0); 101 | assertEquals(sent.getTagsList(), parsed.getTagsList()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.aphyr 6 | riemann-java-client 7 | 0.0.7-SNAPSHOT 8 | jar 9 | Riemann Java Client 10 | Java client for http://aphyr.github.com/riemann/ 11 | / 12 | 13 | 14 | UTF-8 15 | UTF-8 16 | 17 | 18 | 19 | 20 | com.google.protobuf 21 | protobuf-java 22 | 2.4.1 23 | compile 24 | 25 | 26 | 27 | junit 28 | junit-dep 29 | 4.10 30 | test 31 | 32 | 33 | com.yammer.metrics 34 | metrics-core 35 | 2.1.2 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 2.3.2 45 | 46 | 1.6 47 | 1.6 48 | 49 | 50 | 51 | 52 | org.codehaus.mojo 53 | build-helper-maven-plugin 54 | 1.5 55 | 56 | 57 | add-source 58 | generate-sources 59 | 60 | add-source 61 | 62 | 63 | 64 | target/generated-sources 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | maven-antrun-plugin 73 | 74 | 75 | generate-sources 76 | generate-sources 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | run 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | clojars.org 98 | Clojars 99 | https://clojars.org/repo 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/EventDSL.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | import com.aphyr.riemann.Proto.Event; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.io.IOException; 8 | 9 | public class EventDSL { 10 | public final AbstractRiemannClient client; 11 | public final Event.Builder builder; 12 | 13 | public EventDSL(AbstractRiemannClient client) { 14 | this.client = client; 15 | this.builder = Event.newBuilder(); 16 | try { 17 | this.builder.setHost(java.net.InetAddress.getLocalHost().getHostName()); 18 | } catch (java.net.UnknownHostException e) { 19 | // If we can't get the local host, a null host is perfectly 20 | // acceptable. Caller will know soon enough. :) 21 | } 22 | } 23 | 24 | public EventDSL host(String host) { 25 | if (null == host) { 26 | builder.clearHost(); 27 | } else { 28 | builder.setHost(host); 29 | } 30 | return this; 31 | } 32 | 33 | public EventDSL service(String service) { 34 | if (null == service) { 35 | builder.clearService(); 36 | } else { 37 | builder.setService(service); 38 | } 39 | return this; 40 | } 41 | 42 | public EventDSL state(String state) { 43 | if (null == state) { 44 | builder.clearState(); 45 | } else { 46 | builder.setState(state); 47 | } 48 | return this; 49 | } 50 | 51 | public EventDSL description(String description) { 52 | if (null == description) { 53 | builder.clearDescription(); 54 | } else { 55 | builder.setDescription(description); 56 | } 57 | return this; 58 | } 59 | 60 | public EventDSL time(Null n) { 61 | builder.clearMetricF(); 62 | return this; 63 | } 64 | 65 | public EventDSL time(float time) { 66 | builder.setTime((long) time); 67 | return this; 68 | } 69 | 70 | public EventDSL time(double time) { 71 | builder.setTime((long) time); 72 | return this; 73 | } 74 | public EventDSL time(long time) { 75 | builder.setTime(time); 76 | return this; 77 | } 78 | 79 | public EventDSL metric(Null n) { 80 | builder.clearMetricF(); 81 | builder.clearMetricD(); 82 | builder.clearMetricSint64(); 83 | return this; 84 | } 85 | 86 | public EventDSL metric(byte metric) { 87 | builder.setMetricSint64((long) metric); 88 | builder.setMetricF((float) metric); 89 | return this; 90 | } 91 | 92 | public EventDSL metric(short metric) { 93 | builder.setMetricSint64((long) metric); 94 | builder.setMetricF((float) metric); 95 | return this; 96 | } 97 | 98 | public EventDSL metric(int metric) { 99 | builder.setMetricSint64((long) metric); 100 | builder.setMetricF((float) metric); 101 | return this; 102 | } 103 | 104 | public EventDSL metric(long metric) { 105 | builder.setMetricSint64(metric); 106 | builder.setMetricF((float) metric); 107 | return this; 108 | } 109 | 110 | public EventDSL metric(float metric) { 111 | builder.setMetricF(metric); 112 | return this; 113 | } 114 | 115 | public EventDSL metric(double metric) { 116 | builder.setMetricD(metric); 117 | builder.setMetricF((float) metric); 118 | return this; 119 | } 120 | 121 | public EventDSL tag(String tag) { 122 | builder.addTags(tag); 123 | return this; 124 | } 125 | 126 | public EventDSL tags(List tags) { 127 | builder.addAllTags(tags); 128 | return this; 129 | } 130 | 131 | public EventDSL tags(String... tags) { 132 | builder.addAllTags(Arrays.asList(tags)); 133 | return this; 134 | } 135 | 136 | public EventDSL ttl(Null n) { 137 | builder.clearTtl(); 138 | return this; 139 | } 140 | 141 | public EventDSL ttl(float ttl) { 142 | builder.setTtl(ttl); 143 | return this; 144 | } 145 | 146 | public Boolean sendWithAck() throws IOException, ServerError, MsgTooLargeException { 147 | return client.sendEventsWithAck(builder.build()); 148 | } 149 | 150 | public void send() { 151 | client.sendEvents(builder.build()); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/aphyr/riemann/client/AbstractRiemannClient.java: -------------------------------------------------------------------------------- 1 | package com.aphyr.riemann.client; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.net.UnknownHostException; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Arrays; 10 | import java.util.concurrent.ScheduledFuture; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import com.aphyr.riemann.Proto.Event; 14 | import com.aphyr.riemann.Proto.Query; 15 | import com.aphyr.riemann.Proto.Msg; 16 | 17 | public abstract class AbstractRiemannClient { 18 | 19 | public static final int DEFAULT_PORT = 5555; 20 | 21 | protected final InetSocketAddress server; 22 | protected RiemannScheduler scheduler = null; 23 | 24 | public AbstractRiemannClient(final InetSocketAddress server) { 25 | this.server = server; 26 | } 27 | 28 | public AbstractRiemannClient(final int port) throws UnknownHostException { 29 | this.server = new InetSocketAddress(InetAddress.getLocalHost(), port); 30 | } 31 | 32 | public AbstractRiemannClient() throws UnknownHostException { 33 | this(new InetSocketAddress(InetAddress.getLocalHost(), DEFAULT_PORT)); 34 | } 35 | 36 | public EventDSL event() { 37 | return new EventDSL(this); 38 | } 39 | 40 | // Sends events and checks the server's response. Will throw IOException for 41 | // network failures, ServerError for error responses from Riemann. Returns 42 | // true if events acknowledged. 43 | public Boolean sendEventsWithAck(final Event... events) throws IOException, ServerError, MsgTooLargeException { 44 | validate( 45 | sendRecvMessage( 46 | Msg.newBuilder() 47 | .addAllEvents(Arrays.asList(events)) 48 | .build() 49 | ) 50 | ); 51 | return true; 52 | } 53 | 54 | // Sends events in fire-and-forget fashion. Doesn't check server response, 55 | // swallows all exceptions silently. No guarantees on delivery. 56 | public void sendEvents(final Event... events) { 57 | try { 58 | sendMaybeRecvMessage( 59 | Msg.newBuilder() 60 | .addAllEvents(Arrays.asList(events)) 61 | .build() 62 | ); 63 | } catch (IOException e) { 64 | // Fuck it. 65 | } catch (MsgTooLargeException e) { 66 | // Similarly. 67 | } 68 | } 69 | 70 | // Send an Exception event, with state "error" and tagged 71 | // "exception". The event will also be tagged with the exception class name. 72 | // Description includes the exception class and stack trace. 73 | public void sendException(String service, Throwable t) { 74 | final StringBuilder desc = new StringBuilder(); 75 | desc.append(t.toString()); 76 | desc.append("\n\n"); 77 | for (StackTraceElement e : t.getStackTrace()) { 78 | desc.append(e); 79 | desc.append("\n"); 80 | } 81 | 82 | event().service(service) 83 | .state("error") 84 | .tag("exception") 85 | .tag(t.getClass().getSimpleName()) 86 | .description(desc.toString()) 87 | .send(); 88 | } 89 | 90 | public List query(String q) throws IOException, ServerError, MsgTooLargeException { 91 | Msg m = sendRecvMessage(Msg.newBuilder() 92 | .setQuery( 93 | Query.newBuilder().setString(q).build()) 94 | .build()); 95 | 96 | validate(m); 97 | 98 | return Collections.unmodifiableList(m.getEventsList()); 99 | } 100 | 101 | public abstract void sendMessage(Msg message) throws IOException, MsgTooLargeException; 102 | 103 | public abstract Msg recvMessage() throws IOException; 104 | 105 | public abstract Msg sendRecvMessage(Msg message) throws IOException, MsgTooLargeException; 106 | 107 | public abstract Msg sendMaybeRecvMessage(Msg message) throws IOException, MsgTooLargeException; 108 | 109 | public abstract boolean isConnected(); 110 | 111 | public abstract void connect() throws IOException; 112 | 113 | public abstract void disconnect() throws IOException; 114 | 115 | // Returns the scheduler for this client. Creates the scheduler on first use. 116 | public synchronized RiemannScheduler scheduler() { 117 | if (scheduler == null) { 118 | scheduler = new RiemannScheduler(this); 119 | } 120 | return scheduler; 121 | } 122 | 123 | // Set up recurring tasks on this client's scheduler. 124 | // This may be the lowest entropy for any code I've ever written. 125 | public ScheduledFuture every(long interval, Runnable f) { return scheduler().every(interval, f); } 126 | public ScheduledFuture every(long interval, RiemannScheduler.Task f) { return scheduler().every(interval, f); } 127 | public ScheduledFuture every(long interval, TimeUnit unit, Runnable f) { return scheduler().every(interval, unit, f); } 128 | public ScheduledFuture every(long interval, TimeUnit unit, RiemannScheduler.Task f) { return scheduler().every(interval, unit, f); } 129 | public ScheduledFuture every(long interval, long delay, Runnable f) { return scheduler().every(interval, delay, f); } 130 | public ScheduledFuture every(long interval, long delay, RiemannScheduler.Task f) { return scheduler().every(interval, delay, f); } 131 | public ScheduledFuture every(long interval, long delay, TimeUnit unit, Runnable f) { return scheduler().every(interval, delay, unit, f); } 132 | public ScheduledFuture every(long interval, long delay, TimeUnit unit, RiemannScheduler.Task f) { return scheduler().every(interval, delay, unit, f); } 133 | 134 | // Asserts that the message is OK; if not, throws a ServerError. 135 | public Msg validate(Msg message) throws IOException, ServerError { 136 | if (message.hasOk() && !message.getOk()) { 137 | throw new ServerError(message.getError()); 138 | } 139 | return message; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/main/java/com/yammer/metrics/reporting/RiemannReporter.java: -------------------------------------------------------------------------------- 1 | package com.yammer.metrics.reporting; 2 | 3 | import com.aphyr.riemann.client.*; 4 | import com.yammer.metrics.Metrics; 5 | import com.yammer.metrics.core.*; 6 | import com.yammer.metrics.stats.Snapshot; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | import java.net.InetSocketAddress; 12 | import java.util.Map.Entry; 13 | import java.util.SortedMap; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | public class RiemannReporter extends AbstractPollingReporter implements MetricProcessor { 17 | private static final Logger LOG = LoggerFactory.getLogger(RiemannReporter.class); 18 | protected final RiemannClient riemann; 19 | protected final Config c; 20 | 21 | public static class Config { 22 | 23 | public final MetricsRegistry metricsRegistry; 24 | public final MetricPredicate predicate; 25 | public final boolean printVMMetrics; 26 | public final String host; 27 | public final int port; 28 | public final long period; 29 | public final TimeUnit unit; 30 | public final String prefix; 31 | public final String separator; 32 | public final VirtualMachineMetrics vm = VirtualMachineMetrics.getInstance(); 33 | public final Clock clock; 34 | public final String name; 35 | public final String localHost; 36 | 37 | private Config(MetricsRegistry metricsRegistry, MetricPredicate predicate, boolean printVMMetrics, 38 | String host, int port, long period, TimeUnit unit, String prefix, String separator, 39 | Clock clock, String name, String localHost) { 40 | this.metricsRegistry = metricsRegistry; 41 | this.predicate = predicate; 42 | this.printVMMetrics = printVMMetrics; 43 | this.host = host; 44 | this.port = port; 45 | this.period = period; 46 | this.unit = unit; 47 | this.prefix = prefix; 48 | this.separator = separator; 49 | this.clock = clock; 50 | this.name = name; 51 | this.localHost = localHost; 52 | 53 | } 54 | 55 | public static ConfigBuilder newBuilder() { 56 | return new ConfigBuilder(); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "Config{" + 62 | "print_metrics:" + printVMMetrics + ", host:" + host + ", port:" + port + ", period:" + period + 63 | ", unit:" + unit + ", prefix:" + prefix + ", separator:" + separator + ", clock:" + clock + 64 | ", name:" + name + ", localhost:" + localHost + ", metrics_registry:" + metricsRegistry + 65 | ", predicate:" + predicate + "}"; 66 | } 67 | } 68 | 69 | public static final class ConfigBuilder { 70 | // Default values for Config 71 | private MetricsRegistry metricsRegistry = Metrics.defaultRegistry(); 72 | private MetricPredicate predicate = MetricPredicate.ALL; 73 | private boolean printVMMetrics = true; 74 | private String host = "localhost"; 75 | private int port = 5555; 76 | private long period = 60; 77 | private TimeUnit unit = TimeUnit.SECONDS; 78 | private String prefix = null; 79 | private String separator = " "; 80 | private final VirtualMachineMetrics vm = VirtualMachineMetrics.getInstance(); 81 | private Clock clock = Clock.defaultClock(); 82 | private String name = "riemann-reporter"; 83 | private String localHost = null; 84 | 85 | private ConfigBuilder() { } 86 | 87 | public Config build(){ 88 | return new Config(metricsRegistry, predicate, printVMMetrics, host, port, 89 | period, unit, prefix, separator, clock, name, localHost); 90 | } 91 | 92 | public ConfigBuilder metricsRegistry(MetricsRegistry r) { metricsRegistry = r; return this; } 93 | public ConfigBuilder predicate(MetricPredicate p) { predicate = p; return this; } 94 | public ConfigBuilder printVMMetrics(Boolean p) { printVMMetrics = p; return this; } 95 | public ConfigBuilder host(String h) { host = h; return this; } 96 | public ConfigBuilder port(int p) { port = p; return this; } 97 | public ConfigBuilder period(long p) { period = p; return this; } 98 | public ConfigBuilder unit(TimeUnit t) { unit = t; return this; } 99 | public ConfigBuilder prefix(String p) { prefix = p; return this; } 100 | public ConfigBuilder separator(String s) { separator = s; return this; } 101 | public ConfigBuilder clock(Clock c) { clock = c; return this; } 102 | public ConfigBuilder name(String n) { name = n; return this; } 103 | public ConfigBuilder localHost(String l) { localHost = l; return this; } 104 | } 105 | 106 | public static void enable(final Config config) { 107 | try { 108 | if (config == null) 109 | throw new IllegalArgumentException("Config cannot be null"); 110 | 111 | final RiemannReporter reporter = new RiemannReporter(config); 112 | reporter.start(config.period, config.unit); 113 | } catch (Exception e) { 114 | LOG.error("Error creating/starting Riemann reporter: ", e); 115 | } 116 | } 117 | 118 | public RiemannReporter(final Config c) throws IOException { 119 | super(c.metricsRegistry, c.name); 120 | this.riemann = new RiemannClient(new InetSocketAddress(c.host, c.port)); 121 | riemann.connect(); 122 | this.c = c; 123 | } 124 | 125 | @Override 126 | public void run() { 127 | try { 128 | final long epoch = c.clock.time() / 1000; 129 | if (c.printVMMetrics) { 130 | sendVMMetrics(epoch); 131 | } 132 | 133 | sendRegularMetrics(epoch); 134 | } catch (Exception e) { 135 | LOG.warn("Error writing to Riemann", e); 136 | } 137 | } 138 | 139 | protected void sendRegularMetrics(final Long epoch) { 140 | for (Entry> entry : getMetricsRegistry().groupedMetrics(c.predicate).entrySet()) { 141 | for (Entry subEntry : entry.getValue().entrySet()) { 142 | final Metric metric = subEntry.getValue(); 143 | if (metric != null) { 144 | try { 145 | metric.processWith(this, subEntry.getKey(), epoch); 146 | } catch (Exception ignored) { 147 | LOG.error("Error sending regular metric:", ignored); 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | private EventDSL newEvent() { 155 | EventDSL event = riemann.event(); 156 | if (c.localHost != null) { 157 | event.host(c.localHost); 158 | } 159 | return event; 160 | } 161 | 162 | // The service name for a given metric. 163 | public String service(MetricName name, String... rest) { 164 | final StringBuilder sb = new StringBuilder(); 165 | if (null != c.prefix) { 166 | sb.append(c.prefix).append(c.separator); 167 | } 168 | sb.append(name.getGroup()) 169 | .append(c.separator) 170 | .append(name.getType()) 171 | .append(c.separator); 172 | if (name.hasScope()) { 173 | sb.append(name.getScope()) 174 | .append(c.separator); 175 | } 176 | sb.append(name.getName()); 177 | for (String part : rest) { 178 | sb.append(c.separator); 179 | sb.append(part); 180 | } 181 | return sb.toString(); 182 | } 183 | 184 | public String service(String... parts) { 185 | final StringBuilder sb = new StringBuilder(); 186 | if (null != c.prefix) { 187 | sb.append(c.prefix).append(c.separator); 188 | } 189 | 190 | for (String p : parts) { 191 | sb.append(p).append(c.separator); 192 | } 193 | 194 | return sb.substring(0, sb.length() - c.separator.length()); 195 | } 196 | 197 | @Override 198 | public void processGauge(MetricName name, Gauge gauge, Long epoch) { 199 | Object v = gauge.value(); 200 | EventDSL e = newEvent().service(service(name)).time(epoch); 201 | if (v instanceof Integer) { 202 | e.metric((Integer) v).send(); 203 | } else if (v instanceof Long) { 204 | e.metric((Long) v).send(); 205 | } else if (v instanceof Double) { 206 | e.metric((Double) v).send(); 207 | } else if (v instanceof Float) { 208 | e.metric((Float) v).send(); 209 | } else if (v instanceof Number) { 210 | e.metric(((Number) v).floatValue()).send(); 211 | } 212 | } 213 | 214 | @Override 215 | public void processCounter(MetricName name, Counter counter, Long epoch) { 216 | newEvent() 217 | .service(service(name)) 218 | .metric(counter.count()) 219 | .time(epoch) 220 | .send(); 221 | } 222 | 223 | @Override 224 | public void processMeter(MetricName name, Metered meter, Long epoch) { 225 | newEvent() 226 | .service(service(name)) 227 | .metric(meter.oneMinuteRate()) 228 | .time(epoch) 229 | .send(); 230 | } 231 | 232 | @Override 233 | public void processHistogram(MetricName name, Histogram histogram, Long epoch) throws IOException { 234 | final String service = service(name); 235 | sendSummary(name, histogram, epoch); 236 | sendSample(name, histogram, epoch); 237 | } 238 | 239 | @Override 240 | public void processTimer(MetricName name, Timer timer, Long epoch) { 241 | processMeter(name, timer, epoch); 242 | sendSummary(name, timer, epoch); 243 | sendSample(name, timer, epoch); 244 | } 245 | 246 | protected void sendVMMetrics(long epoch) { 247 | newEvent().time(epoch).service(service("jvm", "memory", "heap-usage")).metric(c.vm.heapUsage()).send(); 248 | newEvent().time(epoch).service(service("jvm", "memory", "non-heap-usage")).metric(c.vm.nonHeapUsage()).send(); 249 | for (Entry pool : c.vm.memoryPoolUsage().entrySet()) { 250 | newEvent().time(epoch).service(service("jvm", "memory", "pool-usage", pool.getKey())).metric(pool.getValue()).send(); 251 | } 252 | newEvent().time(epoch).service(service("jvm", "thread", "daemon-count")).metric(c.vm.daemonThreadCount()).send(); 253 | newEvent().time(epoch).service(service("jvm", "thread", "count")).metric(c.vm.threadCount()).send(); 254 | newEvent().time(epoch).service(service("jvm", "uptime")).metric(c.vm.uptime()).send(); 255 | newEvent().time(epoch).service(service("jvm", "fd-usage")).metric(c.vm.fileDescriptorUsage()).send(); 256 | 257 | for(Entry entry : c.vm.threadStatePercentages().entrySet()) { 258 | newEvent().time(epoch).service(service("jvm", "thread", "state", entry.getKey().toString().toLowerCase())).metric(entry.getValue()).send(); 259 | } 260 | 261 | for(Entry entry : c.vm.garbageCollectors().entrySet()) { 262 | newEvent().time(epoch).service(service("jvm", "gc", entry.getKey(), "time")).metric(entry.getValue().getTime(TimeUnit.MILLISECONDS)).send(); 263 | newEvent().time(epoch).service(service("jvm", "gc", entry.getKey(), "runs")).metric(entry.getValue().getRuns()).send(); 264 | } 265 | } 266 | 267 | public void sendSummary(MetricName name, Summarizable metric, Long epoch) { 268 | newEvent().time(epoch).service(service(name, "min")).metric(metric.min()).send(); 269 | newEvent().time(epoch).service(service(name, "max")).metric(metric.max()).send(); 270 | newEvent().time(epoch).service(service(name, "mean")).metric(metric.mean()).send(); 271 | newEvent().time(epoch).service(service(name, "stddev")).metric(metric.stdDev()).send(); 272 | } 273 | 274 | public void sendSample(MetricName name, Sampling metric, Long epoch) { 275 | final Snapshot s = metric.getSnapshot(); 276 | newEvent().time(epoch).service(service(name, ".5")).metric(s.getMedian()).send(); 277 | newEvent().time(epoch).service(service(name, ".75")).metric(s.get75thPercentile()).send(); 278 | newEvent().time(epoch).service(service(name, ".95")).metric(s.get95thPercentile()).send(); 279 | newEvent().time(epoch).service(service(name, ".98")).metric(s.get98thPercentile()).send(); 280 | newEvent().time(epoch).service(service(name, ".99")).metric(s.get99thPercentile()).send(); 281 | newEvent().time(epoch).service(service(name, ".999")).metric(s.get999thPercentile()).send(); 282 | } 283 | } 284 | --------------------------------------------------------------------------------