Skip to content

Commit 9c1044a

Browse files
committed
Allow use of datetime or timedelta where approriate, fixes #43
1 parent 19bc77d commit 9c1044a

2 files changed

Lines changed: 71 additions & 5 deletions

File tree

hawkular/metrics.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import collections
2222
import base64
2323
import ssl
24+
from datetime import datetime, timedelta
2425

2526
try:
2627
import simplejson as json
@@ -58,6 +59,8 @@ class HawkularMetricsClient(HawkularBaseClient):
5859
"""
5960
Internal methods
6061
"""
62+
epoch = datetime.utcfromtimestamp(0)
63+
6164
def _get_url(self, metric_type=None):
6265
if metric_type is None:
6366
metric_type = MetricType._Metrics
@@ -139,32 +142,70 @@ def push(self, metric_type, metric_id, value, timestamp=None):
139142
:param metric_type: MetricType to be matched (required)
140143
:param metric_id: Exact string matching metric id
141144
:param value: Datapoint value (depending on the MetricType)
142-
:param timestamp: Timestamp of the datapoint. If left empty, uses current client time.
145+
:param timestamp: Timestamp of the datapoint. If left empty, uses current client time. Can be milliseconds since epoch or datetime instance
143146
"""
147+
if type(timestamp) is datetime:
148+
timestamp = datetime_to_time_millis(timestamp)
149+
144150
item = create_metric(metric_type, metric_id, create_datapoint(value, timestamp))
145151
self.put(item)
146152

147-
def query_metric(self, metric_type, metric_id, **query_options):
153+
def query_metric(self, metric_type, metric_id, start=None, end=None, **query_options):
148154
"""
149155
Query for metrics datapoints from the server.
150156
151157
:param metric_type: MetricType to be matched (required)
152158
:param metric_id: Exact string matching metric id
159+
:param start: Milliseconds since epoch or datetime instance
160+
:param end: Milliseconds since epoch or datetime instance
153161
:param query_options: For possible query_options, see the Hawkular-Metrics documentation.
154162
"""
163+
if start is not None:
164+
if type(start) is datetime:
165+
query_options['start'] = datetime_to_time_millis(start)
166+
else:
167+
query_options['start'] = start
168+
169+
if end is not None:
170+
if type(end) is datetime:
171+
query_options['end'] = datetime_to_time_millis(end)
172+
else:
173+
query_options['end'] = end
174+
155175
return self._get(
156176
self._get_metrics_raw_url(
157177
self._get_metrics_single_url(metric_type, metric_id)),
158178
**query_options)
159179

160-
def query_metric_stats(self, metric_type, metric_id, **query_options):
180+
def query_metric_stats(self, metric_type, metric_id, start=None, end=None, bucketDuration=None, **query_options):
161181
"""
162182
Query for metric aggregates from the server. This is called buckets in the Hawkular-Metrics documentation.
163183
164184
:param metric_type: MetricType to be matched (required)
165185
:param metric_id: Exact string matching metric id
186+
:param start: Milliseconds since epoch or datetime instance
187+
:param end: Milliseconds since epoch or datetime instance
188+
:param bucketDuration: The timedelta or duration of buckets. Can be a string presentation or timedelta object
166189
:param query_options: For possible query_options, see the Hawkular-Metrics documentation.
167190
"""
191+
if start is not None:
192+
if type(start) is datetime:
193+
query_options['start'] = datetime_to_time_millis(start)
194+
else:
195+
query_options['start'] = start
196+
197+
if end is not None:
198+
if type(end) is datetime:
199+
query_options['end'] = datetime_to_time_millis(end)
200+
else:
201+
query_options['end'] = end
202+
203+
if bucketDuration is not None:
204+
if type(bucketDuration) is timedelta:
205+
query_options['bucketDuration'] = timedelta_to_duration(bucketDuration)
206+
else:
207+
query_options['bucketDuration'] = bucketDuration
208+
168209
return self._get(
169210
self._get_metrics_stats_url(
170211
self._get_metrics_single_url(metric_type, metric_id)),
@@ -312,17 +353,26 @@ def time_millis():
312353
"""
313354
return int(round(time.time() * 1000))
314355

356+
def timedelta_to_duration(td):
357+
return '{}s'.format(int(td.total_seconds()))
358+
359+
def datetime_to_time_millis(dt):
360+
return '{:.0f}'.format((dt - HawkularMetricsClient.epoch).total_seconds() * 1000)
361+
315362
def create_datapoint(value, timestamp=None, **tags):
316363
"""
317364
Creates a single datapoint dict with a value, timestamp and tags.
318365
319366
:param value: Value of the datapoint. Type depends on the id's MetricType
320-
:param timestamp: Optional timestamp of the datapoint. Uses client current time if not set. Millisecond accuracy
367+
:param timestamp: Optional timestamp of the datapoint. Uses client current time if not set. Millisecond accuracy. Can be datetime instance also.
321368
:param tags: Optional datapoint tags. Not to be confused with metric definition tags
322369
"""
323370
if timestamp is None:
324371
timestamp = time_millis()
325372

373+
if type(timestamp) is datetime:
374+
timestamp = datetime_to_time_millis(timestamp)
375+
326376
item = { 'timestamp': timestamp,
327377
'value': value }
328378

tests/test_metrics.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from hawkular.metrics import *
2222
import os
2323
import base64
24+
from datetime import datetime, timedelta
2425

2526
try:
2627
import mock
@@ -196,7 +197,8 @@ def test_add_gauge_single(self):
196197
self.client.push(MetricType.Gauge, 'test.gauge.single.tags', value)
197198

198199
# Fetch results
199-
data = self.client.query_metric(MetricType.Gauge, 'test.gauge.single.tags')
200+
now = datetime.utcnow()
201+
data = self.client.query_metric(MetricType.Gauge, 'test.gauge.single.tags', start=now-timedelta(minutes = 1), end=now)
200202
self.assertEqual(value, float(data[0]['value']))
201203

202204
def test_add_availability_single(self):
@@ -299,6 +301,11 @@ def test_stats_queries(self):
299301
self.assertEqual(10, bp[0]['samples'])
300302
self.assertEqual(2, len(bp[0]['percentiles']))
301303

304+
now = datetime.utcfromtimestamp(t/1000)
305+
306+
bp = self.client.query_metric_stats(MetricType.Gauge, 'test.buckets.1', bucketDuration=timedelta(seconds=2), start=now-timedelta(seconds=10), end=now, distinct=True)
307+
self.assertEqual(5, len(bp), "Single bucket is two seconds")
308+
302309
def test_tenant_changing(self):
303310
self.client.create_metric_definition(MetricType.Availability, 'test.tenant.avail.1')
304311
# Fetch metrics and check that it did appear
@@ -337,6 +344,15 @@ def test_get_metrics_stats_url(self):
337344
url = self.client._get_metrics_stats_url('some.key')
338345
self.assertEqual('some.key/stats', url)
339346

347+
def test_timedelta_to_duration_string(self):
348+
s = timedelta_to_duration(timedelta(hours=1, minutes=3, seconds=4))
349+
self.assertEqual('3784s', s)
350+
351+
s = timedelta_to_duration(timedelta(hours=1, seconds=4))
352+
self.assertEqual('3604s', s)
353+
354+
s = timedelta_to_duration(timedelta(days=4))
355+
self.assertEqual('345600s', s)
340356

341357
if __name__ == '__main__':
342358
unittest.main()

0 commit comments

Comments
 (0)