├── .gitignore ├── project.clj ├── test └── riemann │ ├── client_test.clj │ └── codec_test.clj ├── README.md └── src └── riemann ├── client.clj └── codec.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | *.jar 7 | *.class 8 | .lein-deps-sum 9 | .lein-failures 10 | .lein-plugins 11 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject riemann-clojure-client "0.0.7-SNAPSHOT" 2 | :description "Clojure client for the Riemann monitoring system" 3 | :url "https://github.com/aphyr/riemann-clojure-client" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.4.0"] 7 | [org.clojure/tools.logging "0.2.3"] 8 | [com.aphyr/riemann-java-client "0.0.6"]]) 9 | -------------------------------------------------------------------------------- /test/riemann/client_test.clj: -------------------------------------------------------------------------------- 1 | (ns riemann.client-test 2 | (:use riemann.client 3 | clojure.test)) 4 | 5 | (deftest send-query 6 | (let [c (tcp-client) 7 | e {:service "test" :state "ok"}] 8 | (send-event c {:service "test" :state "ok"}) 9 | (let [e1 (first (query c "service = \"test\" and state = \"ok\""))] 10 | (is (= (:service e) (:service e1))) 11 | (is (= (:state e) (:state e1))) 12 | (is (float? (:ttl e1))) 13 | (is (integer? (:time e1)))))) 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # riemann-clojure-client 2 | 3 | A clojure client for [Riemann](http://aphyr.github.com/riemann) 4 | 5 | ## Usage 6 | 7 | For riemann-java-client, add the boundary repo to your project.clj: 8 | ```clojure 9 | :repositories { 10 | "boundary-site" "http://maven.boundary.com/artifactory/repo" 11 | } 12 | ``` 13 | 14 | Then grab the dependency from [Clojars](https://clojars.org/riemann-clojure-client). 15 | 16 | ``` clojure 17 | (use 'riemann.client) 18 | (def c (tcp-client :host "1.2.3.4")) 19 | (send-event c {:service "foo" :state "ok"}) 20 | (query c "state = \"ok\"") 21 | ``` 22 | 23 | ## License 24 | 25 | Copyright © 2012 Kyle Kingsbury 26 | 27 | Distributed under the Eclipse Public License, the same as Clojure. 28 | -------------------------------------------------------------------------------- /src/riemann/client.clj: -------------------------------------------------------------------------------- 1 | (ns riemann.client 2 | "Network client for connecting to a Riemann server. Usage: 3 | 4 | (def c (tcp-client :host \"monitoring.local\")) 5 | 6 | (send-event c {:service \"fridge\" 7 | :state \"running\" 8 | :metric 2.0 9 | :tags [\"joke\"]}) 10 | 11 | (query c \"tagged \\\"joke\\\"\") 12 | => [{:service \"fridge\" ... }] 13 | 14 | (close c) 15 | 16 | Clients are mildly resistant to failure; they will attempt to reconnect a 17 | dropped connection once before giving up. They're backed by a 18 | RiemannRetryingTcpClient." 19 | 20 | (:import [com.aphyr.riemann.client RiemannRetryingTcpClient 21 | RiemannUDPClient 22 | AbstractRiemannClient] 23 | [java.net InetSocketAddress] 24 | [java.io IOException]) 25 | (:use riemann.codec) 26 | (:use clojure.tools.logging)) 27 | 28 | (defn query 29 | "Query the server for events in the index. Returns a list of events." 30 | [^AbstractRiemannClient client string] 31 | (map decode-pb-event (.query client string))) 32 | 33 | (defn send-event 34 | "Send an event over client." 35 | ([client event] 36 | (send-event client event true)) 37 | ([^AbstractRiemannClient client event ack] 38 | (let [events (into-array com.aphyr.riemann.Proto$Event 39 | (list (encode-pb-event event)))] 40 | (if ack 41 | (.sendEventsWithAck client events) 42 | (.sendEvents client events))))) 43 | 44 | (defn tcp-client 45 | "Create a new TCP client. Example: 46 | 47 | (tcp-client) 48 | (tcp-client :host \"foo\" :port 5555)" 49 | [& { :keys [^String host ^Integer port] 50 | :or {port 5555 51 | host "localhost"} 52 | :as opts}] 53 | (doto (RiemannRetryingTcpClient. (InetSocketAddress. host port)) 54 | (.connect))) 55 | 56 | (defn udp-client 57 | "Create a new UDP client. Example: 58 | 59 | (udp-client) 60 | (udp-client :host \"foo\" :port 5555)" 61 | [& {:keys [^String host ^Integer port] 62 | :or {port 5555 63 | host "localhost"} 64 | :as opts}] 65 | (doto (RiemannUDPClient. (InetSocketAddress. host port)) 66 | (.connect))) 67 | 68 | (defn close-client 69 | "Close a client." 70 | [^AbstractRiemannClient client] 71 | (.disconnect client)) 72 | 73 | (defn reconnect-client 74 | "Reconnect a client." 75 | [client] 76 | (.reconnect client)) 77 | -------------------------------------------------------------------------------- /test/riemann/codec_test.clj: -------------------------------------------------------------------------------- 1 | (ns riemann.codec-test 2 | (:use riemann.codec 3 | clojure.test) 4 | (:import [riemann.codec Msg Event Query])) 5 | 6 | (defn msg-equal? 7 | [m1 m2] 8 | (prn "Compare" m1 m2) 9 | (cond 10 | (map? m1) 11 | (every? (fn [k] (msg-equal? (m1 k) (m2 k))) 12 | (into #{} (concat (keys m1) (keys m2)))) 13 | 14 | (coll? m1) 15 | (every? identity 16 | (map msg-equal? m1 m2)) 17 | 18 | true 19 | (do (prn (class m1) (class m2)) 20 | (= m1 m2)))) 21 | 22 | (defn msg 23 | "Map to Msg" 24 | [m] 25 | (-> m 26 | (assoc :events (map map->Event (:events m))) 27 | (map->Msg))) 28 | 29 | (deftest protobufs 30 | (let [roundtrip (comp decode-pb-msg encode-pb-msg)] 31 | (are [m] (= (msg m) (roundtrip m)) 32 | {} 33 | {:ok true} 34 | {:ok false} 35 | {:error "foo"} 36 | {:query (Query. "hi")} 37 | {:ok false :error "hi" :query (Query. "yo")} 38 | {:events [{}]} 39 | {:events [{:host "sup"}]} 40 | ; Longs 41 | {:events [{:metric Long/MIN_VALUE}]} 42 | {:events [{:metric 0}]} 43 | {:events [{:metric Long/MAX_VALUE}]} 44 | ; Doubles 45 | {:events [{:metric 55555555.5555555555}]} 46 | {:events [{:metric Double/MIN_VALUE}]} 47 | {:events [{:metric Double/MAX_VALUE}]} 48 | ; Floats 49 | {:events [{:metric (float 5555555.555555555)}]} 50 | {:events [{:metric Float/MIN_VALUE}]} 51 | {:events [{:metric Float/MAX_VALUE}]} 52 | {:events [{:host "a" 53 | :service "b" 54 | :state "c" 55 | :description "yo" 56 | :metric (float 1.8) 57 | :tags ["a" "b" "c"] 58 | :time 12345 59 | :ttl (float 10)}]}) 60 | 61 | (let [m (msg {:events [{:host "a" 62 | :service "b" 63 | :state "c" 64 | :description "yo" 65 | :metric (float 1.8) 66 | :tags ["a" "b" "c"] 67 | :time 12345 68 | :ttl (float 10)}]})] 69 | ; (time (dotimes [n 10000000] 70 | ; (roundtrip m)))))) 71 | ))) 72 | -------------------------------------------------------------------------------- /src/riemann/codec.clj: -------------------------------------------------------------------------------- 1 | (ns riemann.codec 2 | "Encodes and decodes Riemann messages and events, between byte arrays, 3 | buffers, and in-memory types." 4 | (:import [com.aphyr.riemann Proto$Query Proto$Event Proto$Msg] 5 | [com.google.protobuf ByteString])) 6 | 7 | (defrecord Query [string]) 8 | (defrecord Event [host service state description metric tags time ttl]) 9 | (defrecord Msg [ok error events query]) 10 | 11 | (defn decode-pb-query 12 | "Transforms a java protobuf Query to a Query." 13 | [^Proto$Query q] 14 | (Query. (.getString q))) 15 | 16 | (defn encode-pb-query 17 | "Transforms a Query to a protobuf Query." 18 | (^Proto$Query [query] 19 | (-> (Proto$Query/newBuilder) 20 | (.setString (:string query)) 21 | (.build)))) 22 | 23 | (defn decode-pb-event 24 | "Transforms a java protobuf to an Event." 25 | [^Proto$Event e] 26 | (Event. 27 | (when (.hasHost e) (.getHost e)) 28 | (when (.hasService e) (.getService e)) 29 | (when (.hasState e) (.getState e)) 30 | (when (.hasDescription e) (.getDescription e)) 31 | (cond 32 | (.hasMetricSint64 e) (.getMetricSint64 e) 33 | (.hasMetricD e) (.getMetricD e) 34 | (.hasMetricF e) (.getMetricF e)) 35 | (when (< 0 (.getTagsCount e)) (vec (.getTagsList e))) 36 | (when (.hasTime e) (.getTime e)) 37 | (when (.hasTtl e) (.getTtl e)))) 38 | 39 | (defn encode-pb-event 40 | "Transforms a map to a java protobuf Event." 41 | [e] 42 | (let [event (Proto$Event/newBuilder)] 43 | (when (:host e) (.setHost event (:host e))) 44 | (when (:service e) (.setService event (:service e))) 45 | (when (:state e) (.setState event (:state e))) 46 | (when (:description e) (.setDescription event (:description e))) 47 | (when-let [m (:metric e)] 48 | ; For backwards compatibility, always construct floats. 49 | (try (.setMetricF event (float m)) (catch Throwable _)) 50 | (if (and (integer? m) (<= Long/MIN_VALUE m Long/MAX_VALUE)) 51 | (.setMetricSint64 event (long m)) 52 | (.setMetricD event (double m)))) 53 | (when (:tags e) (.addAllTags event (:tags e))) 54 | (when (:time e) (.setTime event (long (:time e)))) 55 | (when (:ttl e) (.setTtl event (:ttl e))) 56 | (.build event))) 57 | 58 | (defn decode-pb-msg 59 | "Transforms a java protobuf Msg to a Msg." 60 | [^Proto$Msg m] 61 | (Msg. (when (.hasOk m) (.getOk m)) 62 | (when (.hasError m) (.getError m)) 63 | (map decode-pb-event (.getEventsList m)) 64 | (when (.hasQuery m) (decode-pb-query (.getQuery m))))) 65 | 66 | (defn encode-pb-msg 67 | "Transform a map to a java probuf Msg." 68 | [m] 69 | (let [msg (Proto$Msg/newBuilder)] 70 | (when-let [events (:events m)] 71 | (.addAllEvents msg (map encode-pb-event events))) 72 | (when-not (nil? (:ok m)) (.setOk msg (:ok m))) 73 | (when (:error m) (.setError msg (:error m))) 74 | (when (:query m) (.setQuery msg (encode-pb-query (:query m)))) 75 | (.build msg))) 76 | --------------------------------------------------------------------------------