Skip to content

Commit

Permalink
Merge pull request #1 from hugoduncan/feature/add-persistent-connection
Browse files Browse the repository at this point in the history
Add http options and connection-manager
  • Loading branch information
aphyr committed Dec 19, 2013
2 parents 5dee0ba + bd2a95e commit d6ba622
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 54 deletions.
15 changes: 13 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
(defproject clj-librato "0.0.4-SHAPSHOT"
:description "Clojure interface to the Librato service"
:dependencies [[org.clojure/clojure "1.5.1"]
[clj-http "0.7.5"]
[cheshire "5.2.0"]])
[org.clojure/tools.logging "0.2.6"]
[clj-http "0.7.5"
:exclusions [commons-logging]]
[cheshire "5.2.0"]]
:profiles
{:dev {:dependencies [[log4j/log4j "1.2.16"
:exclusions [javax.mail/mail
javax.jms/jms
com.sun.jdmk/jmxtools
com.sun.jmx/jmxri]]
[org.slf4j/slf4j-log4j12 "1.7.5"]
[org.slf4j/jcl-over-slf4j "1.7.5"]]}
:test {:resource-paths ["resources" "test-resources"]}})
146 changes: 96 additions & 50 deletions src/clj_librato/metrics.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,28 @@
(:use [slingshot.slingshot :only [try+]])
(:require [cheshire.core :as json]
[clj-http.client :as client]
[clj-http.conn-mgr :as conn-mgr]
[clojure.string :as string]
[clojure.tools.logging :as logging]
clj-http.util))

(def uri-base "https://metrics-api.librato.com/v1/")

(defn uri
"The full URI of a particular resource, by path fragments."
[& path-fragments]
(apply str uri-base
(interpose "/" (map (comp clj-http.util/url-encode str)
(apply str uri-base
(interpose "/" (map (comp clj-http.util/url-encode str)
path-fragments))))

(defn unparse-kw
"Convert a clojure-style dashed keyword map into string underscores.
Recursive."
[m]
(cond
(map? m) (into {} (map (fn [[k v]]
[(string/replace (name k) "-" "_")
(unparse-kw v)])
(map? m) (into {} (map (fn [[k v]]
[(string/replace (name k) "-" "_")
(unparse-kw v)])
m))
(coll? m) (map unparse-kw m)
true m))
Expand All @@ -32,40 +34,58 @@
[m]
(into {} (map (fn [[k v]] [(keyword (string/replace k "_" "-")) v]) m)))

(defn connection-manager
"Return a connection manager that can be passed as :connection-manager in
a request."
[{:keys [timeout threads] :or {timeout 10 threads 2} :as options}]
(conn-mgr/make-reusable-conn-manager
(merge {:timeout timeout :threads threads :default-per-route threads}
options)))

(defn request
"Constructs the HTTP client request map."
"Constructs the HTTP client request map.
options will be merged verbatim into the request map."
([user api-key params]
{:basic-auth [user api-key]
:content-type :json
:accept :json
:throw-entire-message? true
:query-params (unparse-kw params)})

([user api-key params body]
(assoc (request user api-key params)
:body (json/generate-string (unparse-kw body)))))
:body (json/generate-string (unparse-kw body)))))

(defn collate [user api-key gauges counters]
"Posts a set of gauges and counters."
(assert (every? :name gauges))
(assert (every? :name counters))
(assert (every? :value gauges))
(assert (every? :value counters))
(client/post (uri "metrics")
(request user api-key {} {:gauges gauges :counters counters})))
(defn collate
"Posts a set of gauges and counters. options is a map of clj-http options."
([user api-key gauges counters]
(collate user api-key gauges counters nil))
([user api-key gauges counters options]
(assert (every? :name gauges))
(assert (every? :name counters))
(assert (every? :value gauges))
(assert (every? :value counters))
(client/post (uri "metrics")
(merge
options
(request user api-key {}
{:gauges gauges :counters counters})))))

(defn metric
"Gets a metric by name.
See http://dev.librato.com/v1/get/metrics"
([user api-key name]
(metric user api-key name {}))

(metric user api-key name {} nil))
([user api-key name params]
(metric user api-key name params nil))

([user api-key name params options]
(assert name)
(try+
(let [body (-> (client/get (uri "metrics" name)
(request user api-key params))
(merge
options
(request user api-key params)))
:body json/parse-string parse-kw)]
(assoc body :measurements
(into {} (map (fn [[source measurements]]
Expand All @@ -77,44 +97,70 @@

(defn create-annotation
"Creates a new annotation, and returns the created annotation as a map.
http://dev.librato.com/v1/post/annotations/:name"
[user api-key name annotation]
(assert name)
(-> (client/post (uri "annotations" name)
(request user api-key {} annotation))
:body
json/parse-string
parse-kw))
([user api-key name annotation]
(create-annotation user api-key name annotation))
([user api-key name annotation options]
{:pre [(or (nil? options)(map? options))]}
(assert name)
(-> (client/post (uri "annotations" name)
(merge
options
(request user api-key {} annotation)))
:body
json/parse-string
parse-kw)))

(defn update-annotation
"Updates an annotation.
http://dev.librato.com/v1/put/annotations/:name/events/:id"
[user api-key name id annotation]
(assert name)
(assert id)
(client/put (uri "annotations" name id)
(request user api-key {} annotation)))

(defn annotate
"Creates or updates an annotation. If id is given, updates. If id is
missing, creates a new annotation."
([user api-key name annotation]
(create-annotation user api-key name annotation))
([user api-key name id annotation]
(update-annotation user api-key name id annotation)))
(update-annotation user api-key name id annotation nil))
([user api-key name id annotation options]
(assert name)
(assert id)
(client/put (uri "annotations" name id)
(merge
options
(request user api-key {} annotation)))))

(let [warn-on-deprecate (atom true)]
;; Deprecated due to argument ambiguity.
;; A future version could rename create-annotation as annotate.
(defn annotate
"Creates or updates an annotation. If id is given, updates. If id is
missing, creates a new annotation."
([user api-key name annotation]
(create-annotation user api-key name annotation nil))
([user api-key name annotation options]
(if (map? annotation)
(create-annotation user api-key name annotation options)
(do
;; user api-key name id annotation
(update-annotation user api-key name annotation options)
(when @warn-on-deprecate
(reset! warn-on-deprecate false)
(logging/warn
(str "`annotate` called for annotation update is deprecated. "
"Please use update-annotation."))))))))

(defn annotation
"Find a particular annotation event.
See http://dev.librato.com/v1/get/annotations/:name/events/:id"
[user api-key name id]
(assert name)
(assert id)
(try+
(-> (client/get (uri "annotations" name id) (request user api-key {}))
:body
json/parse-string
parse-kw)
(catch [:status 404] _ nil)))
([user api-key name id]
(annotation user api-key name id nil))
([user api-key name id options]
(assert name)
(assert id)
(try+
(-> (client/get (uri "annotations" name id)
(merge
options
(request user api-key {})))
:body
json/parse-string
parse-kw)
(catch [:status 404] _ nil))))
108 changes: 106 additions & 2 deletions test/clj_librato/metrics_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,61 @@
(is (= (:measure-time m) (:measure-time gauge)))
(is (= (:value m) (double (:value gauge))))
(is (= (:count m) 1)))
)))
)))

