Merge lp:~lucio.torre/graphite/add-events into lp:~graphite-dev/graphite/main

Proposed by Lucio Torre
Status: Merged
Approved by: chrismd
Approved revision: 310
Merge reported by: chrismd
Merged at revision: not available
Proposed branch: lp:~lucio.torre/graphite/add-events
Merge into: lp:~graphite-dev/graphite/main
Diff against target: 931 lines (+549/-38)
17 files modified
docs/install.rst (+2/-0)
docs/who-is-using.rst (+1/-0)
requirements.txt (+1/-0)
setup.py (+4/-1)
webapp/content/js/jquery.graphite.js (+152/-27)
webapp/graphite/browser/urls.py (+1/-1)
webapp/graphite/events/models.py (+49/-0)
webapp/graphite/events/urls.py (+21/-0)
webapp/graphite/events/views.py (+77/-0)
webapp/graphite/graphlot/views.py (+4/-1)
webapp/graphite/render/functions.py (+154/-2)
webapp/graphite/settings.py (+2/-0)
webapp/graphite/templates/browserHeader.html (+1/-0)
webapp/graphite/templates/event.html (+30/-0)
webapp/graphite/templates/events.html (+42/-0)
webapp/graphite/templates/graphlot.html (+6/-5)
webapp/graphite/urls.py (+2/-1)
To merge this branch: bzr merge lp:~lucio.torre/graphite/add-events
Reviewer Review Type Date Requested Status
chrismd Approve
Sidnei da Silva (community) Approve
Review via email: mp+69142@code.launchpad.net

Description of the change

Add events to graphite.

Events are instantaneous occurrences that we want to track and correlate with our current metrics. Sample events are rollouts, reboots, errors, etc.

Events store the following information: summary, date, tags and "extra data", where you can store whatever you might need. This can all be edited from the admin: http://ubuntuone.com/p/16CJ/

You can add events from the command line using curl:
$ curl -X POST http://localhost:8000/events/ -d '{"what": "Something Interesting"}'

So its very easy to integrate with other tools.

You can see the list of events from: http://localhost:8000/events/ and get to the event details by clicking on its line. (eg, http://localhost:8000/events/4)

Then, using the graphlot ui you can overlay events into a graph by selecting tags that would filter the events or just '*' to select all.

Events get overlayed in the graph, you get a tooltip with the summary when hovering over it and when you click on it you go to the details page.

In order to make developing/testing this easier, there are also some new functions that generate data, so you can see a plot for whatever date you want without having real data.

See: http://ubuntuone.com/p/16CY/

To post a comment you must log in.
lp:~lucio.torre/graphite/add-events updated
306. By Lucio Torre

merged with trunk

307. By Lucio Torre

removed dirt.

Revision history for this message
Sidnei da Silva (sidnei) wrote :

Looks pretty ok to me. A few comments, but none of them blockers:

[1] arrays_equal has a few issues the way it's implemented, but should work given how it's used. There's a really nice array comparison implementation here: http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256BFB0077DFFD

[2] I'm worried about the use of 'autoescape off' here, since basically you could inject some javascript through post_event handler and have it rendered unsanitized into the template, but Lucio pointed out there's more templates already using 'autoescape off' in the tree. Probably deserves some checking all around. It doesn't seem like removing it from those newly introduced templates would break them though.

review: Approve
Revision history for this message
Aman Gupta (tmm1) wrote :

I really like the new test functions. Can you add some docs for http://readthedocs.org/docs/graphite/en/latest/functions.html

Also we should document the new dependency on django.tagging in requirements.txt and in the docs somewhere.

Revision history for this message
Aman Gupta (tmm1) wrote :

As discussed on IRC, it would be nice to expose these new events to the server-side graphing api as well. Probably as a new function: events(tag, tag, ...), which could query the Events model and generate a TimeSeries to graph.

Revision history for this message
Lucio Torre (lucio.torre) wrote :

updated js array comparison to include the one in the link
removed the autoescape for all event related templates
documented test functions
added django-tagging to requirements
added events(*tags) function for use with drawAsInfinite
fixed bug on events tooltips.

lp:~lucio.torre/graphite/add-events updated
308. By Lucio Torre

merged with trunk

309. By Lucio Torre

fixes, documentation and new events functions

310. By Lucio Torre

more doc

Revision history for this message
chrismd (chrismd) wrote :

Looks great, unfortunately I could not merge it into trunk because we haven't upgraded the repo to 2a format yet. The diff however applied cleanly as a patch, which I just committed to trunk.

review: Approve
Revision history for this message
DiegoV (diego-varese) wrote :

Looks like your code does not work with a MySQL backend:

https://bugs.launchpad.net/graphite/+bug/1068861

Revision history for this message
Dieter P (dieter-plaetinck) wrote :

