clj-http-client/test/com/puppetlabs/http/client/impl/metrics_unit_test.clj

199 lines
12 KiB
Clojure
Raw Normal View History

(TK-316) Add metrics support This commit adds metrics support to the http client (clojure and java, sync and async). A metric registry can optionally be passed into the client as a client option on creation. If a metric registry is present, timers will be added to time each request. By default, a timer is added for the URL (stripped of username, password, query string, and path fragments) and the URL plus the method used for the request. In addition, a request can include a `metric-id` option, which takes a tuple of metric ids. If this request option is specified, a timer will be created for each element of the metric id tuple - thus if the tuple is [:foo :bar :baz] there will be a foo timer, a foo.bar timer, and a foo.bar.baz timer. In addition, each timer has a "MetricType" - currently there is only one metric type, bytes-read, which is stopped when the full response has been read. In the future, we may add "response-init" timers that get stopped when the first byte of the response has been read. This commit also adds a `get-client-metrics`/`.getClientMetrics` function that takes a client instance and returns the http client-specific metrics from the metric registry and a `get-client-metrics-data`/`.getClientMetricsData` function for clojure and java sync and async clients to get out metrics data from the client. This function takes a client instance and returns a map of metric name to a map of metric data (for clojure) or a ClientMetricData object (for java), both of which include the mean, count, and aggregate for the timer These `get-client-metrics*`/`.getClientMetrics*` functions also have versions that take a url, url and method, or metric id to allow for filtering of the timers/metrics data returned by these functions. The clojure versions of these functions take a metric filter map. There are also metric filter builder functions to build up the type of metric filter desired from a url, a url and method, or a metric id. These will prevent users from having to know the specifics of how to build a metric themselves; instead they can use a convenience function. An empty metric id can be passed in to the filter to return all metric-id timers.
2016-02-26 05:39:21 +00:00
(ns com.puppetlabs.http.client.impl.metrics-unit-test
(:require [clojure.test :refer :all]
[puppetlabs.http.client.metrics :as metrics])
(:import (com.codahale.metrics MetricRegistry)
(com.puppetlabs.http.client.impl Metrics Metrics$MetricType)
(org.apache.http.message BasicHttpRequest)))
(def bytes-read Metrics$MetricType/BYTES_READ)
(defn add-metric-ns [string]
(str "puppetlabs.http-client.experimental." string))
(deftest start-bytes-read-timers-test
(testing "startBytesReadTimers creates the right timers"
(let [url-id (add-metric-ns "with-url.http://localhost/foo.bytes-read")
url-method-id (add-metric-ns "with-url.http://localhost/foo.GET.bytes-read")]
(testing "metric id timers are not created for a request without a metric id"
(let [metric-registry (MetricRegistry.)]
(Metrics/startBytesReadTimers metric-registry
(BasicHttpRequest. "GET" "http://localhost/foo")
nil)
(is (= (set (list url-id url-method-id)) (set (keys (.getTimers metric-registry)))))))
(testing "metric id timers are not created for a request with an empty metric id"
(let [metric-registry (MetricRegistry.)]
(Metrics/startBytesReadTimers metric-registry
(BasicHttpRequest. "GET" "http://localhost/foo")
(into-array String []))
(is (= (set (list url-id url-method-id)) (set (keys (.getTimers metric-registry)))))))
(testing "metric id timers are created correctly for a request with a metric id"
(let [metric-registry (MetricRegistry.)]
(Metrics/startBytesReadTimers metric-registry
(BasicHttpRequest. "GET" "http://localhost/foo")
(into-array ["foo" "bar" "baz"]))
(is (= (set (list url-id url-method-id
(add-metric-ns "with-metric-id.foo.bytes-read")
(add-metric-ns "with-metric-id.foo.bar.bytes-read")
(add-metric-ns "with-metric-id.foo.bar.baz.bytes-read")))
(set (keys (.getTimers metric-registry)))))))
(testing "url timers should strip off username, password, query string, and fragment"
(let [metric-registry (MetricRegistry.)]
(Metrics/startBytesReadTimers metric-registry
(BasicHttpRequest. "GET" "http://user:pwd@localhost:1234/foo%2cbar/baz?te%2cst=one")
nil)
(Metrics/startBytesReadTimers metric-registry
(BasicHttpRequest. "GET" "http://user:pwd@localhost:1234/foo%2cbar/baz#x%2cyz")
nil)
(Metrics/startBytesReadTimers metric-registry
(BasicHttpRequest. "GET" "http://user:pwd@localhost:1234/foo%2cbar/baz?te%2cst=one#x%2cyz")
nil)
(Metrics/startBytesReadTimers metric-registry
(BasicHttpRequest. "GET" "http://user:pwd@localhost:1234/foo%2cbar/baz?#x%2cyz")
nil)
(is (= (set (list (add-metric-ns "with-url.http://localhost:1234/foo%2cbar/baz.bytes-read")
(add-metric-ns "with-url.http://localhost:1234/foo%2cbar/baz.GET.bytes-read")))
(set (keys (.getTimers metric-registry))))))))))
(defn start-and-stop-timers! [registry req id]
(doseq [timer (Metrics/startBytesReadTimers
registry
req
id)]
(.stop timer)))
(deftest get-client-metrics-data-test
(let [registry (MetricRegistry.)
url "http://test.com/one"
url2 "http://test.com/one/two"]
(start-and-stop-timers! registry (BasicHttpRequest. "GET" url) nil)
(start-and-stop-timers! registry (BasicHttpRequest. "POST" url) nil)
(start-and-stop-timers! registry (BasicHttpRequest. "POST" url) (into-array ["foo" "bar"]))
(start-and-stop-timers! registry (BasicHttpRequest. "GET" url2) (into-array ["foo" "abc"]))
(testing "getClientMetrics without args returns all timers"
(is (= (set
["puppetlabs.http-client.experimental.with-url.http://test.com/one.bytes-read"
"puppetlabs.http-client.experimental.with-url.http://test.com/one.GET.bytes-read"
"puppetlabs.http-client.experimental.with-url.http://test.com/one.POST.bytes-read"
"puppetlabs.http-client.experimental.with-metric-id.foo.bytes-read"
"puppetlabs.http-client.experimental.with-metric-id.foo.bar.bytes-read"
"puppetlabs.http-client.experimental.with-url.http://test.com/one/two.bytes-read"
"puppetlabs.http-client.experimental.with-url.http://test.com/one/two.GET.bytes-read"
"puppetlabs.http-client.experimental.with-metric-id.foo.abc.bytes-read"])
(set (keys (Metrics/getClientMetrics registry)))
(set (keys (Metrics/getClientMetricsData registry))))))
(testing "getClientMetricsData with url returns the right thing"
(let [java-data (Metrics/getClientMetricsDataWithUrl registry url bytes-read)
clj-data (metrics/get-client-metrics-data
registry {:url url :metric-type :bytes-read})]
(is (= (add-metric-ns "with-url.http://test.com/one.bytes-read")
(first (keys java-data))
(first (keys clj-data))))
(is (= 3 (.getCount (first (vals java-data)))
(:count (first (vals clj-data))))))
(let [java-data (Metrics/getClientMetricsDataWithUrl registry url2 bytes-read)
clj-data (metrics/get-client-metrics-data
registry {:url url2 :metric-type :bytes-read})]
(is (= (add-metric-ns "with-url.http://test.com/one/two.bytes-read")
(first (keys java-data))
(first (keys clj-data))))
(is (= 1 (.getCount (first (vals java-data)))
(:count (first (vals clj-data))))))
(testing "getClientMetricsData with url returns nothing if url is not a full match"
(is (= {} (Metrics/getClientMetricsDataWithUrl registry "http://test.com" bytes-read)
(metrics/get-client-metrics-data
registry {:url "http://test.com" :metric-type :bytes-read})))))
(testing "getClientMetricsData with url and method returns the right thing"
(let [java-data (Metrics/getClientMetricsDataWithUrlAndMethod registry url "GET" bytes-read)
clj-data (metrics/get-client-metrics-data
registry {:url url :method :get :metric-type :bytes-read})]
(is (= (add-metric-ns "with-url.http://test.com/one.GET.bytes-read")
(first (keys clj-data))
(first (keys java-data))))
(is (= 1 (.getCount (first (vals java-data)))
(:count (first (vals clj-data))))))
(let [java-data (Metrics/getClientMetricsDataWithUrlAndMethod registry url "POST" bytes-read)
clj-data (metrics/get-client-metrics-data
registry {:url url :method :post :metric-type :bytes-read})]
(is (= (add-metric-ns "with-url.http://test.com/one.POST.bytes-read")
(first (keys java-data))
(first (keys clj-data))))
(is (= 2 (.getCount (first (vals java-data)))
(:count (first (vals clj-data))))))
(let [java-data (Metrics/getClientMetricsDataWithUrlAndMethod registry url2 "GET" bytes-read)
clj-data (metrics/get-client-metrics-data
registry {:url url2 :method :get :metric-type :bytes-read})]
(is (= (add-metric-ns "with-url.http://test.com/one/two.GET.bytes-read")
(first (keys java-data))
(first (keys clj-data))))
(is (= 1 (.getCount (first (vals java-data)))
(:count (first (vals clj-data))))))
(testing "getClientMetricsData with url and method returns nothing if method is not a match"
(is (= {} (Metrics/getClientMetricsDataWithUrlAndMethod
registry "http://test.com" "PUT" bytes-read)
(metrics/get-client-metrics-data
registry {:url "http://test.com" :method :put :metric-type :bytes-read})))))
(testing "getClientMetricsData with metric id returns the right thing"
(let [java-data (Metrics/getClientMetricsDataWithMetricId
registry (into-array ["foo"]) bytes-read)
clj-data (metrics/get-client-metrics-data
registry {:metric-id ["foo"] :metric-type :bytes-read})]
(is (= (add-metric-ns "with-metric-id.foo.bytes-read")
(first (keys java-data))
(first (keys clj-data))))
(is (= 2 (.getCount (first (vals java-data)))
(:count (first (vals clj-data))))))
(let [java-data (Metrics/getClientMetricsDataWithMetricId
registry (into-array ["foo" "bar"]) bytes-read)
clj-data (metrics/get-client-metrics-data
registry {:metric-id ["foo" "bar"] :metric-type :bytes-read})]
(is (= (add-metric-ns "with-metric-id.foo.bar.bytes-read")
(first (keys java-data))
(first (keys clj-data))))
(is (= 1 (.getCount (first (vals java-data)))
(:count (first (vals clj-data))))))
(let [java-data (Metrics/getClientMetricsDataWithMetricId
registry (into-array ["foo" "abc"]) bytes-read)
clj-data (metrics/get-client-metrics-data
registry {:metric-id ["foo" "abc"] :metric-type :bytes-read})]
(is (= (add-metric-ns "with-metric-id.foo.abc.bytes-read")
(first (keys java-data))
(first (keys clj-data))))
(is (= 1 (.getCount (first (vals java-data)))
(:count (first (vals clj-data)))))
(testing "metric id can be specified as keyword or string"
(is (= clj-data
(metrics/get-client-metrics-data
registry {:metric-id ["foo" :abc] :metric-type :bytes-read})))))
(testing "getClientMetricsData with metric id returns nothing if id is not a match"
(is (= {} (Metrics/getClientMetricsDataWithMetricId
registry (into-array ["foo" "cat"]) bytes-read)
(metrics/get-client-metrics-data
registry {:metric-id ["foo" "cat"] :metric-type :bytes-read})))))))
(deftest empty-metric-id-filter-test
(testing "a metric id filter with an empty array returns all metric id timers"
(let [registry (MetricRegistry.)
url "http://test.com/foo/bar"
foo-id (add-metric-ns "with-metric-id.foo.bytes-read")
foo-bar-id (add-metric-ns "with-metric-id.foo.bar.bytes-read")
foo-bar-baz-id (add-metric-ns "with-metric-id.foo.bar.baz.bytes-read")]
(start-and-stop-timers! registry (BasicHttpRequest. "GET" url) (into-array ["foo" "bar" "baz"]))
(testing "empty metric filter returns all metric id timers"
(is (= (set (list foo-id foo-bar-id foo-bar-baz-id))
(set (keys (Metrics/getClientMetricsDataWithMetricId registry (into-array String []) bytes-read)))
(set (keys (metrics/get-client-metrics-data registry (metrics/filter-with-metric-id []))))))))))
(deftest metrics-filter-builder-test
(let [metric-registry (MetricRegistry.)
url "http://test.com/foo/bar"]
(start-and-stop-timers! metric-registry (BasicHttpRequest. "GET" url) (into-array ["foo" "bar"]))
(testing "url-filter works"
(is (= (metrics/get-client-metrics-data metric-registry {:url url :metric-type :bytes-read})
(metrics/get-client-metrics-data metric-registry (metrics/filter-with-url url)))))
(testing "url-method-filter works"
(is (= (metrics/get-client-metrics-data metric-registry {:url url :method :get :metric-type :bytes-read})
(metrics/get-client-metrics-data metric-registry (metrics/filter-with-url-and-method url :get)))))
(testing "metric-id-filter works"
(is (= (metrics/get-client-metrics-data metric-registry {:metric-id [:foo :bar] :metric-type :bytes-read})
(metrics/get-client-metrics-data metric-registry (metrics/filter-with-metric-id [:foo :bar])))))))