From 9c1044abab04df751bb2af4a42d09e1d7a097a26 Mon Sep 17 00:00:00 2001 From: Michael Burman Date: Thu, 23 Mar 2017 11:32:20 +0200 Subject: [PATCH] Allow use of datetime or timedelta where approriate, fixes #43 --- hawkular/metrics.py | 58 ++++++++++++++++++++++++++++++++++++++++--- tests/test_metrics.py | 18 +++++++++++++- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/hawkular/metrics.py b/hawkular/metrics.py index 0bd077e..ac18005 100644 --- a/hawkular/metrics.py +++ b/hawkular/metrics.py @@ -21,6 +21,7 @@ import collections import base64 import ssl +from datetime import datetime, timedelta try: import simplejson as json @@ -58,6 +59,8 @@ class HawkularMetricsClient(HawkularBaseClient): """ Internal methods """ + epoch = datetime.utcfromtimestamp(0) + def _get_url(self, metric_type=None): if metric_type is None: metric_type = MetricType._Metrics @@ -139,32 +142,70 @@ def push(self, metric_type, metric_id, value, timestamp=None): :param metric_type: MetricType to be matched (required) :param metric_id: Exact string matching metric id :param value: Datapoint value (depending on the MetricType) - :param timestamp: Timestamp of the datapoint. If left empty, uses current client time. + :param timestamp: Timestamp of the datapoint. If left empty, uses current client time. Can be milliseconds since epoch or datetime instance """ + if type(timestamp) is datetime: + timestamp = datetime_to_time_millis(timestamp) + item = create_metric(metric_type, metric_id, create_datapoint(value, timestamp)) self.put(item) - def query_metric(self, metric_type, metric_id, **query_options): + def query_metric(self, metric_type, metric_id, start=None, end=None, **query_options): """ Query for metrics datapoints from the server. :param metric_type: MetricType to be matched (required) :param metric_id: Exact string matching metric id + :param start: Milliseconds since epoch or datetime instance + :param end: Milliseconds since epoch or datetime instance :param query_options: For possible query_options, see the Hawkular-Metrics documentation. """ + if start is not None: + if type(start) is datetime: + query_options['start'] = datetime_to_time_millis(start) + else: + query_options['start'] = start + + if end is not None: + if type(end) is datetime: + query_options['end'] = datetime_to_time_millis(end) + else: + query_options['end'] = end + return self._get( self._get_metrics_raw_url( self._get_metrics_single_url(metric_type, metric_id)), **query_options) - def query_metric_stats(self, metric_type, metric_id, **query_options): + def query_metric_stats(self, metric_type, metric_id, start=None, end=None, bucketDuration=None, **query_options): """ Query for metric aggregates from the server. This is called buckets in the Hawkular-Metrics documentation. :param metric_type: MetricType to be matched (required) :param metric_id: Exact string matching metric id + :param start: Milliseconds since epoch or datetime instance + :param end: Milliseconds since epoch or datetime instance + :param bucketDuration: The timedelta or duration of buckets. Can be a string presentation or timedelta object :param query_options: For possible query_options, see the Hawkular-Metrics documentation. """ + if start is not None: + if type(start) is datetime: + query_options['start'] = datetime_to_time_millis(start) + else: + query_options['start'] = start + + if end is not None: + if type(end) is datetime: + query_options['end'] = datetime_to_time_millis(end) + else: + query_options['end'] = end + + if bucketDuration is not None: + if type(bucketDuration) is timedelta: + query_options['bucketDuration'] = timedelta_to_duration(bucketDuration) + else: + query_options['bucketDuration'] = bucketDuration + return self._get( self._get_metrics_stats_url( self._get_metrics_single_url(metric_type, metric_id)), @@ -312,17 +353,26 @@ def time_millis(): """ return int(round(time.time() * 1000)) +def timedelta_to_duration(td): + return '{}s'.format(int(td.total_seconds())) + +def datetime_to_time_millis(dt): + return '{:.0f}'.format((dt - HawkularMetricsClient.epoch).total_seconds() * 1000) + def create_datapoint(value, timestamp=None, **tags): """ Creates a single datapoint dict with a value, timestamp and tags. :param value: Value of the datapoint. Type depends on the id's MetricType - :param timestamp: Optional timestamp of the datapoint. Uses client current time if not set. Millisecond accuracy + :param timestamp: Optional timestamp of the datapoint. Uses client current time if not set. Millisecond accuracy. Can be datetime instance also. :param tags: Optional datapoint tags. Not to be confused with metric definition tags """ if timestamp is None: timestamp = time_millis() + if type(timestamp) is datetime: + timestamp = datetime_to_time_millis(timestamp) + item = { 'timestamp': timestamp, 'value': value } diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 09a70d3..a636b9a 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -21,6 +21,7 @@ from hawkular.metrics import * import os import base64 +from datetime import datetime, timedelta try: import mock @@ -196,7 +197,8 @@ def test_add_gauge_single(self): self.client.push(MetricType.Gauge, 'test.gauge.single.tags', value) # Fetch results - data = self.client.query_metric(MetricType.Gauge, 'test.gauge.single.tags') + now = datetime.utcnow() + data = self.client.query_metric(MetricType.Gauge, 'test.gauge.single.tags', start=now-timedelta(minutes = 1), end=now) self.assertEqual(value, float(data[0]['value'])) def test_add_availability_single(self): @@ -299,6 +301,11 @@ def test_stats_queries(self): self.assertEqual(10, bp[0]['samples']) self.assertEqual(2, len(bp[0]['percentiles'])) + now = datetime.utcfromtimestamp(t/1000) + + bp = self.client.query_metric_stats(MetricType.Gauge, 'test.buckets.1', bucketDuration=timedelta(seconds=2), start=now-timedelta(seconds=10), end=now, distinct=True) + self.assertEqual(5, len(bp), "Single bucket is two seconds") + def test_tenant_changing(self): self.client.create_metric_definition(MetricType.Availability, 'test.tenant.avail.1') # Fetch metrics and check that it did appear @@ -337,6 +344,15 @@ def test_get_metrics_stats_url(self): url = self.client._get_metrics_stats_url('some.key') self.assertEqual('some.key/stats', url) + def test_timedelta_to_duration_string(self): + s = timedelta_to_duration(timedelta(hours=1, minutes=3, seconds=4)) + self.assertEqual('3784s', s) + + s = timedelta_to_duration(timedelta(hours=1, seconds=4)) + self.assertEqual('3604s', s) + + s = timedelta_to_duration(timedelta(days=4)) + self.assertEqual('345600s', s) if __name__ == '__main__': unittest.main()