IMHO managing annotated events has nothing do with time series data; I feel like trying to bolt that logic onto graphite is a bit awkward and requires a lot of code additions/changes precisely because this is a different problem domain.
Based on that realization I wrote a small tool for annotated event management (with similar input/output API's as graphite): http://dieter.plaetinck.be/anthracite-event-database-enrich-monitoring-dashboards-visual-numerical-analysis-events-business-impact.html

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'docs/install.rst'
--- docs/install.rst 2011-03-21 20:22:09 +0000
+++ docs/install.rst 2011-08-23 21:18:40 +0000
@@ -14,11 +14,13 @@
14* Python 2.4 or greater (2.6+ recommended)14* Python 2.4 or greater (2.6+ recommended)
15* `Pycairo <http://www.cairographics.org/pycairo/>`_15* `Pycairo <http://www.cairographics.org/pycairo/>`_
16* `Django <http://www.djangoproject.com/>`_ 1.0 or greater16* `Django <http://www.djangoproject.com/>`_ 1.0 or greater
17* `django-tagging <http://code.google.com/p/django-tagging/>`_ 0.3.1
17* A json module, if you're using Python2.6 this comes standard. With 2.4 you should18* A json module, if you're using Python2.6 this comes standard. With 2.4 you should
18 install `simplejson <http://pypi.python.org/pypi/simplejson/>`_19 install `simplejson <http://pypi.python.org/pypi/simplejson/>`_
19* A Django-supported database module (sqlite comes standard with Python 2.6)20* A Django-supported database module (sqlite comes standard with Python 2.6)
20* `Twisted <http://twistedmatrix.com/>`_ 8.0 or greater (10.0+ recommended)21* `Twisted <http://twistedmatrix.com/>`_ 8.0 or greater (10.0+ recommended)
2122
23
22Also both the Graphite webapp and Carbon require the whisper database library.24Also both the Graphite webapp and Carbon require the whisper database library.
2325
24There are also several optional dependencies, some of which are necessary for26There are also several optional dependencies, some of which are necessary for
2527
=== modified file 'docs/who-is-using.rst'
--- docs/who-is-using.rst 2011-03-28 17:30:25 +0000
+++ docs/who-is-using.rst 2011-08-23 21:18:40 +0000
@@ -8,5 +8,6 @@
8* `Etsy <http://www.etsy.com/>`_ (see http://codeascraft.etsy.com/2010/12/08/track-every-release/)8* `Etsy <http://www.etsy.com/>`_ (see http://codeascraft.etsy.com/2010/12/08/track-every-release/)
9* `Google <http://google-opensource.blogspot.com/2010/09/get-ready-to-rocksteady.html>`_ (opensource Rocksteady project)9* `Google <http://google-opensource.blogspot.com/2010/09/get-ready-to-rocksteady.html>`_ (opensource Rocksteady project)
10* `Media Temple <http://mediatemple.net/>`_10* `Media Temple <http://mediatemple.net/>`_
11* `Canonical <http://www.canonical.com>`_
1112
12And many more, I still need to collect some links...13And many more, I still need to collect some links...
1314
=== modified file 'requirements.txt'
--- requirements.txt 2011-08-04 22:49:45 +0000
+++ requirements.txt 2011-08-23 21:18:40 +0000
@@ -31,5 +31,6 @@
31python-memcached==1.4731python-memcached==1.47
32txAMQP==0.432txAMQP==0.4
33simplejson==2.1.633simplejson==2.1.6
34django-tagging==0.3.1
34http://cairographics.org/releases/py2cairo-1.8.10.tar.gz35http://cairographics.org/releases/py2cairo-1.8.10.tar.gz
35./whisper36./whisper
3637
=== modified file 'setup.py'
--- setup.py 2011-08-20 05:25:28 +0000
+++ setup.py 2011-08-23 21:18:40 +0000
@@ -51,10 +51,13 @@
51 'graphite.metrics',51 'graphite.metrics',
52 'graphite.dashboard',52 'graphite.dashboard',
53 'graphite.graphlot',53 'graphite.graphlot',
54 'graphite.events',
54 'graphite.thirdparty',55 'graphite.thirdparty',
55 'graphite.thirdparty.pytz',56 'graphite.thirdparty.pytz',
56 ],57 ],
57 package_data={'graphite' : ['templates/*', 'local_settings.py.example', 'render/graphTemplates.conf']},58 package_data={'graphite' :
59 ['templates/*', 'local_settings.py.example',
60 'render/graphTemplates.conf']},
58 scripts=glob('bin/*'),61 scripts=glob('bin/*'),
59 data_files=webapp_content.items() + storage_dirs + conf_files,62 data_files=webapp_content.items() + storage_dirs + conf_files,
60 **setup_kwargs63 **setup_kwargs
6164
=== modified file 'webapp/content/js/jquery.graphite.js'
--- webapp/content/js/jquery.graphite.js 2011-07-10 23:30:06 +0000
+++ webapp/content/js/jquery.graphite.js 2011-08-23 21:18:40 +0000
@@ -1,4 +1,38 @@
1(function( $ ) {1(function( $ ) {
2
3 function arrays_equal(array1, array2) {
4 if (array1 == null || array2 == null) {
5 return false;
6 }
7 var temp = new Array();
8 if ( (!array1[0]) || (!array2[0]) ) { // If either is not an array
9 return false;
10 }
11 if (array1.length != array2.length) {
12 return false;
13 }
14 // Put all the elements from array1 into a "tagged" array
15 for (var i=0; i<array1.length; i++) {
16 key = (typeof array1[i]) + "~" + array1[i];
17 // Use "typeof" so a number 1 isn't equal to a string "1".
18 if (temp[key]) { temp[key]++; } else { temp[key] = 1; }
19 // temp[key] = # of occurrences of the value (so an element could appear multiple times)
20 }
21 // Go through array2 - if same tag missing in "tagged" array, not equal
22 for (var i=0; i<array2.length; i++) {
23 key = (typeof array2[i]) + "~" + array2[i];
24 if (temp[key]) {
25 if (temp[key] == 0) { return false; } else { temp[key]--; }
26 // Subtract to keep track of # of appearances in array2
27 } else { // Key didn't appear in array1, arrays are not equal.
28 return false;
29 }
30 }
31 // If we get to this point, then every generated key in array1 showed up the exact same
32 // number of times in array2, so the arrays are equal.
33 return true;
34 }
35
2 $.fn.editable_in_place = function(callback) {36 $.fn.editable_in_place = function(callback) {
3 var editable = $(this);37 var editable = $(this);
4 if (editable.length > 1) {38 if (editable.length > 1) {
@@ -6,14 +40,14 @@
6 }40 }
741
8 var editing = false;42 var editing = false;
9 43
10 editable.bind('click', function () {44 editable.bind('click', function () {
11 var $element = this;45 var $element = this;
1246
13 if (editing == true) return;47 if (editing == true) return;
1448
15 editing = true;49 editing = true;
16 50
17 var $edit = $('<input type="text" class="edit_in_place" value="' + editable.text() + '"/>');51 var $edit = $('<input type="text" class="edit_in_place" value="' + editable.text() + '"/>');
1852
19 $edit.css({'height' : editable.height(), 'width' : editable.width()});53 $edit.css({'height' : editable.height(), 'width' : editable.width()});
@@ -57,9 +91,10 @@
57 var latestPosition = null;91 var latestPosition = null;
58 var autocompleteoptions = {92 var autocompleteoptions = {
59 minChars: 0,93 minChars: 0,
60 selectFirst: false,94 selectFirst: false
61 };95 };
62 96 var markings = [];
97
63 var parse_incoming = function(incoming_data) {98 var parse_incoming = function(incoming_data) {
64 var result = [];99 var result = [];
65 var start = incoming_data.start;100 var start = incoming_data.start;
@@ -77,7 +112,7 @@
77 };112 };
78 };113 };
79114
80 115
81 var render = function () {116 var render = function () {
82 var lines = []117 var lines = []
83 for (i in graph_lines) {118 for (i in graph_lines) {
@@ -94,20 +129,20 @@
94129
95 $.extend(xaxismode, xaxisranges);130 $.extend(xaxismode, xaxisranges);
96 $.extend(yaxismode, yaxisranges);131 $.extend(yaxismode, yaxisranges);
97 132
98 plot = $.plot($("#graph"),133 plot = $.plot($("#graph"),
99 lines,134 lines,
100 {135 {
101 xaxis: xaxismode,136 xaxis: xaxismode,
102 yaxis: yaxismode,137 yaxis: yaxismode,
103 grid: { hoverable: true, },138 grid: { hoverable: true, markings: markings },
104 selection: { mode: "xy" },139 selection: { mode: "xy" },
105 legend: { show: true, container: graph.find("#legend") },140 legend: { show: true, container: graph.find("#legend") },
106 crosshair: { mode: "x" },141 crosshair: { mode: "x" },
107 }142 }
108 );143 );
109144
110 145
111 for (i in lines) {146 for (i in lines) {
112 lines[i] = $.extend({}, lines[i]);147 lines[i] = $.extend({}, lines[i]);
113 lines[i].label = null;148 lines[i].label = null;
@@ -174,7 +209,7 @@
174 if (!updateLegendTimeout)209 if (!updateLegendTimeout)
175 updateLegendTimeout = setTimeout(updateLegend, 50);210 updateLegendTimeout = setTimeout(updateLegend, 50);
176 });211 });
177 212
178 function showTooltip(x, y, contents) {213 function showTooltip(x, y, contents) {
179 $('<div id="tooltip">' + contents + '</div>').css( {214 $('<div id="tooltip">' + contents + '</div>').css( {
180 position: 'absolute',215 position: 'absolute',
@@ -191,7 +226,7 @@
191 var previousPoint = null;226 var previousPoint = null;
192 $("#graph").bind("plothover", function (event, pos, item) {227 $("#graph").bind("plothover", function (event, pos, item) {
193 if (item) {228 if (item) {
194 if (previousPoint != item.datapoint) {229 if ( !arrays_equal(previousPoint, item.datapoint)) {
195 previousPoint = item.datapoint;230 previousPoint = item.datapoint;
196231
197 $("#tooltip").remove();232 $("#tooltip").remove();
@@ -201,13 +236,38 @@
201 showTooltip(item.pageX, item.pageY,236 showTooltip(item.pageX, item.pageY,
202 item.series.label + " = " + y);237 item.series.label + " = " + y);
203 }238 }
204 }239 } else {
205 else {240 calc_distance = function(mark, event) {
206 $("#tooltip").remove();241 mark_where = plot.pointOffset({ x: mark.xaxis.from, y: 0});
207 previousPoint = null;242 d = plot.offset().left + mark_where.left - event.pageX - plot.getPlotOffset().left;
243 return d*d;
244 }
245 distance = undefined;
246 winner = null;
247 for (marki in markings) {
248 mark = markings[marki];
249 dist = calc_distance(mark, pos);
250 if (distance == undefined || distance > dist) {
251 distance = dist;
252 winner = mark;
253 }
254 }
255 if (distance < 20) {
256 if (!arrays_equal(previousPoint,[winner])) {
257 previousPoint = [winner]
258 $("#tooltip").remove();
259 showTooltip(pos.pageX-20, pos.pageY-20, winner.text);
260 }
261 } else {
262 if (previousPoint != null) {
263 previousPoint = null;
264 $("#tooltip").remove();
265 }
266
267 }
208 }268 }
209 });269 });
210 270
211 $("#overview").bind("plotselected", function (event, ranges) {271 $("#overview").bind("plotselected", function (event, ranges) {
212 xaxisranges = { min: ranges.xaxis.from, max: ranges.xaxis.to };272 xaxisranges = { min: ranges.xaxis.from, max: ranges.xaxis.to };
213 yaxisranges = { min: ranges.yaxis.from, max: ranges.yaxis.to };273 yaxisranges = { min: ranges.yaxis.from, max: ranges.yaxis.to };
@@ -231,6 +291,7 @@
231 var metric = $(this);291 var metric = $(this);
232 update_metric_row(metric);292 update_metric_row(metric);
233 });293 });
294 get_events(graph.find("#eventdesc"))
234 render();295 render();
235 }296 }
236297
@@ -245,9 +306,14 @@
245 url = url + '&target=' + series;306 url = url + '&target=' + series;
246 }307 }
247 }308 }
309 events = graph.find("#eventdesc").val();
310 if (events != "") {
311 url = url + "&events=" + events;
312 }
313
248 return url;314 return url;
249 }315 }
250 316
251 var build_when = function () {317 var build_when = function () {
252 var when = '';318 var when = '';
253 var from = graph.find("#from").text();319 var from = graph.find("#from").text();
@@ -265,6 +331,15 @@
265 return 'rawdata?'+when+'&target='+series;331 return 'rawdata?'+when+'&target='+series;
266 }332 }
267333
334 var build_url_events = function (tags) {
335 when = build_when()
336 if (tags == "*") {
337 return '/events/get_data?'+when
338 } else {
339 return '/events/get_data?'+when+'&tags='+tags;
340 }
341 }
342
268 var update_metric_row = function(metric_row) {343 var update_metric_row = function(metric_row) {
269 var metric = $(metric_row);344 var metric = $(metric_row);
270 var metric_name = metric.find(".metricname").text();345 var metric_name = metric.find(".metricname").text();
@@ -290,9 +365,45 @@
290 }365 }
291 });366 });
292367
293 368
294 }369 }
295 370
371 var get_events = function(events_text) {
372 if (events_text.val() == "") {
373 events_text.removeClass("ajaxworking");
374 events_text.removeClass("ajaxerror");
375 markings = [];
376 render();
377 } else {
378 events_text.addClass("ajaxworking");
379 $.ajax({
380 url: build_url_events(events_text.val()),
381 success: function(req_data) {
382 events_text.removeClass("ajaxerror");
383 events_text.removeClass("ajaxworking");
384 markings = [];
385 for (i in req_data) {
386 row = req_data[i];
387 markings.push({
388 color: '#000',
389 lineWidth: 1,
390 xaxis: { from: row.when*1000, to: row.when*1000 },
391 text:'<a href="/events/'+row.id+'/">'+row.what+'<a>'
392 });
393 }
394 render();
395 },
396 error: function(req, status, err) {
397 events_text.removeClass("ajaxworking");
398 events_text.addClass("ajaxerror");
399 render();
400 }
401 });
402 }
403
404 }
405
406
296 // configure the date boxes407 // configure the date boxes
297 graph.find('#from').editable_in_place(408 graph.find('#from').editable_in_place(
298 function(editable, value) {409 function(editable, value) {
@@ -300,7 +411,7 @@
300 recalculate_all();411 recalculate_all();
301 }412 }
302 );413 );
303 414
304415
305 graph.find('#until').editable_in_place(416 graph.find('#until').editable_in_place(
306 function(editable, value) {417 function(editable, value) {
@@ -309,7 +420,7 @@
309 }420 }
310 );421 );
311422
312 graph.find('#update').bind('click', 423 graph.find('#update').bind('click',
313 function() {424 function() {
314 recalculate_all();425 recalculate_all();
315 }426 }
@@ -318,11 +429,11 @@
318 graph.find('#clearzoom').bind('click',429 graph.find('#clearzoom').bind('click',
319 clear_zoom430 clear_zoom
320 );431 );
321 432
322 // configure metricrows433 // configure metricrows
323 var setup_row = function (metric) {434 var setup_row = function (metric) {
324 var metric_name = metric.find('.metricname').text();435 var metric_name = metric.find('.metricname').text();
325 436
326 metric.find('.metricname').editable_in_place(437 metric.find('.metricname').editable_in_place(
327 function(editable, value) {438 function(editable, value) {
328 delete graph_lines[$(editable).text()];439 delete graph_lines[$(editable).text()];
@@ -335,7 +446,7 @@
335 metric.remove();446 metric.remove();
336 render();447 render();
337 });448 });
338 449
339 metric.find('.yaxis').bind('click', function() {450 metric.find('.yaxis').bind('click', function() {
340 if ($(this).text() == "one") {451 if ($(this).text() == "one") {
341 $(this).text("two");452 $(this).text("two");
@@ -346,7 +457,7 @@
346 render();457 render();
347 });458 });
348 }459 }
349 460
350 graph.find('.metricrow').each(function() {461 graph.find('.metricrow').each(function() {
351 setup_row($(this));462 setup_row($(this));
352 });463 });
@@ -375,9 +486,23 @@
375 });486 });
376 });487 });
377488
489 // configure new metric input
490 graph.find('#eventdesc').each(function () {
491 var edit = $(this);
492 edit.keydown(function(e) {
493 if(e.which===13) { // on enter
494 // add row
495 edit.blur();
496 get_events(edit);
497 }
498 });
499 });
500
501
378 // get data502 // get data
379 recalculate_all();503 recalculate_all();
380 });504 });
381 };505 };
382 506
383})( jQuery );507})( jQuery );
508
384509
=== modified file 'webapp/graphite/browser/urls.py'
--- webapp/graphite/browser/urls.py 2009-10-19 06:20:01 +0000
+++ webapp/graphite/browser/urls.py 2011-08-23 21:18:40 +0000
@@ -19,5 +19,5 @@
19 ('^search/?$', 'search'),19 ('^search/?$', 'search'),
20 ('^mygraph/?$', 'myGraphLookup'),20 ('^mygraph/?$', 'myGraphLookup'),
21 ('^usergraph/?$', 'userGraphLookup'),21 ('^usergraph/?$', 'userGraphLookup'),
22 ('', 'browser'),22 ('^$', 'browser'),
23)23)
2424
=== added directory 'webapp/graphite/events'
=== added file 'webapp/graphite/events/__init__.py'
=== added file 'webapp/graphite/events/models.py'
--- webapp/graphite/events/models.py 1970-01-01 00:00:00 +0000
+++ webapp/graphite/events/models.py 2011-08-23 21:18:40 +0000
@@ -0,0 +1,49 @@
1import time
2
3from django.db import models
4from django.contrib import admin
5
6import tagging.fields
7
8
9class Event(models.Model):
10 class Admin: pass
11
12 when = models.DateTimeField()
13 what = models.CharField(max_length=255)
14 data = models.TextField(blank=True)
15 tags = tagging.fields.TagField(default="")
16
17 def get_tags(self):
18 return Tag.objects.get_for_object(self)
19
20 def __str__(self):
21 return "%s: %s" % (self.when, self.what)
22
23 @staticmethod
24 def find_events(time_from=None, time_until=None, tags=None):
25 query = Event.objects.all()
26
27 if time_from is not None:
28 query = query.filter(when__gte=time_from)
29
30 if time_until is not None:
31 query = query.filter(when__lte=time_until)
32
33 if tags is not None:
34 for tag in tags:
35 query = query.filter(tags__iregex=r'\b%s\b' % tag)
36
37 result = list(query.order_by("when"))
38 return result
39
40 def as_dict(self):
41 return dict(
42 when=self.when,
43 what=self.what,
44 data=self.data,
45 tags=self.tags,
46 id=self.id,
47 )
48
49admin.site.register(Event)
0\ No newline at end of file50\ No newline at end of file
151
=== added file 'webapp/graphite/events/urls.py'
--- webapp/graphite/events/urls.py 1970-01-01 00:00:00 +0000
+++ webapp/graphite/events/urls.py 2011-08-23 21:18:40 +0000
@@ -0,0 +1,21 @@
1"""Copyright 2008 Orbitz WorldWide
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License."""
14
15from django.conf.urls.defaults import *
16
17urlpatterns = patterns('graphite.events.views',
18 ('^get_data?$', 'get_data'),
19 (r'(?P<event_id>\d+)/$', 'detail'),
20 ('^$', 'view_events'),
21)
022
=== added file 'webapp/graphite/events/views.py'
--- webapp/graphite/events/views.py 1970-01-01 00:00:00 +0000
+++ webapp/graphite/events/views.py 2011-08-23 21:18:40 +0000
@@ -0,0 +1,77 @@
1import datetime
2import time
3
4import simplejson
5
6from django.http import HttpResponse
7from django.shortcuts import render_to_response, get_object_or_404
8
9from graphite.events import models
10from graphite.render.attime import parseATTime
11
12
13def to_timestamp(dt):
14 return time.mktime(dt.timetuple())
15
16
17class EventEncoder(simplejson.JSONEncoder):
18 def default(self, obj):
19 if isinstance(obj, datetime.datetime):
20 return to_timestamp(obj)
21 return simplejson.JSONEncoder.default(self, obj)
22
23
24def view_events(request):
25 if request.method == "GET":
26 context = dict(events=fetch(request))
27 return render_to_response("events.html", context)
28 else:
29 return post_event(request)
30
31def detail(request, event_id):
32 e = get_object_or_404(models.Event, pk=event_id)
33 context = dict(event=e)
34 return render_to_response("event.html", context)
35
36
37def post_event(request):
38 if request.method == 'POST':
39 event = simplejson.loads(request.raw_post_data)
40 assert isinstance(event, dict)
41
42 values = {}
43 values["what"] = event["what"]
44 values["tags"] = event.get("tags", None)
45 values["when"] = datetime.datetime.fromtimestamp(
46 event.get("when", time.time()))
47 if "data" in event:
48 values["data"] = event["data"]
49
50 e = models.Event(**values)
51 e.save()
52
53 return HttpResponse(status=200)
54 else:
55 return HttpResponse(status=405)
56
57def get_data(request):
58 return HttpResponse(simplejson.dumps(fetch(request), cls=EventEncoder),
59 mimetype="application/json")
60
61def fetch(request):
62 if request.GET.get("from", None) is not None:
63 time_from = parseATTime(request.GET["from"])
64 else:
65 time_from = datetime.datetime.fromtimestamp(0)
66
67 if request.GET.get("until", None) is not None:
68 time_until = parseATTime(request.GET["until"])
69 else:
70 time_until = datetime.datetime.now()
71
72 tags = request.GET.get("tags", None)
73 if tags is not None:
74 tags = request.GET.get("tags").split(" ")
75
76 return [x.as_dict() for x in
77 models.Event.find_events(time_from, time_until, tags=tags)]
078
=== modified file 'webapp/graphite/graphlot/views.py'
--- webapp/graphite/graphlot/views.py 2011-07-12 07:37:01 +0000
+++ webapp/graphite/graphlot/views.py 2011-08-23 21:18:40 +0000
@@ -20,7 +20,9 @@
2020
21 untiltime = request.GET.get('until', "-0hour")21 untiltime = request.GET.get('until', "-0hour")
22 fromtime = request.GET.get('from', "-24hour")22 fromtime = request.GET.get('from', "-24hour")
23 context = dict(metric_list=metrics, fromtime=fromtime, untiltime=untiltime)23 events = request.GET.get('events', "")
24 context = dict(metric_list=metrics, fromtime=fromtime, untiltime=untiltime,
25 events=events)
24 return render_to_response("graphlot.html", context)26 return render_to_response("graphlot.html", context)
2527
26def get_data(request):28def get_data(request):
@@ -45,6 +47,7 @@
45 raise Http40447 raise Http404
46 return HttpResponse(simplejson.dumps(result), mimetype="application/json")48 return HttpResponse(simplejson.dumps(result), mimetype="application/json")
4749
50
48def find_metric(request):51def find_metric(request):
49 """Autocomplete helper on metric names."""52 """Autocomplete helper on metric names."""
50 try:53 try:
5154
=== modified file 'webapp/graphite/render/functions.py'
--- webapp/graphite/render/functions.py 2011-08-10 18:25:01 +0000
+++ webapp/graphite/render/functions.py 2011-08-23 21:18:40 +0000
@@ -17,11 +17,17 @@
17URL parameters to change the data being graphed in some way.17URL parameters to change the data being graphed in some way.
18"""18"""
1919
20from graphite.render.datalib import TimeSeries, timestamp20import datetime
21from graphite.render.attime import parseTimeOffset
22from itertools import izip21from itertools import izip
23import math22import math
24import re23import re
24import random
25import time
26
27from graphite.render.datalib import fetchData, TimeSeries, timestamp
28from graphite.render.attime import parseTimeOffset
29
30from graphite.events import models
2531
26#Utility functions32#Utility functions
27def safeSum(values):33def safeSum(values):
@@ -1360,6 +1366,141 @@
1360 return results1366 return results
13611367
13621368
1369def timeFunction(requestContext, name):
1370 """
1371 Short Alias: time()
1372
1373 Just returns the timestamp for each X value. T
1374
1375 Example:
1376
1377 .. code-block:: none
1378
1379 &target=time("The.time.series")
1380
1381 This would create a series named "The.time.series" that contains in Y the same
1382 value (in seconds) as X.
1383
1384 """
1385
1386 step = 60
1387 delta = datetime.timedelta(seconds=step)
1388 when = requestContext["startTime"]
1389 values = []
1390
1391 while when < requestContext["endTime"]:
1392 values.append(time.mktime(when.timetuple()))
1393 when += delta
1394
1395 return [TimeSeries(name,
1396 time.mktime(requestContext["startTime"].timetuple()),
1397 time.mktime(requestContext["endTime"].timetuple()),
1398 step, values)]
1399
1400
1401def sinFunction(requestContext, name, amplitude=1):
1402 """
1403 Short Alias: sin()
1404
1405 Just returns the sine of the current time. he optional amplitude parameter
1406 changes the amplitude of the wave.
1407
1408 Example:
1409
1410 .. code-block:: none
1411
1412 &target=sin("The.time.series", 2)
1413
1414 This would create a series named "The.time.series" that contains sin(x)*2.
1415 """
1416 step = 60
1417 delta = datetime.timedelta(seconds=step)
1418 when = requestContext["startTime"]
1419 values = []
1420
1421 while when < requestContext["endTime"]:
1422 values.append(math.sin(time.mktime(when.timetuple()))*amplitude)
1423 when += delta
1424
1425 return [TimeSeries(name,
1426 time.mktime(requestContext["startTime"].timetuple()),
1427 time.mktime(requestContext["endTime"].timetuple()),
1428 step, values)]
1429
1430def randomWalkFunction(requestContext, name):
1431 """
1432 Short Alias: randomWalk()
1433
1434 Returns a random walk starting at 0. This is great for testing when there is
1435 no real data in whisper.
1436
1437 Example:
1438
1439 .. code-block:: none
1440
1441 &target=randomWalk("The.time.series")
1442
1443 This would create a series named "The.time.series" that contains points where
1444 x(t) == x(t-1)+random()-0.5, and x(0) == 0.
1445 """
1446 step = 60
1447 delta = datetime.timedelta(seconds=step)
1448 when = requestContext["startTime"]
1449 values = []
1450 current = 0
1451 while when < requestContext["endTime"]:
1452 values.append(current)
1453 current += random.random() - 0.5
1454 when += delta
1455
1456 return [TimeSeries(name,
1457 time.mktime(requestContext["startTime"].timetuple()),
1458 time.mktime(requestContext["endTime"].timetuple()),
1459 step, values)]
1460
1461def events(requestContext, *tags):
1462 """
1463 Returns the number of events at this point in time. Usable with
1464 drawAsInfinite.
1465
1466 Example:
1467
1468 .. code-block:: none
1469
1470 &target=events("tag-one", "tag-two")
1471 &target=events("*")
1472
1473 Returns all events tagged as "tag-one" and "tag-two" and the second one
1474 returns all events.
1475 """
1476 step = 60
1477 name = "events(" + ", ".join(tags) + ")"
1478 delta = datetime.timedelta(seconds=step)
1479 when = requestContext["startTime"]
1480 values = []
1481 current = 0
1482 if tags == ("*",):
1483 tags = None
1484 events = models.Event.find_events(requestContext["startTime"],
1485 requestContext["endTime"], tags=tags)
1486 eventsp = 0
1487
1488 while when < requestContext["endTime"]:
1489 count = 0
1490 if events:
1491 while eventsp < len(events) and events[eventsp].when >= when \
1492 and events[eventsp].when < (when + delta):
1493 count += 1
1494 eventsp += 1
1495
1496 values.append(count)
1497 when += delta
1498
1499 return [TimeSeries(name,
1500 time.mktime(requestContext["startTime"].timetuple()),
1501 time.mktime(requestContext["endTime"].timetuple()),
1502 step, values)]
1503
1363def pieAverage(requestContext, series):1504def pieAverage(requestContext, series):
1364 return safeDiv(safeSum(series),safeLen(series))1505 return safeDiv(safeSum(series),safeLen(series))
13651506
@@ -1437,6 +1578,17 @@
1437 'exclude' : exclude,1578 'exclude' : exclude,
1438 'constantLine' : constantLine,1579 'constantLine' : constantLine,
1439 'threshold' : threshold,1580 'threshold' : threshold,
1581
1582 # test functions
1583 'time': timeFunction,
1584 "sin": sinFunction,
1585 "randomWalk": randomWalkFunction,
1586 'timeFunction': timeFunction,
1587 "sinFunction": sinFunction,
1588 "randomWalkFunction": randomWalkFunction,
1589
1590 #events
1591 'events': events,
1440}1592}
14411593
14421594
14431595
=== modified file 'webapp/graphite/settings.py'
--- webapp/graphite/settings.py 2011-07-26 05:16:59 +0000
+++ webapp/graphite/settings.py 2011-08-23 21:18:40 +0000
@@ -170,10 +170,12 @@
170 'graphite.account',170 'graphite.account',
171 'graphite.dashboard',171 'graphite.dashboard',
172 'graphite.whitelist',172 'graphite.whitelist',
173 'graphite.events',
173 'django.contrib.auth',174 'django.contrib.auth',
174 'django.contrib.sessions',175 'django.contrib.sessions',
175 'django.contrib.admin',176 'django.contrib.admin',
176 'django.contrib.contenttypes',177 'django.contrib.contenttypes',
178 'tagging',
177)179)
178180
179AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']181AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']
180182
=== modified file 'webapp/graphite/templates/browserHeader.html'
--- webapp/graphite/templates/browserHeader.html 2011-07-10 23:30:06 +0000
+++ webapp/graphite/templates/browserHeader.html 2011-08-23 21:18:40 +0000
@@ -97,6 +97,7 @@
97 <tr><td style='font-size: smaller;'>97 <tr><td style='font-size: smaller;'>
98 User Interface: <a href="/dashboard/" target="_top">Dashboard</a> |98 User Interface: <a href="/dashboard/" target="_top">Dashboard</a> |
99 <a href="/graphlot/" target="_top">flot (experimental)</a> |99 <a href="/graphlot/" target="_top">flot (experimental)</a> |
100 <a href="/events/" target="_top">events (experimental)</a> |
100 <a href="/cli/" target="_top">Command-Line (deprecated)</a>101 <a href="/cli/" target="_top">Command-Line (deprecated)</a>
101 </td></tr>102 </td></tr>
102103
103104
=== added file 'webapp/graphite/templates/event.html'
--- webapp/graphite/templates/event.html 1970-01-01 00:00:00 +0000
+++ webapp/graphite/templates/event.html 2011-08-23 21:18:40 +0000
@@ -0,0 +1,30 @@
1<html>
2 <head>
3 <title>{{event.what}}</title>
4 <link rel="stylesheet" type="text/css" href="/content/css/table.css" />
5 <style type="text/css">
6 body {
7 font-family: sans-serif;
8 font-size: 16px;
9 margin: 50px;
10 max-width: 1200px;
11 }
12 </style>
13
14
15 </head>
16 <body>
17 <div id="title" style="text-align:center">
18 <h1>{{event.what}}</h1>
19 </div>
20 <div class="graphite">
21 <div id="main" >
22 <table class="styledtable" width=100%>
23 <tr><td>when</td><td>{{event.when|date:"H:m:s D d M Y" }}</td></tr>
24 <tr><td>tags</td><td>{{event.tags}}</td></tr>
25 <tr><td>data</td><td>{{event.data}}</td></tr>
26 </table>
27 </div>
28 </div>
29 </body>
30</html>
0\ No newline at end of file31\ No newline at end of file
132
=== added file 'webapp/graphite/templates/events.html'
--- webapp/graphite/templates/events.html 1970-01-01 00:00:00 +0000
+++ webapp/graphite/templates/events.html 2011-08-23 21:18:40 +0000
@@ -0,0 +1,42 @@
1<html>
2 <head>
3 <title>Events</title>
4 <link rel="stylesheet" type="text/css" href="../content/css/table.css" />
5 <style type="text/css">
6 body {
7 font-family: sans-serif;
8 font-size: 16px;
9 margin: 50px;
10 max-width: 1200px;
11 }
12 </style>
13
14
15 </head>
16 <body>
17 <div id="title" style="text-align:center">
18 <h1>graphite events</h1>
19 </div>
20 <div class="graphite">
21 <div id="main" >
22 {% if events %}
23 <table class="styledtable" width=100%>
24 <tr><th>when</th><th>what</th><th>tags</th></tr>
25 {% for event in events %}
26 <tr>
27 <td>{{event.when|date:"H:m:s D d M Y" }}</td>
28 <td><a href="/events/{{event.id}}/">{{event.what}}</a></td>
29 <td>{{event.tags}}</td>
30 </tr>
31 {% endfor %}
32 {% else %}
33 <br/>No events. Add events using
34 <a href="/admin/events/event/">the admin interface</a> or by posting
35 (eg, curl -X POST http://localhost:8000/events/ -d
36 '{"what": "Something Interesting"}')
37 {% endif %}
38 </table>
39 </div>
40 </div>
41 </body>
42</html>
043
=== modified file 'webapp/graphite/templates/graphlot.html'
--- webapp/graphite/templates/graphlot.html 2010-10-30 21:58:12 +0000
+++ webapp/graphite/templates/graphlot.html 2011-08-23 21:18:40 +0000
@@ -1,5 +1,3 @@
1{% autoescape off %}
2
3<html>1<html>
4 <head>2 <head>
5 <title>Graphlot</title>3 <title>Graphlot</title>
@@ -59,6 +57,11 @@
59 <br/>57 <br/>
6058
61 <table id="rowlist" class="styledtable" style="float:left">59 <table id="rowlist" class="styledtable" style="float:left">
60 <tr><th style="width:750px">events</th></tr>
61 <tr id="eventsrow"><td><input class="event_tags" style="width:600px" type="text" id="eventdesc" value="{{events}}"/></td></tr>
62 </table>
63
64 <table id="rowlist" class="styledtable" style="float:left">
62 <tr><th style="width:750px">metric</th><th>y-axis</th></tr>65 <tr><th style="width:750px">metric</th><th>y-axis</th></tr>
63 {% for metric in metric_list %}66 {% for metric in metric_list %}
64 <tr class="metricrow">67 <tr class="metricrow">
@@ -94,6 +97,4 @@
9497
95 </div>98 </div>
96 </body>99 </body>
97</html>
98
99{% endautoescape %}
100\ No newline at end of file100\ No newline at end of file
101</html>
101\ No newline at end of file102\ No newline at end of file
102103
=== modified file 'webapp/graphite/urls.py'
--- webapp/graphite/urls.py 2011-07-12 07:37:01 +0000
+++ webapp/graphite/urls.py 2011-08-23 21:18:40 +0000
@@ -35,7 +35,8 @@
35 ('^dashboard/?', include('graphite.dashboard.urls')),35 ('^dashboard/?', include('graphite.dashboard.urls')),
36 ('^whitelist/?', include('graphite.whitelist.urls')),36 ('^whitelist/?', include('graphite.whitelist.urls')),
37 ('^content/(?P<path>.*)$', 'django.views.static.serve', {'document_root' : settings.CONTENT_DIR}),37 ('^content/(?P<path>.*)$', 'django.views.static.serve', {'document_root' : settings.CONTENT_DIR}),
38 ('^graphlot/?', include('graphite.graphlot.urls')),38 ('graphlot/', include('graphite.graphlot.urls')),
39 ('^events/', include('graphite.events.urls')),
39 ('', 'graphite.browser.views.browser'),40 ('', 'graphite.browser.views.browser'),
40)41)
4142

Subscribers

People subscribed via source and target branches