├── django_redis_monitor
├── __init__.py
├── mysql_backend
│ ├── __init__.py
│ └── base.py
├── sqlite3_backend
│ ├── __init__.py
│ └── base.py
├── postgresql_psycopg2_backend
│ ├── __init__.py
│ └── base.py
├── templates
│ └── django_redis_monitor
│ │ ├── nagios.xml
│ │ ├── monitor_totals_only.html
│ │ └── monitor.html
├── middleware.py
├── views.py
├── cursor_wrapper.py
└── redis_monitor.py
├── redis_monitor_demo
├── __init__.py
├── .gitignore
├── urls.py
├── manage.py
└── settings.py
├── LICENSE.txt
└── README.txt
/django_redis_monitor/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/redis_monitor_demo/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_redis_monitor/mysql_backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_redis_monitor/sqlite3_backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/redis_monitor_demo/.gitignore:
--------------------------------------------------------------------------------
1 | data.db
2 |
3 |
--------------------------------------------------------------------------------
/django_redis_monitor/postgresql_psycopg2_backend/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/django_redis_monitor/templates/django_redis_monitor/nagios.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ db_count }}
5 | {{ db_total_ms }}
6 |
7 |
8 | {{ request_count }}
9 | {{ request_total_ms }}
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/redis_monitor_demo/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 | from django.http import HttpResponse
3 | import time
4 | from random import random
5 |
6 | from django.contrib import admin
7 | admin.autodiscover()
8 |
9 | urlpatterns = patterns('',
10 | (r'^$', lambda request: time.sleep(random()) or HttpResponse('Hello!')),
11 | (r'^admin/(.*)', admin.site.root),
12 | ('^redis-monitor/$', 'django_redis_monitor.views.monitor'),
13 | )
14 |
--------------------------------------------------------------------------------
/redis_monitor_demo/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import sys
3 | sys.path.append('../')
4 |
5 | from django.core.management import execute_manager
6 | try:
7 | import settings # Assumed to be in the same directory.
8 | except ImportError:
9 | import sys
10 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
11 | sys.exit(1)
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/django_redis_monitor/mysql_backend/base.py:
--------------------------------------------------------------------------------
1 | from django.db.backends import *
2 | from django.db.backends.mysql.base import DatabaseClient
3 | from django.db.backends.mysql.base import DatabaseCreation
4 | from django.db.backends.mysql.base import DatabaseIntrospection
5 | from django.db.backends.mysql.base import DatabaseFeatures
6 | from django.db.backends.mysql.base import DatabaseOperations
7 | from django.db.backends.mysql.base import DatabaseWrapper \
8 | as OriginalDatabaseWrapper
9 |
10 | from django_redis_monitor.cursor_wrapper import MonitoredCursorWrapper
11 |
12 | class DatabaseWrapper(OriginalDatabaseWrapper):
13 |
14 | def _cursor(self):
15 | cursor = super(DatabaseWrapper, self)._cursor()
16 | return MonitoredCursorWrapper(cursor, self)
17 |
--------------------------------------------------------------------------------
/django_redis_monitor/sqlite3_backend/base.py:
--------------------------------------------------------------------------------
1 | from django.db.backends import *
2 | from django.db.backends.sqlite3.base import DatabaseClient
3 | from django.db.backends.sqlite3.base import DatabaseCreation
4 | from django.db.backends.sqlite3.base import DatabaseIntrospection
5 | from django.db.backends.sqlite3.base import DatabaseFeatures
6 | from django.db.backends.sqlite3.base import DatabaseOperations
7 | from django.db.backends.sqlite3.base import DatabaseWrapper \
8 | as OriginalDatabaseWrapper
9 |
10 | from django_redis_monitor.cursor_wrapper import MonitoredCursorWrapper
11 |
12 | class DatabaseWrapper(OriginalDatabaseWrapper):
13 |
14 | def _cursor(self):
15 | cursor = super(DatabaseWrapper, self)._cursor()
16 | return MonitoredCursorWrapper(cursor, self)
17 |
--------------------------------------------------------------------------------
/django_redis_monitor/postgresql_psycopg2_backend/base.py:
--------------------------------------------------------------------------------
1 | from django.db.backends import *
2 | from django.db.backends.postgresql_psycopg2.base import DatabaseClient
3 | from django.db.backends.postgresql_psycopg2.base import DatabaseCreation
4 | from django.db.backends.postgresql_psycopg2.base import DatabaseIntrospection
5 | from django.db.backends.postgresql_psycopg2.base import DatabaseFeatures
6 | from django.db.backends.postgresql_psycopg2.base import DatabaseOperations
7 | from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper \
8 | as OriginalDatabaseWrapper
9 | from django.db.backends.postgresql_psycopg2.base import get_version
10 |
11 | from django_redis_monitor.cursor_wrapper import MonitoredCursorWrapper
12 |
13 | class DatabaseWrapper(OriginalDatabaseWrapper):
14 |
15 | def _cursor(self):
16 | cursor = super(DatabaseWrapper, self)._cursor()
17 | return MonitoredCursorWrapper(cursor, self)
18 |
--------------------------------------------------------------------------------
/django_redis_monitor/templates/django_redis_monitor/monitor_totals_only.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | Monitoring
6 |
23 |
24 |
25 | Monitoring
26 |
27 | Requests
28 |
29 | Total requests since counting started: {{ requests.hits }}
30 | Sum of request duration since counting started: {{ requests.weight }} microseconds (millionths of a second)
31 |
32 | SQL queries
33 |
34 | Total queries since counting started: {{ sqlops.hits }}
35 | Sum of queries duration since counting started: {{ sqlops.weight }} microseconds (millionths of a second)
36 |
37 |
38 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010, Simon Willison
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 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/django_redis_monitor/middleware.py:
--------------------------------------------------------------------------------
1 | from django.db.backends import BaseDatabaseWrapper
2 | from django.conf import settings
3 | from redis_monitor import get_instance
4 | import time, logging
5 |
6 | class RedisMonitorMiddleware(object):
7 | def process_request(self, request):
8 | if self.should_track_request(request):
9 | self.tracking = True
10 | self.start_time = time.time()
11 | self.rm = get_instance('requests')
12 | else:
13 | self.tracking = False
14 |
15 | def process_response(self, request, response):
16 | if getattr(self, 'tracking', False):
17 | duration = time.time() - self.start_time
18 | duration_in_microseconds = int(1000000 * duration)
19 | try:
20 | self.rm.record_hit_with_weight(duration_in_microseconds)
21 | except Exception, e:
22 | logging.warn('RedisMonitor error: %s' % str(e))
23 | return response
24 |
25 | def should_track_request(self, request):
26 | blacklist = getattr(settings, 'REDIS_MONITOR_REQUEST_BLACKLIST', [])
27 | for item in blacklist:
28 | if isinstance(item, basestring) and request.path == item:
29 | return False
30 | elif hasattr(item, 'match') and item.match(request.path):
31 | return False
32 | return True
33 |
--------------------------------------------------------------------------------
/django_redis_monitor/templates/django_redis_monitor/monitor.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | Monitoring
6 |
23 |
24 |
25 | Monitoring
26 |
27 |
28 |
Requests per second (for 10 second buckets):
29 |
30 |
31 | | Time | Reqs in 10 secs | Reqs/second |
32 | Total req duration | Avg duration per req |
33 |
34 | {% for obj in requests %}
35 |
36 | | {{ obj.0|date:"H:i:s" }} |
37 | TODO |
38 | {{ obj.1|floatformat:"3" }} |
39 | TODO | TODO |
40 |
41 | {% endfor %}
42 |
43 |
44 |
45 |
46 |
SQLops per second (for 10 second buckets):
47 |
48 |
49 | | Time | SQLops/second |
50 |
51 | {% for obj in sqlops %}
52 |
53 | | {{ obj.0|date:"H:i:s" }} |
54 | {{ obj.1|floatformat:"3" }} |
55 |
56 | {% endfor %}
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/django_redis_monitor/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render_to_response as render
2 | from django.http import HttpResponse
3 | from django.conf import settings
4 | from redis_monitor import get_instance
5 |
6 | def monitor(request):
7 | requests = get_instance('requests')
8 | sqlops = get_instance('sqlops')
9 | if getattr(settings, 'REDIS_MONITOR_ONLY_TRACK_TOTALS', False):
10 | return render('django_redis_monitor/monitor_totals_only.html', {
11 | 'requests': requests.get_totals(),
12 | 'sqlops': sqlops.get_totals(),
13 | })
14 | else:
15 | return render('django_redis_monitor/monitor.html', {
16 | 'requests': reversed(
17 | list(requests.get_recent_hits_per_second(minutes = 10))
18 | ),
19 | 'sqlops': reversed(
20 | list(sqlops.get_recent_hits_per_second(minutes = 10))
21 | ),
22 | })
23 |
24 | def nagios(request):
25 | if not getattr(settings, 'REDIS_MONITOR_ONLY_TRACK_TOTALS', False):
26 | return HttpResponse(
27 | 'nagios only available in REDIS_MONITOR_ONLY_TRACK_TOTALS mode'
28 | )
29 | requests = get_instance('requests').get_totals()
30 | sqlops = get_instance('sqlops').get_totals()
31 | return render('django_redis_monitor/nagios.xml', {
32 | 'db_count': sqlops.get('hits', 0),
33 | 'db_total_ms': int(int(sqlops.get('weight', 0)) / 1000.0),
34 | 'request_count': requests.get('hits', 0),
35 | 'request_total_ms': int(int(requests.get('weight', 0)) / 1000.0),
36 | })
37 |
--------------------------------------------------------------------------------
/django_redis_monitor/cursor_wrapper.py:
--------------------------------------------------------------------------------
1 | from redis_monitor import get_instance
2 | import time
3 |
4 | class MonitoredCursorWrapper(object):
5 | def __init__(self, cursor, db):
6 | self.cursor = cursor
7 | self.db = db
8 | self.rm = get_instance('sqlops')
9 |
10 | def execute(self, sql, params=()):
11 | start = time.time()
12 | try:
13 | return self.cursor.execute(sql, params)
14 | finally:
15 | stop = time.time()
16 | duration_in_microseconds = int(1000000 * (stop - start))
17 | try:
18 | self.rm.record_hit_with_weight(duration_in_microseconds)
19 | except Exception, e:
20 | pass #logging.warn('RedisMonitor error: %s' % str(e))
21 |
22 | def executemany(self, sql, param_list):
23 | start = time.time()
24 | try:
25 | return self.cursor.executemany(sql, param_list)
26 | finally:
27 | stop = time.time()
28 | duration_in_microseconds = int(1000000 * (stop - start))
29 | try:
30 | self.rm.record_hits_with_total_weight(
31 | len(param_list), duration_in_microseconds
32 | )
33 | except Exception, e:
34 | pass #logging.warn('RedisMonitor error: %s' % str(e))
35 |
36 | def __getattr__(self, attr):
37 | if attr in self.__dict__:
38 | return self.__dict__[attr]
39 | else:
40 | return getattr(self.cursor, attr)
41 |
42 | def __iter__(self):
43 | return iter(self.cursor)
44 |
--------------------------------------------------------------------------------
/redis_monitor_demo/settings.py:
--------------------------------------------------------------------------------
1 | DEBUG = True
2 | TEMPLATE_DEBUG = DEBUG
3 |
4 | ADMINS = ()
5 | MANAGERS = ADMINS
6 |
7 | DATABASE_ENGINE = 'django_redis_monitor.sqlite3_backend'
8 | DATABASE_NAME = 'data.db'
9 | DATABASE_USER = ''
10 | DATABASE_PASSWORD = ''
11 | DATABASE_HOST = ''
12 | DATABASE_PORT = ''
13 |
14 | TIME_ZONE = 'UTC'
15 |
16 | LANGUAGE_CODE = 'en-us'
17 | SITE_ID = 1
18 |
19 | USE_I18N = True
20 |
21 | MEDIA_ROOT = ''
22 | MEDIA_URL = ''
23 |
24 | ADMIN_MEDIA_PREFIX = '/media/'
25 |
26 | SECRET_KEY = 'u3o25&^sgu79))-09v!ekid%1cbsa^h%75o3p%u_voh3&93vl1'
27 |
28 | TEMPLATE_LOADERS = (
29 | 'django.template.loaders.filesystem.load_template_source',
30 | 'django.template.loaders.app_directories.load_template_source',
31 | )
32 |
33 | MIDDLEWARE_CLASSES = (
34 | 'django_redis_monitor.middleware.RedisMonitorMiddleware',
35 | 'django.middleware.common.CommonMiddleware',
36 | 'django.contrib.sessions.middleware.SessionMiddleware',
37 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
38 | )
39 |
40 | ROOT_URLCONF = 'redis_monitor_demo.urls'
41 |
42 | TEMPLATE_DIRS = (
43 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
44 | # Always use forward slashes, even on Windows.
45 | # Don't forget to use absolute paths, not relative paths.
46 | )
47 |
48 | INSTALLED_APPS = (
49 | 'django.contrib.auth',
50 | 'django.contrib.contenttypes',
51 | 'django.contrib.sessions',
52 | 'django.contrib.sites',
53 | 'django.contrib.flatpages',
54 | 'django.contrib.admin',
55 | 'django_redis_monitor', # For its templates directory
56 | )
57 |
58 | import re
59 | REDIS_MONITOR_REQUEST_BLACKLIST = (
60 | '/favicon.ico',
61 | '/redis-monitor/',
62 | re.compile('^/static/'),
63 | )
64 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | django-redis-monitor
2 | ====================
3 |
4 | Redis-backed performance monitoring for Django. Can keep track of Django
5 | requests per second, Django SQL operations per second and the average
6 | duration per request or per SQL operation over time.
7 |
8 | Numbers of hits and overall weights are stored in 10 second buckets, to save
9 | on space. Only results from previous complete buckets are returned, so it can
10 | take up to 10 seconds for a hit to be registered in the hits-per-second
11 | metrics.
12 |
13 | Installation:
14 | -------------
15 |
16 | You will need to install and run a Redis server using a recent version of
17 | Redis (one that supports the HINCRBY and HGETALL commands). Redis 2.0.0 or
18 | higher should be fine.
19 |
20 | You will also need redis-py from http://github.com/andymccurdy/redis-py/ -
21 | you can pip install the latest version like so:
22 |
23 | pip install http://github.com/andymccurdy/redis-py/tarball/master
24 |
25 | Usage:
26 | ------
27 |
28 | 1. Ensure django_redis_monitor is on your Python path.
29 |
30 | 2. Add RedisMonitorMiddleware to your middleware, at the top of the stack
31 | so that the performance overhead added by other middlewares is included
32 | in the request duration calculation:
33 |
34 | MIDDLEWARE_CLASSES = (
35 | 'django_redis_monitor.middleware.RedisMonitorMiddleware',
36 | ) + MIDDLEWARE_CLASSES
37 |
38 | 3. Set your DATABASE_ENGINE setting to 'django_redis_monitor.sqlite3_backend'
39 | or 'django_redis_monitor.postgresql_psycopg2_backend' or
40 | 'django_redis_monitor.mysql_backend' so your SQL queries can be
41 | intercepted and counted.
42 |
43 | If you are using South for migrations, you will also need to add a
44 | corresponding SOUTH_DATABASE_ADAPTERS setting:
45 |
46 | SOUTH_DATABASE_ADAPTERS = {
47 | 'default': 'south.db.postgresql_psycopg2',
48 | # Or 'south.db.sqlite3' or 'south.db.mysql'
49 | }
50 |
51 | 4. Optional step: Add redis settings to your settings.py file (otherwise
52 | the following defaults will be used):
53 |
54 | REDIS_MONITOR_HOST = 'localhost'
55 | REDIS_MONITOR_PORT = 6379
56 | REDIS_MONITOR_DB = 0
57 |
58 | 5. Add 'django_redis_monitor' to your INSTALLED_APPS setting so Django can
59 | find the correct template for the monitor view. Alternatively, copy the
60 | monitor.html template in to a django_redis_monitor directory in your
61 | existing templates/ directory.
62 |
63 | 7. By default, django_redis_monitor records statistics over time in to
64 | separate buckets. If you are using an external monitoring tool such as
65 | Nagios you may only need to report the total number or duration of requests
66 | since counting began. If so, you can add the following setting:
67 |
68 | REDIS_MONITOR_ONLY_TRACK_TOTALS = True
69 |
70 | 8. Hook up the monitoring view function in your urls.py:
71 |
72 | urlpatterns = patterns('',
73 | # ...
74 | (r'^redis-monitor/$', 'django_redis_monitor.views.monitor'),
75 | )
76 |
77 | If you plan to use the nagios reporting hook, add this as well:
78 |
79 | (r'^redis-monitor/nagios\.xml$', 'django_redis_monitor.views.nagios'),
80 |
81 | If you want the monitoring view to only be visible to super users, do this:
82 |
83 | from django_redis_monitor.views import monitor
84 | from django.contrib.auth.decorators import user_passes_test
85 |
86 | def requires_superuser(view_fn):
87 | decorator = user_passes_test(lambda u: u.is_superuser)
88 | return decorator(view_fn)
89 |
90 | urlpatterns = patterns('',
91 | # ...
92 | ('^redis-monitor/$', requires_superuser(monitor)),
93 | )
94 |
95 | 9. Hit your application with a bunch of requests.
96 |
97 | 10. Go to http://localhost:8000/redis-monitor/ to see the results.
98 |
99 | 11. If you want to ignore requests to certain URLs (the redis-monitor view
100 | for example) you can add an optional REDIS_MONITOR_REQUEST_BLACKLIST
101 | setting. This can contain either strings or regular expressions:
102 |
103 | import re
104 | REDIS_MONITOR_REQUEST_BLACKLIST = (
105 | '/favicon.ico',
106 | '/redis-monitor/nagios.xml',
107 | re.compile('^/static/'),
108 | )
109 |
--------------------------------------------------------------------------------
/django_redis_monitor/redis_monitor.py:
--------------------------------------------------------------------------------
1 | import datetime # we use utcnow to insulate against daylight savings errors
2 | import redis
3 |
4 | class RedisMonitor(object):
5 | def __init__(self, prefix='', redis_obj=None, redis_host='localhost',
6 | redis_port=6379, redis_db=0
7 | ):
8 | assert prefix and ' ' not in prefix, \
9 | 'prefix (e.g. "rps") is required and must not contain spaces'
10 | self.prefix = prefix
11 | if redis_obj is None:
12 | redis_obj = redis.Redis(
13 | host=redis_host, port=redis_port, db=redis_db
14 | )
15 | self.r = redis_obj
16 |
17 | def _hash_and_slot(self, dt = None):
18 | dt = dt or datetime.datetime.utcnow()
19 | hash = dt.strftime('%Y%m%d:%H') # 20100709:12 = 12th hour of that day
20 | slot = '%02d:%d' % ( # 24:3 for seconds 30-39 in minute 24
21 | dt.minute, dt.second / 10
22 | )
23 | return ('%s:%s' % (self.prefix, hash), slot)
24 |
25 | def _calculate_start(self, hours, minutes, seconds, now = None):
26 | now = now or datetime.datetime.utcnow()
27 | delta = (60 * 60 * hours) + (60 * minutes) + seconds
28 | return now - datetime.timedelta(seconds = delta)
29 |
30 | def record_hit(self):
31 | self.record_hits(1)
32 |
33 | def record_hits(self, num_hits):
34 | hash, slot = self._hash_and_slot()
35 | self.r.hincrby(hash, slot, num_hits)
36 |
37 | def record_hit_with_weight(self, weight):
38 | self.record_hits_with_total_weight(1, weight)
39 |
40 | def record_hits_with_total_weight(self, num_hits, total_weight):
41 | hash, slot = self._hash_and_slot()
42 | self.r.hincrby(hash, slot, num_hits)
43 | self.r.hincrby(hash, slot + 'w', total_weight)
44 |
45 | def get_recent_hits(self, hours = 0, minutes = 0, seconds = 0):
46 | gathered = self.get_recent_hits_and_weights(hours, minutes, seconds)
47 | for date, hits, weight in gathered:
48 | yield date, hits
49 |
50 | def get_recent_hits_and_weights(
51 | self, hours = 0, minutes = 0, seconds = 0
52 | ):
53 | start = self._calculate_start(hours, minutes, seconds)
54 | start = start.replace(
55 | second = (start.second / 10) * 10, microsecond = 0
56 | )
57 | preloaded_hashes = {}
58 | gathered = []
59 | current = start
60 | now = datetime.datetime.utcnow().replace(
61 | second = (start.second / 10) * 10, microsecond = 0
62 | )
63 | while current < now:
64 | hash, slot = self._hash_and_slot(current)
65 | if hash not in preloaded_hashes:
66 | preloaded_hashes[hash] = self.r.hgetall(hash)
67 | hits = int(preloaded_hashes[hash].get(slot, 0))
68 | weight = int(preloaded_hashes[hash].get(slot + 'w', 0))
69 | gathered.append((current, hits, weight))
70 | current += datetime.timedelta(seconds = 10)
71 | return gathered
72 |
73 | def get_recent_hits_per_second(self, hours = 0, minutes = 0, seconds = 0):
74 | gathered = self.get_recent_hits(hours, minutes, seconds)
75 | for date, hits in gathered:
76 | yield date, hits / 10.0
77 |
78 | def get_recent_avg_weights(self, hours = 0, minutes = 0, seconds = 0):
79 | gathered = self.get_recent_hits_and_weights(hours, minutes, seconds)
80 | for date, hits, weight in gathered:
81 | if weight == 0 or hits == 0:
82 | yield date, 0
83 | else:
84 | yield date, float(weight) / hits
85 |
86 | class RedisMonitorTotalsOnly(RedisMonitor):
87 |
88 | def record_hits_with_total_weight(self, num_hits, total_weight):
89 | hash = '%s:totals' % self.prefix
90 | self.r.hincrby(hash, 'hits', num_hits)
91 | self.r.hincrby(hash, 'weight', total_weight)
92 |
93 | def get_recent_hits_and_weights(self, *args, **kwargs):
94 | raise NotImplemented, 'REDIS_MONITOR_ONLY_TRACK_TOTALS mode'
95 |
96 | def get_totals(self):
97 | hash = '%s:totals' % self.prefix
98 | return self.r.hgetall(hash) or {}
99 |
100 | def get_instance(prefix):
101 | from django.conf import settings
102 | from django.core import signals
103 | host = getattr(settings, 'REDIS_MONITOR_HOST', 'localhost')
104 | port = getattr(settings, 'REDIS_MONITOR_PORT', 6379)
105 | db = getattr(settings, 'REDIS_MONITOR_DB', 0)
106 | only_track_totals = getattr(
107 | settings, 'REDIS_MONITOR_ONLY_TRACK_TOTALS', False
108 | )
109 | if only_track_totals:
110 | klass = RedisMonitorTotalsOnly
111 | else:
112 | klass = RedisMonitor
113 | obj = klass(prefix, redis_host=host, redis_port=port, redis_db=db)
114 | # Ensure we disconnect at the end of the request cycle
115 | signals.request_finished.connect(
116 | lambda **kwargs: obj.r.connection.disconnect()
117 | )
118 | return obj
119 |
120 |
--------------------------------------------------------------------------------