├── .gitignore ├── CHANGES.txt ├── MANIFEST.in ├── README.markdown ├── munin ├── __init__.py ├── helpers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_test.py │ └── __init__.py ├── models.py ├── urls.py └── views.py ├── plugins └── django.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | build 3 | *~ 4 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.2.1 (2016-01-06) 2 | ================== 3 | 4 | * Django 1.9 compatibility (from @gustavi) 5 | 6 | 0.2.0 (2015-04-06) 7 | ================== 8 | 9 | * python3 compatibility (from @firm1) 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include munin/ * 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # django-munin 2 | 3 | This is a Django application to make it a bit simpler to use 4 | [Munin](http://munin-monitoring.org/) to monitor various metrics 5 | for your Django app. 6 | 7 | First, it includes a munin plugin that you can symlink into 8 | `/etc/munin/plugins/` and point at your django application and it will 9 | gather data for munin to graph. Second, it contains a couple views 10 | that return some very basic information about the state of your app: 11 | database performance, number of users, number of sessions, etc. Third, 12 | it provides a decorator to make it simple to expose your own custom 13 | metrics to Munin. 14 | 15 | ## Installing 16 | 17 | Install `django-munin` into your python path with the usual `pip install` 18 | or whatever you are doing. Then add `munin` to your `INSTALLED_APPS` and 19 | run `manage.py syncdb` (it just needs to set up one database table 20 | that it will use for performance testing). 21 | 22 | To access the included basic views, add the following pattern to your 23 | `urls.py`: 24 | 25 | ('^munin/',include('munin.urls')), 26 | 27 | The views available there are then going to be at: 28 | 29 | * `munin/db_performance/` (milliseconds to perform insert/select/delete operations) 30 | * `munin/total_users/` (total number of Users) 31 | * `munin/active_users/` (number of users logged in in the last hour) 32 | * `munin/total_sessions/` (total number of sessions) 33 | * `munin/active_sessions/` (number of sessions that are not expired) 34 | 35 | Those were the only metrics I could think of that would be potentially 36 | useful on just about any Django app and were likely to always be 37 | available. 38 | 39 | (I'm going to assume that you are already a pro at configuring 40 | Munin. If not, go get on that. Munin is very cool) 41 | 42 | Next, copy `plugins/django.py` into your `/usr/share/munin/plugins/` 43 | directory. 44 | 45 | For each metric that you want Munin to monitor, make a symlink in 46 | `/etc/munin/plugins/` to `/usr/share/munin/plugins/django.py` with an 47 | appropriate name. Eg, to monitor all five of the included ones (as 48 | root, probably): 49 | 50 | $ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_db_performance 51 | $ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_total_users 52 | $ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_active_users 53 | $ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_total_sessions 54 | $ ln -s /usr/share/munin/plugins/django.py /etc/munin/plugins/myapp_active_sessions 55 | 56 | You then need to configure each of them in 57 | `/etc/munin/plugin-conf.d/munin-node` 58 | 59 | For each, give it a stanza with `env.url` and `graph_category` set. To 60 | continue the above, you'd add something like: 61 | 62 | [myapp_db_performance] 63 | env.url http://example.com/munin/db_performance/ 64 | env.graph_category myapp 65 | 66 | [myapp_total_users] 67 | env.url http://example.com/munin/total_users/ 68 | env.graph_category myapp 69 | 70 | [myapp_active_users] 71 | env.url http://example.com/munin/active_users/ 72 | env.graph_category myapp 73 | 74 | [myapp_total_sessions] 75 | env.url http://example.com/munin/total_sessions/ 76 | env.graph_category myapp 77 | 78 | [myapp_active_sessions] 79 | env.url http://example.com/munin/active_sessions/ 80 | env.graph_category myapp 81 | 82 | If your HTTP server require Basic Authentication, you can add login and password 83 | as parameters: 84 | 85 | [myapp_active_sessions] 86 | env.url http://example.com/munin/active_sessions/ 87 | env.graph_category myapp 88 | env.login mylogin 89 | env.password mypassword 90 | 91 | Restart your Munin node, and it should start collecting and graphing 92 | that data. 93 | 94 | ## Custom munin views 95 | 96 | Those are pretty generic metrics though and the real power of this 97 | application is that you can easily expose your own custom 98 | metrics. Basically, anything that you can calculate in the context of 99 | a Django view in your application, you can easily expose to Munin. 100 | 101 | `django-munin` includes a `@muninview` decorator that lets you write a 102 | regular django view that returns a list of `(key,value)` tuples and it 103 | will expose those to that `django.py` munin plugin for easy graphing. 104 | 105 | The `@muninview` decorator takes a `config` parameter, which is just a 106 | string of munin config directives. You'll want to put stuff like 107 | `graph_title`, `graph_vlabel`, and `graph_info` there. Possibly 108 | `graph_category` too (if you include it there, remove it from the munin 109 | plugin conf stanza). The view function that it wraps then just needs 110 | to return a list of tuples. 111 | 112 | The simplest way to get a feel for how this works is to look at how 113 | the included views were written. So check out [munin/views.py](https://github.com/ccnmtl/django-munin/blob/master/munin/views.py). 114 | -------------------------------------------------------------------------------- /munin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccnmtl/django-munin/3675b26a963edbe075abc1947ce9f7befcfcb939/munin/__init__.py -------------------------------------------------------------------------------- /munin/helpers.py: -------------------------------------------------------------------------------- 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 22 | -------------------------------------------------------------------------------- /munin/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.8 on 2016-12-05 01:52 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | ] 15 | -------------------------------------------------------------------------------- /munin/migrations/0002_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.8 on 2016-12-05 01:52 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('munin', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Test', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=256)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /munin/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccnmtl/django-munin/3675b26a963edbe075abc1947ce9f7befcfcb939/munin/migrations/__init__.py -------------------------------------------------------------------------------- /munin/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class Test(models.Model): 4 | name = models.CharField(max_length=256) 5 | -------------------------------------------------------------------------------- /munin/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import re_path 2 | 3 | from munin.views import total_users, active_users, total_sessions, \ 4 | active_sessions, db_performance 5 | 6 | urlpatterns = [ 7 | re_path(r'^total_users/$', total_users), 8 | re_path(r'^active_users/$', active_users), 9 | re_path(r'^total_sessions/$', total_sessions), 10 | re_path(r'^active_sessions/$', active_sessions), 11 | re_path(r'^db_performance/$', db_performance), 12 | ] 13 | -------------------------------------------------------------------------------- /munin/views.py: -------------------------------------------------------------------------------- 1 | from .helpers import muninview 2 | from django.contrib.sessions.models import Session 3 | from datetime import datetime 4 | from datetime import timedelta 5 | import time 6 | from .models import Test 7 | from django.contrib.auth import get_user_model 8 | 9 | 10 | User = get_user_model() 11 | 12 | 13 | @muninview(config="""graph_title Total Users 14 | graph_vlabel users""") 15 | def total_users(request): 16 | return [("users",User.objects.all().count())] 17 | 18 | @muninview(config="""graph_title Active Users 19 | graph_vlabel users 20 | graph_info Number of users logged in during the last hour""") 21 | def active_users(request): 22 | hour_ago = datetime.now() - timedelta(hours=1) 23 | return [("users",User.objects.filter(last_login__gt=hour_ago).count())] 24 | 25 | @muninview(config="""graph_title Total Sessions 26 | graph_vlabel sessions""") 27 | def total_sessions(request): 28 | return [("sessions",Session.objects.all().count())] 29 | 30 | @muninview(config="""graph_title Active Sessions 31 | graph_vlabel sessions""") 32 | def active_sessions(request): 33 | return [("sessions",Session.objects.filter(expire_date__gt=datetime.now()).count())] 34 | 35 | @muninview(config="""graph_title DB performance 36 | graph_vlabel milliseconds 37 | graph_info performance of simple insert/select/delete operations""") 38 | def db_performance(request): 39 | start = time.time() 40 | t = Test.objects.create(name="inserting at %f" % start) 41 | end = time.time() 42 | insert = end - start 43 | start = time.time() 44 | t2 = Test.objects.get(id=t.id) 45 | end = time.time() 46 | select = end - start 47 | start = time.time() 48 | t2.delete() 49 | end = time.time() 50 | delete = end - start 51 | return [("insert",1000 * insert), 52 | ("select",1000 * select), 53 | ("delete",1000 * delete)] 54 | -------------------------------------------------------------------------------- /plugins/django.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | import urllib2 4 | import os 5 | import base64 6 | 7 | url = os.environ['url'] 8 | category = os.environ.get('graph_category',"") 9 | login = os.environ.get('login',"") 10 | password = os.environ.get('password',"") 11 | base64string = base64.encodestring('%s:%s' % (login, password)).replace('\n', '') 12 | 13 | if len(sys.argv) == 2: 14 | url = url + "?" + sys.argv[1] + "=1" 15 | request = urllib2.Request(url) 16 | if login != "" and password != "": 17 | request.add_header("Authorization", "Basic %s" % base64string) 18 | print urllib2.urlopen(request).read() 19 | # they can set the category in the config 20 | if category != "": 21 | print "graph_category " + category 22 | else: 23 | request = urllib2.Request(url) 24 | if login != "" and password != "": 25 | request.add_header("Authorization", "Basic %s" % base64string) 26 | data = urllib2.urlopen(request).readlines() 27 | for line in data: 28 | parts = line.split(" ") 29 | label = parts[0] 30 | value = " ".join(parts[1:]) 31 | print label + ".value " + value 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 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 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.2.2", 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 | include_package_data=True, 42 | packages=['munin'], 43 | test_suite='nose.collector', 44 | ) 45 | 46 | --------------------------------------------------------------------------------