Skip to content

Commit

Permalink
Allow use of datetime or timedelta where approriate, fixes #43
Browse files Browse the repository at this point in the history
  • Loading branch information
burmanm committed Mar 23, 2017
1 parent 19bc77d commit 9c1044a
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 5 deletions.
58 changes: 54 additions & 4 deletions hawkular/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import collections
import base64
import ssl
from datetime import datetime, timedelta

try:
import simplejson as json
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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 }

Expand Down
18 changes: 17 additions & 1 deletion tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from hawkular.metrics import *
import os
import base64
from datetime import datetime, timedelta

try:
import mock
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()

0 comments on commit 9c1044a

Please sign in to comment.