Skip to content

Instantly share code, notes, and snippets.

@jaredhirsch
Last active April 1, 2016 07:25
Show Gist options
  • Save jaredhirsch/5709485 to your computer and use it in GitHub Desktop.
Save jaredhirsch/5709485 to your computer and use it in GitHub Desktop.
ELB dashboard JSON.

This gist explains how to create a generic ELB monitoring dashboard in graphite 0.9.10, which allows import/export, but not via dashboard UI.

The python script attached to this gist should work, but it didn't immediately work for me, so I just did the following via the python repl:

Python 2.7.2 (default, Oct 11 2012, 20:14:37) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import json
>>> import sys
>>> import urllib
>>> jason = json.dumps(json.load(open('elb-dashboard.json', 'r'))['state'])
>>> post = "state=" + urllib.quote(jason)
>>> result = json.load(urllib.urlopen('http://localhost:8005/dashboard/save/elb', data=post))
>>> result
{u'success': True}

This created a fully generic ELB-monitoring dashboard at http://localhost:8005/dashboard/elb. 🍻

The only thing that needs to vary here is the endpoint for the graphite server; I've used http://localhost:8005 simply because I'm connected via port forwarding from my laptop.

The relevant JSON file is also attached to this gist. I'll see if I can figure out how to add a screenshot. If not, check one out at http://i.imgur.com/aF4YTnO.png

from __future__ import print_function
# @whd's script from https://people.mozilla.org/~wdawson/dashboard_api
# simple script to demonstrate graphite dashboard api
import json
import sys
import urllib
GRAPHITE_URL = sys.argv[1]
ACTION = sys.argv[2] # GET|CHECK|SAVE
if ACTION == "GET":
name = sys.argv[3]
endpoint = GRAPHITE_URL + '/dashboard/load/' + name
print(urllib.urlopen(endpoint).read())
elif ACTION == "CHECK":
name = sys.argv[3]
endpoint = GRAPHITE_URL + '/dashboard/find/?query=' + name
res = json.load(urllib.urlopen(endpoint))['dashboards']
exit(0 if len(res) > 0 else 1)
elif ACTION == "SAVE":
name = sys.argv[3]
endpoint = GRAPHITE_URL + '/dashboard/save/' + name
json_file = sys.argv[4]
jason = json.dumps(json.load(open(json_file, 'r'))['state'])
encoded = urllib.quote(jason)
post = "state=" + encoded
result = json.load(urllib.urlopen(endpoint, data=post))
exit(0 if result['success'] else 1)
else:
exit(2)
{
"state": {
"name": "elb",
"defaultGraphParams": {
"width": 400,
"from": "-30minutes",
"until": "now",
"height": 250
},
"refreshConfig": {
"interval": 60000,
"enabled": true
},
"graphs": [
["target=aws.elb.persona-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count", {
"from": "-30minutes",
"target": ["aws.elb.persona-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count"],
"title": "Webheads",
"areaMode": "first",
"areaAlpha": "0.5",
"height": "349",
"width": "715",
"_salt": "1370021062.985",
"until": "now"
}, "/render?width=400&from=-30minutes&until=now&height=250&target=aws.elb.persona-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count&title=Webheads&_salt=1370021062.985&areaAlpha=0.5&areaMode=first&_uniq=0.31552550014823133"
],
["target=aws.elb.dbwrite.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count", {
"from": "-30minutes",
"target": ["aws.elb.dbwrite.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count"],
"title": "DBwriter",
"height": "308",
"width": "586",
"_salt": "1370021188.508",
"until": "now"
}, "/render?width=400&from=-30minutes&until=now&height=250&target=aws.elb.dbwrite.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count&title=DBwriter&_salt=1370021188.508&_uniq=0.4476449452851562"
],
["target=aws.elb.keysign.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count", {
"from": "-30minutes",
"target": ["aws.elb.keysign.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count"],
"title": "Keysigner",
"height": "308",
"width": "586",
"_salt": "1370027700.183",
"until": "now"
}, "/render?width=400&from=-30minutes&until=now&height=250&target=aws.elb.keysign.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count&title=Keysigner&_salt=1370027700.183&_uniq=0.8304083006714642"
],
["target=aws.elb.bt-login-persona-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count", {
"width": "586",
"height": "308",
"target": ["aws.elb.bt-login-persona-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count"],
"_salt": "1370027894.053",
"title": "Bigtent"
}, "/render?width=400&from=-30minutes&until=now&height=250&target=aws.elb.bt-login-persona-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count&_salt=1370027894.053&title=Bigtent&_uniq=0.38898555644169097"
],
["target=aws.elb.browserid-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count", {
"width": "586",
"height": "308",
"target": ["aws.elb.browserid-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count"],
"_salt": "1370028212.911",
"title": "browserid-org"
}, "/render?width=400&from=-30minutes&until=now&height=250&target=aws.elb.browserid-org.{requestcount,httpcode_backend_2xx,httpcode_backend_5xx,httpcode_backend_4xx,httpcode_backend_3xx}.sum.count&_salt=1370028212.911&title=browserid-org&_uniq=0.688600230684627"
]
],
"timeConfig": {
"startDate": "2013-05-31T10:18:49",
"endTime": "5:00 PM",
"endDate": "2013-05-31T10:18:49",
"startTime": "9:00 AM",
"type": "relative"
},
"graphSize": {
"width": 400,
"height": 250
}
}
}