(defn test-value
[]
{:name "test.gauge"
:source "clj-librato"
:value (/ (rand-int 1000) (rand-int 1000))
:measure-time (now)})

(defn test-collate-and-guage
[options]
(testing "gauge"
(let [gauge (test-value)]
;; Submit gauge
(is (collate user apikey [gauge] [] options) "is created")

(testing "can be queried"
(let [metric (metric user apikey (:name gauge)
{:end-time (:measure-time gauge)
:count 1
:resolution 1}
options)]
(is (= (:type metric) "gauge"))
(is (= (:name metric) (:name gauge)))
(testing "has a last metric value"
(let [m (-> metric
:measurements
(get (:source gauge))
(first))]
(is m)
(is (= (:measure-time m) (:measure-time gauge)))
(is (= (:value m) (double (:value gauge))))
(is (= (:count m) 1)))))))))

(deftest collate-with-http-options-test
(testing "reject nil names"
(is (thrown? java.lang.AssertionError
(collate user apikey [{:name nil}] [] {})))
(is (thrown? java.lang.AssertionError
(collate user apikey [] [{:name nil}] {}))))

(testing "reject nil values"
(is (thrown? java.lang.AssertionError
(collate user apikey [{:name "foo"
:value nil}] [] {})))
(is (thrown? java.lang.AssertionError
(collate user apikey [] [{:name "foo"
:value nil}] {}))))

(testing "with no http options"
(test-collate-and-guage nil))
(testing "with persistent http options"
(let [cm (connection-manager {})]
(is cm "connection manager created")
(test-collate-and-guage {:connection-manager cm}))))

(deftest annotate-test
(let [name "test.annotations"
Expand All @@ -82,14 +136,64 @@
(is (= (:end-time e) (:end-time event)))

; Update annotation
(annotate user apikey name (:id res)
(annotate user apikey name (:id res)
{:end-time (inc (:end-time event))})

; Verify update was applied.
(is (= (inc (:end-time event))
(:end-time (annotation user apikey name (:id res)))))))

(defn test-annotation
[]
{:title (str "A test event: " (rand 10000000))
:source "clj-librato"
:description "Testing clj-librato annotations"
:start-time (now)
:end-time (+ 10 (now))})

(defn annotation=
[e1 e2]
(let [ks [:title :description :source :start-time :end-time]]
(= (select-keys e1 ks) (select-keys e2 ks))))

(defn test-annotate
[options]
(testing "a test annotation"
(let [name "test.annotations"
annot-map (test-annotation)]

(testing "can be created"
(let [res (annotate user apikey name annot-map options)]
(is res "is created without error")

(testing "and is queryable"
(let [a (annotation user apikey name (:id res) options)]
(is a "can be queried by returned id")
(is (= res a) "is as returned by annotate")
(is (annotation= a annot-map) "has matching attributes")))

(testing "and can be updated"
(update-annotation user apikey name (:id res)
{:end-time (inc (:end-time annot-map))}
options)
(is (= (inc (:end-time annot-map))
(:end-time (annotation user apikey name (:id res) options)))
"updated attribute matches")))))))

(deftest annotate-with-http-options-test
(testing "with no http options"
(test-annotate nil))
(testing "with persistent http options"
(let [cm (connection-manager {})]
(is cm "connection manager created")
(test-annotate {:connection-manager cm}))))

(deftest annotation-test
; 404s return nil
(is (nil? (annotation user apikey "asdilhugflsdbfg" 1234)))
(is (nil? (annotation user apikey name 2345235624534))))

(deftest annotation-with-http-options-test
; 404s return nil
(is (nil? (annotation user apikey "asdilhugflsdbfg" 1234 {})))
(is (nil? (annotation user apikey name 2345235624534 {}))))

0 comments on commit d6ba622

Please sign in to comment.