Skip to content

Commit 86dabf5

Browse files
authored
Merge pull request #44 from burmanm/feature/43
Allow use of datetime or timedelta where approriate, fixes #43
2 parents 19bc77d + 2cc57e5 commit 86dabf5

File tree

3 files changed

+79
-12
lines changed

3 files changed

+79
-12
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This repository includes the necessary Python client libraries to access Hawkula
77

88
## Introduction
99

10-
Python client to access Hawkular-Metrics, an abstraction to invoke REST-methods on the server endpoint using urllib2. No external dependencies, works with Python 2.7.x (tested on 2.7.5/2.7.6 and 2.7.10) and Python 3.4.x (tested with the Python 3.4.2, might work with newer versions also)
10+
Python client to access Hawkular-Metrics, an abstraction to invoke REST-methods on the server endpoint using urllib2. No external dependencies, works with Python 2.7.x (tested on 2.7.5/2.7.6 and 2.7.10/2.7.13) and Python 3.4.x / Python 3.5.x (tested with the Python 3.4.2 and Python 3.5.3, might work with newer versions also).
1111

1212
## License and copyright
1313

@@ -36,7 +36,7 @@ To install, run ``python setup.py install`` if you installed from source code, o
3636

3737
To use hawkular-client-python in your own program, after installation import from hawkular the class HawkularMetricsClient and instantiate it. After this, push dicts with keys id, timestamp and value with put or use assistant method create to send events. pydoc gives the list of allowed parameters for each function.
3838

39-
Timestamps should be in the milliseconds after epoch and numeric values should be float. The client provides a method to request current time in milliseconds, ``time_millis()``
39+
The client provides a method to request current time in milliseconds, ``time_millis()`` that's accepted by the methods, but you can use ``datetime`` and ``timedelta`` to control the time also when sending requests to the Hawkular-Metrics.
4040

4141
See metrics_test.py for more detailed examples and [Hawkular-Metrics documentation](http://www.hawkular.org/docs/components/metrics/index.html) for more detailed explanation of available features.
4242

@@ -100,8 +100,9 @@ Example pushing a multiple values:
100100

101101
```python
102102
>>> from hawkular.metrics import create_datapoint, create_metric, time_millis
103-
>>> datapoint = create_datapoint(float(4.35), time_millis())
104-
>>> datapoint2 = create_datapoint(float(4.42), time_millis() + 10)
103+
>>> t = datetime.utcnow()
104+
>>> datapoint = create_datapoint(float(4.35), t)
105+
>>> datapoint2 = create_datapoint(float(4.42), t + timedelta(seconds=10))
105106
>>> metric = create_metric(MetricType.Gauge, 'example.doc.1', [datapoint, datapoint2])
106107
>>> client.put(metric)
107108
```

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: 20 additions & 4 deletions
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):
@@ -262,9 +264,9 @@ def test_add_mixed_metrics_and_datapoints(self):
262264

263265
def test_query_options(self):
264266
# Create metric with two values
265-
t = time_millis()
267+
t = datetime.utcnow()
266268
v1 = create_datapoint(float(1.45), t)
267-
v2 = create_datapoint(float(2.00), (t - 2000))
269+
v2 = create_datapoint(float(2.00), (t - timedelta(seconds=2)))
268270

269271
m = create_metric(MetricType.Gauge, 'test.query.gauge.1', [v1, v2])
270272
self.client.put(m)
@@ -274,7 +276,7 @@ def test_query_options(self):
274276
self.assertEqual(2, len(d))
275277

276278
# Query for data which has start time limitation
277-
d = self.client.query_metric(MetricType.Gauge, 'test.query.gauge.1', start=(t - 1000))
279+
d = self.client.query_metric(MetricType.Gauge, 'test.query.gauge.1', start=(t - timedelta(seconds=1)))
278280
self.assertEqual(1, len(d))
279281

280282
def test_stats_queries(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)