Skip to content

Commit

Permalink
Implemented retrieval of metrics (the main function of this library).…
Browse files Browse the repository at this point in the history
… Still need to fix some todos and allow querying by things other than app/app_id
  • Loading branch information
andrewgross committed Apr 6, 2012
1 parent 6cdacd1 commit 9f786d0
Showing 1 changed file with 70 additions and 44 deletions.
114 changes: 70 additions & 44 deletions newrelic.py
@@ -1,5 +1,6 @@
import requests
import logging
import sys

from time import time
from urllib import urlencode
Expand All @@ -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
Expand All @@ -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):
Expand All @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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}

Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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.