Refined the sql debug view at the bottom of each page. Added a column showing the WHERE clause, as that is quite helpful in working out where a given query is coming from. Added an sql_debug template variable to make it easier to switch between the sql debug view and using the django-debug-toolbar.

- Legacy-Id: 12225
This commit is contained in:
Henrik Levkowetz 2016-10-28 16:46:05 +00:00
parent c6177d4f92
commit a1934d1713
3 changed files with 87 additions and 41 deletions

View file

@ -18,6 +18,12 @@ def debug_mark_queries_from_view(request):
if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS: if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
from django.db import connection from django.db import connection
for query in connection.queries: for query in connection.queries:
query['where'] = 'V' # V is for 'view' query['loc'] = 'V' # V is for 'view'
return context_extras return context_extras
def sql_debug(request):
if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
return {'sql_debug': True }
else:
return {'sql_debug': False }

View file

@ -1,43 +1,49 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #} {# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}{% origin %} {% load origin %}{% origin %}
{% if debug %} {% if debug %}
{% load debug_filters %} {% if sql_debug %}
{% load future %} {% load debug_filters %}
{% load future %}
<div id="debug"> <div id="debug">
<hr> <hr>
<p> <p>
{{ sql_queries|length }} queries ({{ sql_queries|timesum }}s) {{ sql_queries|length }} queries ({{ sql_queries|timesum }}s)
{% if sql_queries|length != 0 %} {% if sql_queries|length != 0 %}
<a class="btn btn-default btn-xs" <a class="btn btn-default btn-xs"
onclick="$('#debug-query-table').toggleClass('hide');">Show</a> onclick="$('#debug-query-table').toggleClass('hide');">Show</a>
{% endif %} {% endif %}
</p> </p>
<table class="table table-condensed table-striped tablesorter hide" id="debug-query-table"> <table class="table table-condensed table-striped tablesorter hide" id="debug-query-table">
<thead> <thead>
<tr> <tr>
<th data-header="sequence">#</th> <th data-header="sequence">#</th>
<th data-header="query">SQL</th> <th data-header="query">SQL</th>
<th data-header="count">Count</th> <th data-header="count">Count</th>
<th data-header="where">View/ Templ.</th> <th data-header="where">WHERE</th>
<th data-header="time">Time</th> <th data-header="loc">View/ Templ.</th>
<th data-header="acc">Acc.</th> <th data-header="time">Time</th>
</tr> <th data-header="acc">Acc.</th>
</thead> </tr>
<tbody> </thead>
{% with sql_queries|annotate_sql_queries as sql_query_info %} <tbody>
{% for query in sql_query_info %} {% with sql_queries|annotate_sql_queries as sql_query_info %}
<tr> {% for query in sql_query_info %}
<td>{{ forloop.counter }}</td> <tr>
<td>{{ query.sql|expand_comma|escape }}</td> <td>{{ forloop.counter }}</td>
<td>{{ query.count }}</td> <td>{{ query.sql|expand_comma|escape }}</td>
<td>{{ query.where }}</td> <td>{{ query.count }}</td>
<td>{{ query.time }}</td> <td>{{ query.where }}</td>
<td>{{ query.time_accum }}</td> <td>{{ query.loc }}</td>
</tr> <td>{{ query.time }}</td>
{% endfor %} <td>{{ query.time_accum }}</td>
{% endwith %} </tr>
</tbody> {% endfor %}
</table> {% endwith %}
</div> </tbody>
</table>
</div>
{% else %}
<div class='text-center text-muted small'>Add 'ietf.context_processors.sql_debug' to settings.TEMPLATE_CONTECT_PROCESSORS to turn on the SQL statement table</div>
{% endif %}
{% endif %} {% endif %}

View file

@ -1,3 +1,6 @@
import sys
import sqlparse
from django import template from django import template
register = template.Library() register = template.Library()
@ -20,12 +23,43 @@ def expand_comma(value):
return value.replace(",", ", ") return value.replace(",", ", ")
def get_sql_parts(sql):
q = {}
s = sqlparse.parse(sql)[0] # assuming there's only one statement
q['where'] = None
q['from'] = None
# use sqlparse to pick out some interesting parts of the statement
state = None
for e in s:
if e.is_whitespace:
continue
if state == None:
if e.is_keyword:
key = e.normalized.lower()
state = 'value'
elif e.is_group and e[0].is_keyword:
key = e[0].normalized.lower()
val = str(e)
state = 'store'
else:
pass
elif state == 'value':
val = str(e)
state = 'store'
else:
sys.stderr.write("Unexpected sqlparse iteration state in annotate_sql_queries(): '%s'" % state )
if state == 'store':
q[key] = val
state = None
return q
@register.filter() @register.filter()
def annotate_sql_queries(queries): def annotate_sql_queries(queries):
counts = {} counts = {}
timeacc = {} timeacc = {}
for q in queries: for q in queries:
sql = q['sql'] sql = q['sql']
q.update(get_sql_parts(sql))
if not sql in counts: if not sql in counts:
counts[sql] = 0; counts[sql] = 0;
counts[sql] += 1 counts[sql] += 1
@ -33,8 +67,8 @@ def annotate_sql_queries(queries):
timeacc[sql] = 0.0; timeacc[sql] = 0.0;
timeacc[sql] += float(q['time']) timeacc[sql] += float(q['time'])
for q in queries: for q in queries:
if q.get('where', None) == None: if q.get('loc', None) == None:
q['where'] = 'T' # template q['loc'] = 'T' # template
sql = q['sql'] sql = q['sql']
q['count'] = str(counts[sql]) q['count'] = str(counts[sql])
q['time_accum'] = "%4.3f" % timeacc[sql] q['time_accum'] = "%4.3f" % timeacc[sql]