Permalink
Browse files

Implemented retrieval of metrics (the main function of this library).…

… Still need to fix some todos and allow querying by things other than app/app_id
  • Loading branch information...
1 parent 6cdacd1 commit 9f786d0fc05dc5929c51681cf6ccd32414804037 @andrewgross committed Apr 6, 2012
Showing with 70 additions and 44 deletions.
  1. +70 −44 newrelic.py
View
@@ -1,5 +1,6 @@
import requests
import logging
+import sys
from time import time
from urllib import urlencode
@@ -14,10 +15,9 @@
logger = logging.getLogger(__name__)
-
class Client(object):
"""A Client for interacting with New Relic resources"""
- def __init__(self, account_id=None, api_key=None, proxy=None, retries=3, retry_delay=1, timeout=1.000):
+ def __init__(self, account_id=None, api_key=None, proxy=None, retries=3, retry_delay=1, timeout=1.000, debug=False):
"""
Create a NewRelic REST API client
TODO: implement proxy support
@@ -41,41 +41,48 @@ def __init__(self, account_id=None, api_key=None, proxy=None, retries=3, retry_d
self.retries = retries
self.retry_delay = retry_delay
self.timeout = timeout
+ self.debug = debug
+ if self.debug is True:
+ self.config = {'verbose': sys.stderr}
+ else:
+ self.config = {}
def _make_request(self, request, uri, **kwargs):
- attempts = 0
+ attempts = 1
response = None
- while attempts < self.retries:
+ while attempts <= self.retries:
try:
- response = request(uri, **kwargs)
+ response = request(uri, config=self.config, **kwargs)
except (requests.ConnectionError, requests.HTTPError) as ce:
logger.error('Error connecting to New Relic API: {}'.format(ce))
sleep(self.retry_delay)
attempts += 1
else:
break
+ if not response and attempts > 1:
+ raise NewRelicApiException('Unable to connect to the NewRelic API after {} attempts'.format(attempts))
if not response:
- raise NewRelicApiException
- if not str(response.status_code).startswith('2') :
+ raise NewRelicApiException('No response received from NewRelic API')
+ if not str(response.status_code).startswith('2'):
self._handle_api_error(response.status_code)
return self._parse_xml(response.text)
def _parse_xml(self, response):
parser = etree.XMLParser(remove_blank_text=True, strip_cdata=False, ns_clean=True, recover=True)
if response.startswith('<?xml version="1.0" encoding="UTF-8"?>'):
- response = ''.join(response.split('\n')[1:])
+ response = '\n'.join(response.split('\n')[1:])
tree = etree.XML(response, parser)
return tree
- def _handle_api_error(self, status_code):
+ def _handle_api_error(self, status_code, error_message):
if 403 == status_code:
- raise NewRelicInvalidApiKeyException
+ raise NewRelicInvalidApiKeyException(error_message)
elif 404 == status_code:
- raise NewRelicUnknownApplicationException
+ raise NewRelicUnknownApplicationException(error_message)
elif 422 == status_code:
- raise NewRelicInvalidParameterException
+ raise NewRelicInvalidParameterException(error_message)
else:
- raise NewRelicApiException
+ raise NewRelicApiException(error_message)
def _make_get_request(self, uri, parameters=None, timeout=None):
@@ -98,7 +105,8 @@ def _api_rate_limit_exceeded(self, api_call, window=60):
"""
We want to keep track of the last time we sent a request to the NewRelic API, but only for certain operations.
This method will dynamically add an attribute to the Client class with a unix timestamp with the name of the API api_call
- we make so that we can check it later.
+ we make so that we can check it later. We return the amount of time until we can perform another API call so that appropriate waiting
+ can be implemented.
"""
current_call_time = int(time())
try:
@@ -111,7 +119,7 @@ def _api_rate_limit_exceeded(self, api_call, window=60):
setattr(self, api_call.__name__ + ".window", current_call_time)
return False
else:
- return True
+ return window - (current_call_time - previous_call_time)
def view_applications(self):
@@ -178,8 +186,8 @@ def get_metric_names(self, app_id, re=None, limit=5000):
Errors: 403 Invalid API Key, 422 Invalid Parameters
Endpoint: api.newrelic.com
"""
- if self._api_rate_limit_exceeded(self.get_metric_names):
- raise NewRelicApiRateLimitException
+ if self._api_rate_limit_exceeded(self.get_metric_data):
+ raise NewRelicApiRateLimitException(str(self._api_rate_limit_exceeded(self.get_metric_data)))
parameters = {'re': re, 'limit': limit}
@@ -195,22 +203,25 @@ def get_metric_names(self, app_id, re=None, limit=5000):
metrics[metric.get('name')] = fields
return metrics
- def get_metric_data(self, applications, metrics, fields, start_time, end_time, summary=False):
+ def get_metric_data(self, applications, metrics, field, begin, end, summary=False):
"""
- Requires: account ID, list of application IDs, list of metrics, metric fields, begin_time, end_time
+ Requires: account ID, list of application IDs, list of metrics, metric fields, begin, end
Method: Get
Endpoint: api.newrelic.com
Restrictions: Rate limit to 1x per minute
Errors: 403 Invalid API key, 422 Invalid Parameters
- Returns: A list of tuples, (app name, begin_time, end_time, metric_name, [(field name, value),...])
+ Returns: A list of metric objects, each will have information about its start/end time, application, metric name and
+ any associated values
"""
+ # Make sure we aren't going to hit an API timeout
if self._api_rate_limit_exceeded(self.get_metric_data):
- raise NewRelicApiRateLimitException
+ raise NewRelicApiRateLimitException(str(self._api_rate_limit_exceeded(self.get_metric_data)))
- parameters = {}
+ # Just in case the API needs parameters to be in order
+ parameters = OrderedDict()
- # Figure out what we were passed and set out parameter correctly
+ # Figure out what we were passed and set our parameter correctly
try:
int(applications[0])
except ValueError:
@@ -222,54 +233,57 @@ def get_metric_data(self, applications, metrics, fields, start_time, end_time, s
app_string = app_string + "[]"
# Set our parameters
- for app in applications:
- parameters[app_string] = app
+ parameters[app_string] = applications
- for metric in metrics:
- parameters['metrics[]'] = metric
+ parameters['metrics[]'] = metrics
- for field in fields:
- parameters['field'] = field
+ parameters['field'] = field
- parameters['begin'] = begin_time
- parameters['end'] = end_time
+ parameters['begin'] = begin
+ parameters['end'] = end
parameters['summary'] = int(summary)
- uri = "https://api.newrelic.com/api/v1/accounts/{0}/metrics/data.xml".format(str(app_id))
+ uri = "https://api.newrelic.com/api/v1/accounts/{0}/metrics/data.xml".format(str(self.account_id))
# A longer timeout is needed due to the amount of data that can be returned
response = self._make_get_request(uri, parameters=parameters, timeout=5.000)
+ # Parsing our response
+ returned_metrics = []
+ for metric in response.xpath('/metrics/metric'):
+ m = Metric(metric)
+ returned_metrics.append(m)
+ return returned_metrics
# Exceptions
class NewRelicApiException(Exception):
- def __init__(self):
+ def __init__(self, message):
super(NewRelicApiException, self).__init__()
- pass
+ print message
class NewRelicInvalidApiKeyException(NewRelicApiException):
- def __init__(self):
- super(NewRelicInvalidApiKeyException, self).__init__()
+ def __init__(self, message):
+ super(NewRelicInvalidApiKeyException, self).__init__(message)
pass
class NewRelicCredentialException(NewRelicApiException):
- def __init__(self):
- super(NewRelicCredentialException, self).__init__()
+ def __init__(self, message):
+ super(NewRelicCredentialException, self).__init__(message)
pass
class NewRelicInvalidParameterException(NewRelicApiException):
- def __init__(self):
- super(NewRelicInvalidParameterException, self).__init__()
+ def __init__(self, message):
+ super(NewRelicInvalidParameterException, self).__init__(message)
pass
class NewRelicUnknownApplicationException(NewRelicApiException):
- def __init__(self):
- super(NewRelicUnknownApplicationException, self).__init__()
+ def __init__(self, message):
+ super(NewRelicUnknownApplicationException, self).__init__(message)
pass
class NewRelicApiRateLimitException(NewRelicApiException):
- def __init__(self, arg):
- super(NewRelicApiRateLimitException, self).__init__()
+ def __init__(self, message):
+ super(NewRelicApiRateLimitException, self).__init__(message)
pass
# Data Classes
@@ -280,3 +294,15 @@ def __init__(self, properties):
self.name = properties['name']
self.app_id = properties['id']
self.url = properties['overview-url']
+
+class Metric(object):
+ def __init__(self, metric):
+ super(Metric, self).__init__()
+ for k,v in metric.items():
+ setattr(self, k, v)
+ for field in metric.xpath('field'):
+ # Each field has a 'name=metric_type' section. We want to have this accessible in the object by calling the
+ # metric_type property of the object directly
+ setattr(self, field.values()[0], field.text)
+
+

0 comments on commit 9f786d0

Please sign in to comment.