-
Notifications
You must be signed in to change notification settings - Fork 42
Created new data source for Librato and updated example YAML config #88
base: master
Are you sure you want to change the base?
Changes from all commits
7daaa35
646bfe8
91ab5e1
3b838ca
b0f87c6
b7a553e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| from datetime import datetime | ||
| from datetime import timedelta | ||
| from itertools import izip | ||
| from urlparse import urljoin | ||
|
|
||
| import requests | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to add this to setup.py? or why not? |
||
|
|
||
| try: | ||
| import simplejson as json | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not just use simplejson?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well the answer just says 'use one or the other as fallback' - it doesn't specifically tell you to use one over the other. Since json is in the standard library though wouldn't it make more sense to import that first? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK simplejson gets a lot more attention than built-in json.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd agree with gkayling on the import order of json/simplejson. Since json is built-in (well at least after 2.6), it makes sense to make it as a fall back. |
||
| except ImportError: | ||
| import json | ||
|
|
||
| import firefly.data_source | ||
|
|
||
| class Librato(firefly.data_source.DataSource): | ||
| """ | ||
| Stats from the Librato API | ||
| """ | ||
|
|
||
| DESC = "Librato" | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| super(Librato, self).__init__(*args, **kwargs) | ||
| self.librato_url = kwargs['librato_url'] | ||
| self.username = kwargs['username'] | ||
| self.password = kwargs['password'] | ||
| self.resolution = kwargs['resolution'] | ||
| self.offset = 100 | ||
|
|
||
| def list_path(self, path): | ||
| query = '.'.join(path + ['*']) | ||
| metric_name = query.split('*')[0] | ||
|
|
||
| contents = list() | ||
|
|
||
| # Each metric in Librato has a number of sources, as opposed to Firefly where everything is a source | ||
| # In the case that there is a metric name in the path, get its sources | ||
| if metric_name: | ||
| params = { | ||
| 'start_time': (datetime.now() - timedelta(hours=1)).strftime('%s'), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why starting with "1-hour" earlier? If you consider this as an arbitrary constant, define the constant up front and refer it by the variable name
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the default graph Firefly loads has an x-axis spanning an hour. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what happens with this if the user changes the firefly x-axis span? |
||
| 'resolution': self.resolution | ||
| } | ||
|
|
||
| find_url = self._build_librato_metrics_url(metric_name[:-1], params) | ||
|
|
||
| r = requests.get(find_url, auth=(self.username, self.password)) | ||
| find_results = r.json() | ||
|
|
||
| for measurement in find_results['measurements']: | ||
| contents.extend(self._form_entries_from_file(measurement)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would there be duplicated sources?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Within a particular metric? No. |
||
|
|
||
| # Otherwise, get all the metrics | ||
| else: | ||
| offset = 0 | ||
| metrics_remaining = True | ||
|
|
||
| # Librato can only send a 100 metrics at a time, so multiple requests are required to get all the metrics | ||
| while metrics_remaining: | ||
|
|
||
| find_url = urljoin(self.librato_url, 'v1/metrics?name=' + query + '&offset=' + str(offset)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-format to make it clear. Code across multiple lines is preferred. Seems that there's several similar "url building code blocks", could you isolate them out into a function? |
||
|
|
||
| r = requests.get(find_url, auth=(self.username, self.password)) | ||
| find_results = r.json() | ||
|
|
||
| offset += self.offset | ||
|
|
||
| if find_results['metrics']: | ||
| for metric in find_results['metrics']: | ||
| contents.extend(self._form_entries_from_dir(metric['name'])) | ||
| else: | ||
| metrics_remaining = False | ||
|
|
||
| return contents | ||
|
|
||
| def _form_entries_from_dir(self, name): | ||
| return [{'type': 'dir', 'name': name, 'children': None}] | ||
|
|
||
| def _form_entries_from_file(self, name): | ||
| return [{'type': 'file', 'name': name}] | ||
|
|
||
| def data(self, sources, start, end, width): | ||
|
|
||
| params = { | ||
| 'start_time': start, | ||
| 'end_time': end, | ||
| 'resolution': self.resolution | ||
| } | ||
|
|
||
| metric_name = sources[0][0] | ||
| source = sources[0][1] | ||
|
|
||
| render_url = self._build_librato_metrics_url(metric_name, params) | ||
|
|
||
| r = requests.get(render_url, auth=(self.username, self.password)) | ||
| render_results = r.json() | ||
|
|
||
| out = [] | ||
|
|
||
| for result in render_results['measurements']: | ||
| # Librato shortens really long source names with a '..' | ||
| # So, we check if either the source name matches or the beginning and end of the shortened source name are substrings | ||
| if (source == result) or all(substr in result for substr in source.split('..')): | ||
| for datapoint in render_results['measurements'][source]: | ||
| point = {} | ||
| point['t'] = datapoint['measure_time'] | ||
| point['v'] = [datapoint['value']] | ||
| out.append(point) | ||
| break | ||
|
|
||
| return json.dumps(out) | ||
|
|
||
| def _build_librato_metrics_url(self, metric_name, params): | ||
| url_params = 'v1/metrics/' + metric_name + '?' | ||
| url_params += '&'.join(['%s=%s' % (k, v) for k, v in params.items()]) | ||
| return urljoin(self.librato_url, url_params) | ||
|
|
||
| def legend(self, sources): | ||
| return self._svc(sources) | ||
|
|
||
| def title(self, sources): | ||
| return ["librato"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove extra empty line(s) at the end of a file |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ | |
| PyHamcrest==1.8.0 | ||
| testify==0.5.2 | ||
| flake8 | ||
| requests>=2.3, <3.0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ | |
| -e . | ||
| PyYAML==3.10 | ||
| tornado>= 1.1, <2.0 | ||
| requests>= 2.3, < 3.0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| "pycurl", | ||
| "pyyaml == 3.10", | ||
| "tornado >= 1.1, <2.0", | ||
| "requests >=2.3, <3.0", | ||
| ] | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
urllib2 and requests? pick one or the other.
also this requirement needs to be added to our requirements.txt