1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
import os
import SocketServer
from datetime import datetime
import traceback
import django
from django.conf import settings
from django.db import connection
from django.db.backends import util
from django.template.loader import render_to_string
from django.utils import simplejson
from django.utils.encoding import force_unicode
from django.utils.hashcompat import sha_constructor
from debug_toolbar.panels import DebugPanel
# Figure out some paths
django_path = os.path.realpath(os.path.dirname(django.__file__))
socketserver_path = os.path.realpath(os.path.dirname(SocketServer.__file__))
# TODO:This should be set in the toolbar loader as a default and panels should
# get a copy of the toolbar object with access to its config dictionary
SQL_WARNING_THRESHOLD = getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}).get('SQL_WARNING_THRESHOLD', 500)
def tidy_stacktrace(strace):
"""
Clean up stacktrace and remove all entries that:
1. Are part of Django (except contrib apps)
2. Are part of SocketServer (used by Django's dev server)
3. Are the last entry (which is part of our stacktracing code)
"""
trace = []
for s in strace[:-1]:
s_path = os.path.realpath(s[0])
if django_path in s_path and not 'django/contrib' in s_path:
continue
if socketserver_path in s_path:
continue
trace.append((s[0], s[1], s[2], s[3]))
return trace
class DatabaseStatTracker(util.CursorDebugWrapper):
"""
Replacement for CursorDebugWrapper which stores additional information
in `connection.queries`.
"""
def execute(self, sql, params=()):
start = datetime.now()
try:
return self.cursor.execute(sql, params)
finally:
stop = datetime.now()
duration = ms_from_timedelta(stop - start)
stacktrace = tidy_stacktrace(traceback.extract_stack())
_params = ''
try:
_params = simplejson.dumps([force_unicode(x) for x in params])
except TypeError:
pass # object not JSON serializable
# We keep `sql` to maintain backwards compatibility
self.db.queries.append({
'sql': self.db.ops.last_executed_query(self.cursor, sql, params),
'duration': duration,
'raw_sql': sql,
'params': _params,
'hash': sha_constructor(settings.SECRET_KEY + sql + _params).hexdigest(),
'stacktrace': stacktrace,
'start_time': start,
'stop_time': stop,
'is_slow': (duration > SQL_WARNING_THRESHOLD)
})
util.CursorDebugWrapper = DatabaseStatTracker
class SQLDebugPanel(DebugPanel):
"""
Panel that displays information about the SQL queries run while processing
the request.
"""
name = 'SQL'
has_content = True
def __init__(self):
self._offset = len(connection.queries)
self._sql_time = 0
def nav_title(self):
return 'SQL'
def nav_subtitle(self):
self._sql_time = sum([q['duration'] for q in connection.queries[self._offset:]])
num_queries = len(connection.queries) - self._offset
return "%d %s in %.2fms" % (
num_queries,
(num_queries == 1) and 'query' or 'queries',
self._sql_time
)
def title(self):
return 'SQL Queries'
def url(self):
return ''
def content(self):
sql_queries = connection.queries[self._offset:]
width_ratio_tally = 0
for query in sql_queries:
query['sql'] = reformat_sql(query['sql'])
try:
query['width_ratio'] = (query['duration'] / self._sql_time) * 100
except ZeroDivisionError:
query['width_ratio'] = 0
query['start_offset'] = width_ratio_tally
width_ratio_tally += query['width_ratio']
context = {
'queries': sql_queries,
'sql_time': self._sql_time,
'is_mysql': settings.DATABASE_ENGINE == 'mysql',
}
return render_to_string('debug_toolbar/panels/sql.html', context)
def ms_from_timedelta(td):
"""
Given a timedelta object, returns a float representing milliseconds
"""
return (td.seconds * 1000) + (td.microseconds / 1000.0)
def reformat_sql(sql):
sql = sql.replace(',', ', ')
sql = sql.replace('SELECT ', 'SELECT\n\t')
sql = sql.replace(' FROM ', '\nFROM\n\t')
sql = sql.replace(' WHERE ', '\nWHERE\n\t')
sql = sql.replace(' INNER JOIN', '\n\tINNER JOIN')
sql = sql.replace(' LEFT OUTER JOIN' , '\n\tLEFT OUTER JOIN')
sql = sql.replace(' ORDER BY ', '\nORDER BY\n\t')
sql = sql.replace(' HAVING ', '\nHAVING\n\t')
sql = sql.replace(' GROUP BY ', '\nGROUP BY\n\t')
# Use Pygments to highlight SQL if it's available
try:
from pygments import highlight
from pygments.lexers import SqlLexer
from pygments.formatters import HtmlFormatter
sql = highlight(sql, SqlLexer(), HtmlFormatter())
except ImportError:
pass
return sql
|