Skip to content

Commit f4699aa

Browse files
committed
initial version
0 parents  commit f4699aa

File tree

8 files changed

+248
-0
lines changed

8 files changed

+248
-0
lines changed

README.markdown

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
This is a Django application to make it a bit simpler to use
2+
Munin (http://munin-monitoring.org/) to monitor various metrics
3+
for your Django app.
4+
5+
First, it includes a munin plugin that you can symlink into
6+
/etc/munin/plugins/ and point at your django application and it will
7+
gather data for munin to graph. Second, it contains a couple views
8+
that return some very basic information about the state of your app:
9+
database performance, number of users, number of sessions, etc. Third,
10+
it provides a decorator to make it simple to expose your own custom
11+
metrics to Munin.
12+
13+
Install django-munin into your python path with the usual pip install
14+
or whatever you are doing. Then add 'munin' to your INSTALLED_APPS and
15+
run 'manage.py syncdb' (it just needs to set up one database table
16+
that it will use for performance testing).
17+
18+
To access the included basic views, add the following pattern to your
19+
urls.py:
20+
21+
('^munin/',include('munin.urls')),
22+
23+
The views available there are then going to be at:
24+
25+
* munin/db_performance/ (milliseconds to perform insert/select/delete operations)
26+
* munin/total_users/ (total number of Users)
27+
* munin/active_users/ (number of users logged in in the last hour)
28+
* munin/total_sessions/ (total number of sessions)
29+
* munin/active_sessions/ (number of sessions that are not expired)
30+
31+
Those were the only metrics I could think of that would be potentially
32+
useful on just about any Django app and were likely to always be
33+
available.
34+
35+
(I'm going to assume that you are already a pro at configuring
36+
Munin. If not, go get on that. Munin is very cool)
37+
38+
Next, copy scripts/django.py into your /usr/share/munin/plugins/
39+
directory.
40+
41+
For each metric that you want Munin to monitor, make a symlink in
42+
/etc/munin/plugins/ to /usr/share/munin/plugins/django.py with an
43+
appropriate name. Eg, to monitor all five of the included ones (as
44+
root, probably):
45+
46+
$ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_db_performance
47+
$ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_total_users
48+
$ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_active_users
49+
$ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_total_sessions
50+
$ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_active_sessions
51+
52+
You then need to configure each of them in
53+
/etc/munin/plugin-conf.d/munin-node
54+
55+
For each, give it a stanza with env.url and graph_category set. To
56+
continue the above, you'd add something like:
57+
58+
[myapp_db_performance]
59+
env.url http://example.com/munin/db_performance/
60+
graph_category myapp
61+
62+
[myapp_total_users]
63+
env.url http://example.com/munin/total_users/
64+
graph_category myapp
65+
66+
[myapp_active_users]
67+
env.url http://example.com/munin/active_users/
68+
graph_category myapp
69+
70+
[myapp_total_sessions]
71+
env.url http://example.com/munin/total_sessions/
72+
graph_category myapp
73+
74+
[myapp_active_sessions]
75+
env.url http://example.com/munin/active_sessions/
76+
graph_category myapp
77+
78+
Restart your Munin node, and it should start collecting and graphing
79+
that data.
80+
81+
Those are pretty generic metrics though and the real power of this
82+
application is that you can easily expose your own custom
83+
metrics. Basically, anything that you can calculate in the context of
84+
a Django view in your application, you can easily expose to Munin.
85+
86+
django-munin includes a 'muninview' decorator that lets you write a
87+
regular django view that returns a list of (key,value) tuples and it
88+
will expose those to that django.py munin plugin for easy graphing.
89+
90+
The @muninview decorator takes a config parameter, which is just a
91+
string of munin config directives. You'll want to put stuff like
92+
graph_title, graph_vlabel, and graph_info there. Possibly
93+
graph_category too (if you include it there, remove it from the munin
94+
plugin conf stanza). The view function that it wraps then just needs
95+
to return a list of tuples.
96+
97+
The simplest way to get a feel for how this works is to look at how
98+
the included views were written. So check out munin/views.py.

munin/__init__.py

Whitespace-only changes.

munin/helpers.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from django.http import HttpResponse
2+
3+
class muninview(object):
4+
""" decorator to make it simpler to write munin views """
5+
def __init__(self,config=""):
6+
self.config = config
7+
8+
def __call__(self, func):
9+
def rendered_func(request, *args, **kwargs):
10+
tuples = func(request, *args, **kwargs)
11+
if 'autoconfig' in request.GET:
12+
return HttpResponse("yes")
13+
if 'config' in request.GET:
14+
rows = ["%s.label %s" % (t[0].replace(" ","_"),t[0]) for t in tuples]
15+
return HttpResponse("\n".join([self.config] + rows))
16+
if type(tuples) == type([]):
17+
rows = ["%s %s" % (t[0].replace(" ","_"),str(t[1])) for t in tuples]
18+
return HttpResponse("\n".join(rows))
19+
else:
20+
return tuples
21+
return rendered_func

munin/models.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from django.db import models
2+
3+
class Test(models.Model):
4+
name = models.CharField(max_length=256)

munin/urls.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.conf.urls.defaults import *
2+
3+
urlpatterns = patterns('',
4+
(r'^total_users/$','munin.views.total_users'),
5+
(r'^active_users/$','munin.views.active_users'),
6+
(r'^total_sessions/$','munin.views.total_sessions'),
7+
(r'^active_sessions/$','munin.views.active_sessions'),
8+
(r'^db_performance/$','munin.views.db_performance'),
9+
)

munin/views.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from helpers import muninview
2+
from django.contrib.auth.models import User
3+
from django.contrib.sessions.models import Session
4+
from datetime import datetime
5+
from datetime import timedelta
6+
import time
7+
from models import Test
8+
9+
@muninview(config="""graph_title Total Users
10+
graph_vlabel users""")
11+
def total_users(request):
12+
return [("users",User.objects.all().count())]
13+
14+
@muninview(config="""graph_title Active Users
15+
graph_vlabel users
16+
graph_info Number of users logged in during the last hour""")
17+
def active_users(request):
18+
hour_ago = datetime.now() - timedelta(hours=1)
19+
return [("users",User.objects.filter(last_login__gt=hour_ago).count())]
20+
21+
@muninview(config="""graph_title Total Sessions
22+
graph_vlabel sessions""")
23+
def total_sessions(request):
24+
return [("sessions",Session.objects.all().count())]
25+
26+
@muninview(config="""graph_title Active Sessions
27+
graph_vlabel sessions""")
28+
def active_sessions(request):
29+
return [("sessions",Session.objects.filter(expire_date__gt=datetime.now()).count())]
30+
31+
@muninview(config="""graph_title DB performance
32+
graph_vlabel milliseconds
33+
graph_info performance of simple insert/select/delete operations""")
34+
def db_performance(request):
35+
start = time.time()
36+
t = Test.objects.create(name="inserting at %f" % start)
37+
end = time.time()
38+
insert = end - start
39+
start = time.time()
40+
t2 = Test.objects.get(id=t.id)
41+
end = time.time()
42+
select = end - start
43+
start = time.time()
44+
t2.delete()
45+
end = time.time()
46+
delete = end - start
47+
return [("insert",1000 * insert),
48+
("select",1000 * select),
49+
("delete",1000 * delete)]

plugins/django.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/python
2+
import sys
3+
import urllib2
4+
import os
5+
6+
url = os.environ['url']
7+
category = os.environ.get('graph_category',"")
8+
9+
if len(sys.argv) == 2:
10+
url = url + "?" + sys.argv[1] + "=1"
11+
print urllib2.urlopen(url).read()
12+
# they can set the category in the config
13+
if category != "":
14+
print "graph_category " + category
15+
else:
16+
data = urllib2.urlopen(url).readlines()
17+
for line in data:
18+
parts = line.split(" ")
19+
label = parts[0]
20+
value = " ".join(parts[1:])
21+
print label + ".value " + value
22+

setup.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright (c) 2011, Columbia Center For New Media Teaching And Learning (CCNMTL)
2+
# All rights reserved.
3+
#
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions are met:
6+
# * Redistributions of source code must retain the above copyright
7+
# notice, this list of conditions and the following disclaimer.
8+
# * Redistributions in binary form must reproduce the above copyright
9+
# notice, this list of conditions and the following disclaimer in the
10+
# documentation and/or other materials provided with the distribution.
11+
# * Neither the name of the CCNMTL nor the
12+
# names of its contributors may be used to endorse or promote products
13+
# derived from this software without specific prior written permission.
14+
#
15+
# THIS SOFTWARE IS PROVIDED BY CCNMTL ``AS IS'' AND ANY
16+
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
# DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
19+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
26+
from setuptools import setup, find_packages
27+
28+
setup(
29+
name="django-munin",
30+
version="0.1.1",
31+
author="Anders Pearson",
32+
author_email="anders@columbia.edu",
33+
url="http://github.com/ccnmtl/django-munin/",
34+
description="Munin adaptor for Django",
35+
long_description="Helper application for using munin to monitor your Django apps",
36+
install_requires = [],
37+
scripts = [],
38+
license = "BSD",
39+
platforms = ["any"],
40+
zip_safe=False,
41+
package_data = {'' : ['*.*']},
42+
packages=['munin'],
43+
test_suite='nose.collector',
44+
)
45+

0 commit comments

Comments
 (0)