More info, from @whd:

Some good news.

You won't need to roll a new graphite to get the programmatic dashboard import stuff. It's all there under the hood in the latest stable; only the GUI edit button didn't make it to 0.9.10.

API Summary: (all payloads are JSON)

/GET GRAPHITE_HOST/dashboard/find/?query=NAME

Return an array of matching results, for our purposes simply filtering on name suffices to determine if a dashboard is defined.

/GET GRAPHITE_HOST/dashboard/load/NAME

Return the specification of the dashboard NAME.

/POST GRAPHITE_HOST/dashboard/save/NAME

Save the dashboard definition to Django's internal structures. Here is the annoying part. The server interprets the POST as a URL-encoded query string, since it's usually constructed by the JS of the webapp. The webapp does a conversion from:

{"state": STUFF} to state=STUFF

so the latter is what the server expects to receive. STUFF is a giant embedded JSON string and since it's URL-encoded it is nigh impossible to actually read, e.g.

state=%7B%22name%22%3A%22bar%22%2C%22timeConfig%22%3A%7B%22type%22%3A%22relative%22%2C%22relativeStartQuantity%22%3A%222%22%2C%22relativeStartUnits%22%3A%22hours%22%2C%22relativeUntilQuantity%22%3A%22%22%2C%22relativeUntilUnits%22%3A%22now%22%2C%22startDate%22%3A%222013-06-03T08%3A26%3A46%22%2C%22startTime%22%3A%229%3A00%20AM%22%2C%22endDate%22%3A%222013-06-03T08%3A26%3A46%22%2C%22endTime%22%3A%225%3A00%20PM%22%7D%2C%22refreshConfig%22%3A%7B%22enabled%22%3Afalse%2C%22interval%22%3A60000%7D%2C%22graphSize%22%3A%7B%22width%22%3A400%2C%22height%22%3A250%7D%2C%22defaultGraphParams%22%3A%7B%22width%22%3A400%2C%22from%22%3A%22-2hours%22%2C%22until%22%3A%22now%22%2C%22height%22%3A250%7D%2C%22graphs%22%3A%5B%5B%5B%22stats_counts.statsd.packets_received%22%5D%2C%7B%22target%22%3A%5B%22stats_counts.statsd.packets_received%22%5D%7D%2C%22%2Frender%3Fwidth%3D400%26from%3D-2hours%26until%3Dnow%26height%3D250%26target%3Dstats_counts.statsd.packets_received%26_uniq%3D0.79433781532812864%26title%3Dstats_counts.statsd.packets_received%22%5D%5D%7D

c.f.

{"state": {"name": "bar", "defaultGraphParams": {"width": 400, "from": "-2hours", "until": "now", "height": 250}, "refreshConfig": {"interval": 60000, "enabled": false}, "graphs": [[["stats_counts.statsd.packets_received"], {"target": ["stats_counts.statsd.packets_received"]}, "/render?width=400&from=-2hours&until=now&height=250&target=stats_counts.statsd.packets_received&_uniq=0.21331518678925931&title=stats_counts.statsd.packets_received"]], "timeConfig": {"startDate": "2013-06-03T01:26:46", "relativeStartUnits": "hours", "endDate": "2013-06-03T01:26:46", "relativeStartQuantity": "2", "relativeUntilQuantity": "", "startTime": "9:00 AM", "endTime": "5:00 PM", "type": "relative", "relativeUntilUnits": "now"}, "graphSize": {"width": 400, "height": 250}}}

Some crappy python for demonstration at:

https://people.mozilla.org/~wdawson/dashboard_api

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment