From a2676f5b8735c2c8e350defbe0600a081f9626d8 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 30 Jul 2015 21:54:12 +0200 Subject: [PATCH 1/5] Add support for firing metrics via the metrics API. --- go_http/metrics.py | 33 +++++++++++++++++++++++++++++++++ go_http/tests/test_metrics.py | 34 +++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/go_http/metrics.py b/go_http/metrics.py index 1532d87..99ab237 100644 --- a/go_http/metrics.py +++ b/go_http/metrics.py @@ -74,3 +74,36 @@ def get_metric(self, metric, start, interval, nulls): "nulls": nulls } return self._api_request("GET", "metrics/", payload) + + def fire(self, **metrics): + """ + Fire metrics. + + :param dict metrics: + A mapping of metric names to floating point metric values. + + When metrics are fired they may specify an aggregator. The + aggregation method is determined by the suffix of the metric name. + For example, ``foo.last`` fires a metric that uses the ``last`` + aggregation method. + + The available aggregators are: + + :Average: + ``avg``. Aggregates by averaging the values in each time period. + :Sum: + ``sum``. Aggregates by summing all the values in each time period. + :Maximum: + ``max``. Aggregates by choosing the maximum value in each time + period. + :Minimum: + ``min``. Aggregates by choosing the minimum value in each time + period. + :Last: + ``last``. Aggregates by choosing the last value in each time + period. + + Note that metrics can also be fired via an HTTP conversation API. + See :meth:`go_http.send.HttpApiSender.fire_metric`. + """ + return self._api_request("POST", "metrics/", metrics) diff --git a/go_http/tests/test_metrics.py b/go_http/tests/test_metrics.py index 9ca8978..8700119 100644 --- a/go_http/tests/test_metrics.py +++ b/go_http/tests/test_metrics.py @@ -22,11 +22,11 @@ def send(self, request, *args, **kw): return super(RecordingAdapter, self).send(request, *args, **kw) -class TestMetricApiReader(TestCase): +class TestMetricApiClient(TestCase): def setUp(self): self.session = TestSession() - self.sender = MetricsApiClient( + self.client = MetricsApiClient( auth_token="auth-token", api_url="http://example.com/api/v1/go", session=self.session) @@ -43,13 +43,17 @@ def test_default_api_url(self): self.assertEqual(client.api_url, "http://go.vumi.org/api/v1/go") - def check_request(self, request, method, headers=None): + def check_request(self, request, method, data=None, headers=None): self.assertEqual(request.method, method) if headers is not None: for key, value in headers.items(): self.assertEqual(request.headers[key], value) + if data is None: + self.assertEqual(request.body, None) + else: + self.assertEqual(json.loads(request.body), data) - def test_send_request(self): + def test_get_metric(self): response = {u'stores.store_name.metric_name.agg': [{u'x': 1413936000000, u'y': 88916.0}, @@ -67,9 +71,29 @@ def test_send_request(self): "metrics/?m=stores.store_name.metric_name.agg" "&interval=1d&from=-30d&nulls=omit", adapter) - result = self.sender.get_metric( + result = self.client.get_metric( "stores.store_name.metric_name.agg", "-30d", "1d", "omit") self.assertEqual(result, response) self.check_request( adapter.request, 'GET', headers={"Authorization": u'Bearer auth-token'}) + + def test_fire(self): + response = [{ + 'name': 'foo.last', + 'value': 3.1415, + 'aggregator': 'last', + }] + adapter = RecordingAdapter(json.dumps(response)) + self.session.mount( + "http://example.com/api/v1/go/" + "metrics/", adapter) + + result = self.client.fire(**{ + "foo.last": 3.1415, + }) + self.assertEqual(result, response) + self.check_request( + adapter.request, 'POST', + data={"foo.last": 3.1415}, + headers={"Authorization": u'Bearer auth-token'}) From 527d060b5cdd877065f2f1f141ba68dfc5cf401b Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Thu, 30 Jul 2015 21:54:38 +0200 Subject: [PATCH 2/5] Mention in the HttpApiSender that metrics can now also be fired via the metrics API. --- go_http/send.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go_http/send.py b/go_http/send.py index b6581bb..31ad2c7 100644 --- a/go_http/send.py +++ b/go_http/send.py @@ -147,6 +147,9 @@ def fire_metric(self, metric, value, agg="last"): :param str agg: Aggregation type. Defaults to ``'last'``. Other allowed values are ``'sum'``, ``'avg'``, ``'max'`` and ``'min'``. + + Note that metrics can also be fired via the metrics API. + See :meth:`go_http.metrics.MetricsApiClient.fire`. """ data = [ [ From 61a7a5a8d4815ab0f3f7b3e33fcc470bd5e6da84 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 31 Jul 2015 16:16:14 +0200 Subject: [PATCH 3/5] Mention that invalid metric names cause metric firing to fail. --- go_http/metrics.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go_http/metrics.py b/go_http/metrics.py index 929e857..50f448a 100644 --- a/go_http/metrics.py +++ b/go_http/metrics.py @@ -87,6 +87,9 @@ def fire(self, **metrics): For example, ``foo.last`` fires a metric that uses the ``last`` aggregation method. + If a metric name does not end in a valid aggregator name, firing + the set of metrics will fail. + The available aggregators are: :Average: From 066baa084ae79099cbdec74ec987b8365861a9b9 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 31 Jul 2015 16:18:33 +0200 Subject: [PATCH 4/5] Switch to ordinary dict instead of kwargs since valid metric names are never valid Python identifiers. --- go_http/metrics.py | 2 +- go_http/tests/test_metrics.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go_http/metrics.py b/go_http/metrics.py index 50f448a..3e2d111 100644 --- a/go_http/metrics.py +++ b/go_http/metrics.py @@ -75,7 +75,7 @@ def get_metric(self, metric, start, interval, nulls): } return self._api_request("GET", "metrics/", payload) - def fire(self, **metrics): + def fire(self, metrics): """ Fire metrics. diff --git a/go_http/tests/test_metrics.py b/go_http/tests/test_metrics.py index 0bcf68d..4b3c0ba 100644 --- a/go_http/tests/test_metrics.py +++ b/go_http/tests/test_metrics.py @@ -89,7 +89,7 @@ def test_fire(self): "http://example.com/api/v1/go/" "metrics/", adapter) - result = self.client.fire(**{ + result = self.client.fire({ "foo.last": 3.1415, }) self.assertEqual(result, response) From fe1bfb41b0844f3c7fea22a314f6f5eae55cdce6 Mon Sep 17 00:00:00 2001 From: Simon Cross Date: Fri, 31 Jul 2015 16:51:56 +0200 Subject: [PATCH 5/5] Document that metric names *must* end in an aggregator. --- go_http/metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go_http/metrics.py b/go_http/metrics.py index 3e2d111..8c0e45d 100644 --- a/go_http/metrics.py +++ b/go_http/metrics.py @@ -82,7 +82,7 @@ def fire(self, metrics): :param dict metrics: A mapping of metric names to floating point metric values. - When metrics are fired they may specify an aggregator. The + When metrics are fired they must specify an aggregator. The aggregation method is determined by the suffix of the metric name. For example, ``foo.last`` fires a metric that uses the ``last`` aggregation method.