Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

metric query API support #45

Merged
merged 1 commit into from
May 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions datadog/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# datadog
from datadog.api.exceptions import ClientError, ApiError, HttpBackoff, \
HttpTimeout, ApiNotInitialized
from datadog.api import _api_version, _timeout, _max_timeouts, _backoff_period
from datadog.api import _api_version, _max_timeouts, _backoff_period
from datadog.util.compat import json, is_p3k

log = logging.getLogger('dd.datadogpy')
Expand All @@ -22,7 +22,6 @@ class HTTPClient(object):
_backoff_timestamp = None
_timeout_counter = 0
_api_version = _api_version
_timeout = _timeout

@classmethod
def request(cls, method, path, body=None, attach_host_name=False, response_formatter=None,
Expand Down Expand Up @@ -62,7 +61,7 @@ def request(cls, method, path, body=None, attach_host_name=False, response_forma

# Import API, User and HTTP settings
from datadog.api import _api_key, _application_key, _api_host, \
_swallow, _host_name, _proxies, _max_retries
_swallow, _host_name, _proxies, _max_retries, _timeout

# Check keys and add then to params
if _api_key is None:
Expand Down Expand Up @@ -112,15 +111,15 @@ def request(cls, method, path, body=None, attach_host_name=False, response_forma
headers=headers,
params=params,
data=body,
timeout=cls._timeout,
timeout=_timeout,
proxies=_proxies)

result.raise_for_status()
except requests.ConnectionError as e:
raise ClientError("Could not request %s %s%s: %s" % (method, _api_host, url, e))
except requests.exceptions.Timeout as e:
cls._timeout_counter += 1
raise HttpTimeout('%s %s timed out after %d seconds.' % (method, url, cls._timeout))
raise HttpTimeout('%s %s timed out after %d seconds.' % (method, url, _timeout))
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404 or e.response.status_code == 400:
pass
Expand Down
4 changes: 2 additions & 2 deletions datadog/api/graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ def create(cls, **params):
:param metric_query: metric query
:type metric_query: string query

:param start: timestamp of the start of the query.
:param start: query start timestamp
:type start: POSIX timestamp

:param end: timestamp of the end of the query.
:param end: query end timestamp
:type end: POSIX timestamp

:param event_query: a query that will add event bands to the graph
Expand Down
52 changes: 47 additions & 5 deletions datadog/api/metrics.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import time

from datadog.api.base import SendableAPIResource
from datadog.api.base import SearchableAPIResource, SendableAPIResource
from datadog.api.exceptions import ApiError


class Metric(SendableAPIResource):

class Metric(SearchableAPIResource, SendableAPIResource):
"""
A wrapper around Metric HTTP API.
A wrapper around Metric HTTP API
"""
_class_url = '/series'
_class_url = None
_json_name = 'series'

_METRIC_QUERY_ENDPOINT = '/query'
_METRIC_SUBMIT_ENDPOINT = '/series'

@classmethod
def _process_points(cls, points):
now = time.time()
Expand Down Expand Up @@ -42,6 +45,9 @@ def send(cls, *metrics, **single_metric):

:returns: JSON response from HTTP request
"""
# Set the right endpoint
cls._class_url = cls._METRIC_SUBMIT_ENDPOINT

try:
if metrics:
for metric in metrics:
Expand All @@ -57,3 +63,39 @@ def send(cls, *metrics, **single_metric):
raise KeyError("'points' parameter is required")

return super(Metric, cls).send(attach_host_name=True, **metrics_dict)

@classmethod
def query(cls, **params):
"""
Query metrics from Datadog

:param start: query start timestamp
:type start: POSIX timestamp

:param end: query end timestamp
:type end: POSIX timestamp

:param query: metric query
:type query: string query

:return: JSON response from HTTP request

*start* and *end* should be less than 24 hours apart.
It is *not* meant to retrieve metric data in bulk.

>>> api.Metric.query(start=int(time.time()) - 3600, end=int(time.time()),
query='avg:system.cpu.idle{*}')
"""
# Set the right endpoint
cls._class_url = cls._METRIC_QUERY_ENDPOINT

# `from` is a reserved keyword in Python, therefore
# `api.Metric.query(from=...)` is not permited
# -> map `start` to `from` and `end` to `to`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better anyway since the rest of the api seems to expect start and end parameters when searching for something (I checked api//events.py)

try:
params['from'] = params.pop('start')
params['to'] = params.pop('end')
except KeyError as e:
raise ApiError("The parameter '{0}' is required".format(e.args[0]))

return super(Metric, cls)._search(**params)
16 changes: 11 additions & 5 deletions tests/integration/api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
from nose.tools import assert_true as ok

# datadog
import datadog.util.compat.json as json
from datadog import initialize
from datadog import api as dog
from datadog.api.exceptions import ApiError
from datadog.util.compat import json
from tests.util.snapshot_test_utils import (
assert_snap_not_blank, assert_snap_has_no_events
)
Expand Down Expand Up @@ -311,17 +311,23 @@ def test_search(self):
assert len(results['results']['hosts']) > 0
assert len(results['results']['metrics']) > 0

@attr("metric")
def test_metrics(self):
now = datetime.datetime.now()
now_ts = int(time.mktime(now.timetuple()))
metric_name = "test.metric." + str(now_ts)
host_name = "test.host." + str(now_ts)

dog.Metric.send(metric='test.metric.' + str(now_ts),
points=1, host="test.host." + str(now_ts))
dog.Metric.send(metric=metric_name, points=1, host=host_name)
time.sleep(self.wait_time)

results = dog.Infrastructure.search(q='metrics:test.metric.' + str(now_ts))
metric_query = dog.Metric.query(start=now_ts - 3600, end=now_ts + 3600,
query="avg:%s{host:%s}" % (metric_name, host_name))
assert len(metric_query['series']) == 1, metric_query

# results = dog.Infrastructure.search(q='metrics:test.metric.' + str(now_ts))
# TODO mattp: cache issue. move this test to server side.
# assert len(results['metrics']) == 1, results
# assert len(results['results']['metrics']) == 1, results

matt_series = [
(int(time.mktime((now - datetime.timedelta(minutes=25)).timetuple())), 5),
Expand Down
13 changes: 4 additions & 9 deletions tests/unit/api/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from datadog import initialize, api
from datadog.api.base import CreateableAPIResource, UpdatableAPIResource, DeletableAPIResource,\
GetableAPIResource, ListableAPIResource, ActionAPIResource
from datadog.util.compat import is_p3k, json
from datadog.util.compat import iteritems, json

# 3p
import requests
Expand Down Expand Up @@ -81,14 +81,9 @@ def request_called_with(self, method, url, data=None, params=None):

if params:
assert 'params' in others
if is_p3k():
for (k, v) in params.items():
assert k in others['params'], others['params']
assert v == others['params'][k]
else:
for (k, v) in params.iteritems():
assert k in others['params'], others['params']
assert v == others['params'][k]
for (k, v) in iteritems(params):
assert k in others['params'], others['params']
assert v == others['params'][k]

def tearDown(self):
self.request_patcher.stop()
Expand Down
15 changes: 14 additions & 1 deletion tests/unit/api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

# datadog
from datadog import initialize, api
from datadog.util.compat import is_p3k
from datadog.api import Metric
from datadog.api.exceptions import ApiNotInitialized
from datadog.util.compat import is_p3k
from tests.unit.api.helper import (
DatadogAPIWithInitialization,
DatadogAPINoInitialization,
Expand Down Expand Up @@ -192,3 +193,15 @@ def test_actionable(self):
MyActionable.trigger_action('POST', "actionname", id=actionable_object_id, mydata="val")
self.request_called_with('POST', "host/api/v1/actionname/" + str(actionable_object_id),
data={'mydata': "val"})

def test_metric_submit_query_switch(self):
"""
Specific to Metric subpackages: endpoints are different for submission and queries
"""
Metric.send(points="val")
self.request_called_with('POST', "host/api/v1/series",
data={'series': [{'points': "val", 'host': api._host_name}]})

Metric.query(start="val1", end="val2")
self.request_called_with('GET', "host/api/v1/query",
params={'from': "val1", 'to': "val2"})