Skip to content
This repository was archived by the owner on Mar 22, 2023. It is now read-only.
Open
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
5 changes: 5 additions & 0 deletions firefly.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ data_server:
# The data sources to collect data from.
# These are Python modules contained in data_sources/
data_sources:
- data_sources.librato.Librato
- data_sources.ganglia_rrd.GangliaRRD
- data_sources.stat_monster_rrd.StatMonsterRRD
# - data_sources.graphite_wsp.GraphiteWSP
Expand All @@ -48,6 +49,10 @@ data_server:
data_sources.graphite_wsp.GraphiteWSP:
# path to the root of Graphite whisper files
graphite_storage: null
data_sources.librato.Librato:
librato_url: "https://metrics-api.librato.com/"
username: "librato_username"
password: "librato_password"

# The location of the SQLite database file which contains the data store
# for the DATA SERVER
Expand Down
121 changes: 121 additions & 0 deletions firefly/data_sources/librato.py
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
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

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

why not just use simplejson?

Copy link
Author

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Yes.
It seems that this answer favours importing simplejson first, and json as a fallback, which I agree with.
You have the opposite.

Copy link
Author

Choose a reason for hiding this comment

The 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?

Choose a reason for hiding this comment

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

AFAIK simplejson gets a lot more attention than built-in json.
But I'll defer to you if you feel more strongly about it.

Copy link
Contributor

Choose a reason for hiding this comment

The 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'),
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Author

Choose a reason for hiding this comment

The 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.

Choose a reason for hiding this comment

The 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))
Copy link
Contributor

Choose a reason for hiding this comment

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

Would there be duplicated sources?

Copy link
Author

Choose a reason for hiding this comment

The 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))
Copy link
Contributor

Choose a reason for hiding this comment

The 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"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove extra empty line(s) at the end of a file

1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
PyHamcrest==1.8.0
testify==0.5.2
flake8
requests>=2.3, <3.0
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
-e .
PyYAML==3.10
tornado>= 1.1, <2.0
requests>= 2.3, < 3.0
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"pycurl",
"pyyaml == 3.10",
"tornado >= 1.1, <2.0",
"requests >=2.3, <3.0",
]


Expand Down