Mercurial > hg > savane-forge
changeset 101:7e2908e2a8e6
Add debug footer with SQL stats
author | Sylvain Beucler <beuc@beuc.net> |
---|---|
date | Sat, 01 Aug 2009 11:00:18 +0200 |
parents | b2d75b8b5fd2 |
children | eb952fbc0d01 |
files | src/savane/middleware/__init__.py src/savane/middleware/debug.py src/settings.py |
diffstat | 3 files changed, 176 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/src/savane/middleware/debug.py @@ -0,0 +1,175 @@ +# http://www.djangosnippets.org/snippets/766/ +# http://www.djangosnippets.org/snippets/1033/ +# Author: simon (May 21, 2008) +# Author: crucialfelix (September 7, 2008) +# Copyright (C) 2009 Sylvain Beucler +# +# I hate legal-speak as much as anybody, but on a site which is geared +# toward sharing code there has to be at least a little bit of it, so +# here goes: +# +# By creating an account here you agree to three things: +# +# 1. That you will only post code which you wrote yourself and that +# you have the legal right to release under these terms. +# +# 2. That you grant any third party who sees the code you post a +# royalty-free, non-exclusive license to copy and distribute that +# code and to make and distribute derivative works based on that +# code. You may include license terms in snippets you post, if you +# wish to use a particular license (such as the BSD license or GNU +# GPL), but that license must permit royalty-free copying, +# distribution and modification of the code to which it is applied. +# +# 3. That if you post code of which you are not the author or for +# which you do not have the legal right to distribute according to +# these terms, you will indemnify and hold harmless the operators of +# this site and any third parties who are exposed to liability as a +# result of your actions. +# +# If you can't legally agree to these terms, or don't want to, you +# cannot create an account here. + +import time +from django.dispatch import dispatcher +from django.core.signals import request_started +from django.test.signals import template_rendered +from django.conf import settings +from django.db import connection +from django.utils.encoding import force_unicode +from django.utils.safestring import mark_safe + +TEMPLATE = """ +<div id="debug" style="clear:both;"> +<a href="#debugbox" + onclick="this.style.display = 'none'; + document.getElementById('debugbox').style.display = 'block'; + return false;" + style="font-size: small; color: red; text-decoration: none; display: block; margin: 12px;" +>+</a> + +<div style="display: none;clear: both; border: 1px solid red; padding: 12px; margin: 12px; overflow: scroll" id="debugbox"> + +<p>Server-time taken: {{ server_time|floatformat:"5" }} seconds</p> +<p>View: <strong>{{view}}</strong></p> +<p>Templates used:</p> +{% if templates %} +<ol> + {% for template in templates %} + <li><strong>{{ template.0 }}</strong> loaded from <samp>{{ template.1 }}</samp></li> + {% endfor %} +</ol> +{% else %} + None +{% endif %} +<p>Template path:</p> +{% if template_dirs %} + <ol> + {% for template in template_dirs %} + <li>{{ template }}</li> + {% endfor %} + </ol> +{% else %} + None +{% endif %} +<p>SQL executed:</p> +{% if sql %} +<ol> +{% for query in sql %} + <li><pre>{{ query.sql|linebreaksbr }}</pre><p>took {{ query.time|floatformat:"3" }} seconds</p></li> +{% endfor %} +</ol> +<p>Total SQL time: {{ sql_total }}</p> +{% else %} + None +{% endif %} +</div> +</div> +</body> +""" + +# Monkeypatch instrumented test renderer from django.test.utils - we could use +# django.test.utils.setup_test_environment for this but that would also set up +# e-mail interception, which we don't want +from django.test.utils import instrumented_test_render +from django.template import Template, Context +if Template.render != instrumented_test_render: + Template.original_render = Template.render + Template.render = instrumented_test_render +# MONSTER monkey-patch +old_template_init = Template.__init__ +def new_template_init(self, template_string, origin=None, name='<Unknown Template>'): + old_template_init(self, template_string, origin, name) + self.origin = origin +Template.__init__ = new_template_init + +class DebugFooter: + def process_request(self, request): + self.time_started = time.time() + self.templates_used = [] + self.contexts_used = [] + self.sql_offset_start = len(connection.queries) + template_rendered.connect(self._storeRenderedTemplates) + + def process_response(self, request, response): + # Only include debug info for text/html pages not accessed via Ajax + if 'text/html' not in response['Content-Type']: + return response + if request.is_ajax(): + return response + if not settings.DEBUG: + return response + if response.status_code != 200: + return response + + templates = [ + (t.name, t.origin and t.origin.name or 'No origin') + for t in self.templates_used + ] + + sql_queries = connection.queries[self.sql_offset_start:] + # Reformat sql queries a bit + sql_total = 0.0 + for query in sql_queries: + query['sql'] = reformat_sql(query['sql']) + sql_total += float(query['time']) + + from django.core.urlresolvers import resolve + view_func, args, kwargs = resolve(request.META['PATH_INFO']) + + if hasattr(view_func,'view_func'): + # it the view_func has a view_func then its a decorator + co = view_func.view_func.func_code + else: + co = view_func.func_code + + view = '%s.%s' % (view_func.__module__, view_func.__name__) + debug_content = Template(TEMPLATE).render(Context({ + 'server_time': time.time() - self.time_started, + 'templates': templates, + 'sql': sql_queries, + 'sql_total': sql_total, + 'template_dirs': settings.TEMPLATE_DIRS, + 'view': view + })) + + content = response.content + response.content = force_unicode(content).replace('</body>', debug_content) + + return response + + def _storeRenderedTemplates(self, **kwargs): + #signal=signal, sender=sender, template=template, context=context): + template = kwargs.get('template') + if(template): + self.templates_used.append(template) + context = kwargs.get('context') + if(context): + self.contexts_used.append(context) + +def reformat_sql(sql): + sql = sql.replace('`,`', '`, `') + sql = sql.replace('` FROM `', '` \n FROM `') + sql = sql.replace('` WHERE ', '` \n WHERE ') + sql = sql.replace(' ORDER BY ', ' \n ORDER BY ') + return sql