Skip to content

Commit 12fdf7e

Browse files
committed
Updated cache panel to handle multiple backends and look more like the SQL panel. This is based mostly on the awesome work by @diox done in 9aa062b. Closes #134.
1 parent cf2fcb5 commit 12fdf7e

File tree

9 files changed

+216
-130
lines changed

9 files changed

+216
-130
lines changed

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ The debug toolbar has two settings that can be set in `settings.py`:
124124
* `TAG`: If set, this will be the tag to which debug_toolbar will attach the
125125
debug toolbar. Defaults to 'body'.
126126

127-
* `ENABLE_STACKTRACES`: If set, this will show stacktraces for SQL queries.
128-
Enabling stacktraces can increase the CPU time used when executing
129-
queries. Defaults to True.
127+
* `ENABLE_STACKTRACES`: If set, this will show stacktraces for SQL queries
128+
and cache calls. Enabling stacktraces can increase the CPU time used when
129+
executing queries. Defaults to True.
130130

131131
Example configuration::
132132

debug_toolbar/media/debug_toolbar/js/toolbar.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ window.djdt = (function(window, document, jQuery) {
6464
if (id === '' || !id) {
6565
return;
6666
}
67-
68-
btn.parents('.djDebugPanelContent').find('#sqlMain_' + id).find('.djDebugCollapsed').toggle(open_me);
69-
btn.parents('.djDebugPanelContent').find('#sqlMain_' + id).find('.djDebugUncollapsed').toggle(!open_me);
67+
var name = btn.attr('data-toggle-name');
68+
btn.parents('.djDebugPanelContent').find('#' + name + '_' + id).find('.djDebugCollapsed').toggle(open_me);
69+
btn.parents('.djDebugPanelContent').find('#' + name + '_' + id).find('.djDebugUncollapsed').toggle(!open_me);
7070
$(this).parents('.djDebugPanelContent').find('.djToggleDetails_' + id).each(function(){
7171
var $this = $(this);
7272
if (open_me) {

debug_toolbar/media/debug_toolbar/js/toolbar.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

debug_toolbar/panels/cache.py

Lines changed: 141 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,125 @@
1-
import time
21
import inspect
32
import sys
3+
import time
44

5+
from django.conf import settings
56
from django.core import cache
7+
from django.core.cache import get_cache as base_get_cache
68
from django.core.cache.backends.base import BaseCache
9+
from django.dispatch import Signal
10+
from django.template import Node
11+
from django.utils.datastructures import SortedDict
712
from django.utils.translation import ugettext_lazy as _, ungettext
13+
814
from debug_toolbar.panels import DebugPanel
15+
from debug_toolbar.utils import (tidy_stacktrace, render_stacktrace,
16+
get_template_info, get_stack)
17+
18+
19+
cache_called = Signal(providing_args=["time_taken", "name", "return_value", "args", "kwargs", "trace"])
20+
21+
22+
def send_signal(method):
23+
def wrapped(self, *args, **kwargs):
24+
t = time.time()
25+
value = method(self, *args, **kwargs)
26+
t = time.time() - t
27+
28+
enable_stacktraces = getattr(settings,
29+
'DEBUG_TOOLBAR_CONFIG', {}).get('ENABLE_STACKTRACES', True)
30+
if enable_stacktraces:
31+
stacktrace = tidy_stacktrace(reversed(get_stack()))
32+
else:
33+
stacktrace = []
34+
35+
template_info = None
36+
cur_frame = sys._getframe().f_back
37+
try:
38+
while cur_frame is not None:
39+
if cur_frame.f_code.co_name == 'render':
40+
node = cur_frame.f_locals['self']
41+
if isinstance(node, Node):
42+
template_info = get_template_info(node.source)
43+
break
44+
cur_frame = cur_frame.f_back
45+
except:
46+
pass
47+
del cur_frame
48+
cache_called.send(sender=self.__class__, time_taken=t,
49+
name=method.__name__, return_value=value,
50+
args=args, kwargs=kwargs, trace=stacktrace,
51+
template_info=template_info, backend=self.cache)
52+
return value
53+
return wrapped
954

1055

1156
class CacheStatTracker(BaseCache):
1257
"""A small class used to track cache calls."""
1358
def __init__(self, cache):
1459
self.cache = cache
15-
self.reset()
16-
17-
def reset(self):
18-
self.calls = []
19-
self.hits = 0
20-
self.misses = 0
21-
self.sets = 0
22-
self.gets = 0
23-
self.get_manys = 0
24-
self.deletes = 0
25-
self.total_time = 0
2660

2761
def _get_func_info(self):
2862
frame = sys._getframe(3)
2963
info = inspect.getframeinfo(frame)
3064
return (info[0], info[1], info[2], info[3])
3165

32-
def get(self, *args, **kwargs):
33-
t = time.time()
34-
value = self.cache.get(*args, **kwargs)
35-
this_time = time.time() - t
36-
self.total_time += this_time * 1000
37-
if value is None:
38-
self.misses += 1
39-
else:
40-
self.hits += 1
41-
self.gets += 1
42-
self.calls.append((this_time, 'get', args, kwargs, self._get_func_info()))
43-
return value
44-
45-
def set(self, *args, **kwargs):
46-
t = time.time()
47-
self.cache.set(*args, **kwargs)
48-
this_time = time.time() - t
49-
self.total_time += this_time * 1000
50-
self.sets += 1
51-
self.calls.append((this_time, 'set', args, kwargs, self._get_func_info()))
52-
53-
def delete(self, *args, **kwargs):
54-
t = time.time()
55-
self.cache.delete(*args, **kwargs)
56-
this_time = time.time() - t
57-
self.total_time += this_time * 1000
58-
self.deletes += 1
59-
self.calls.append((this_time, 'delete', args, kwargs, self._get_func_info()))
60-
61-
def get_many(self, *args, **kwargs):
62-
t = time.time()
63-
results = self.cache.get_many(*args, **kwargs)
64-
this_time = time.time() - t
65-
self.total_time += this_time * 1000
66-
self.get_manys += 1
67-
for key, value in results.iteritems():
68-
if value is None:
69-
self.misses += 1
70-
else:
71-
self.hits += 1
72-
self.calls.append((this_time, 'get_many', args, kwargs, self._get_func_info()))
73-
return results
66+
def __contains__(self, key):
67+
return self.cache.__contains__(key)
7468

7569
def make_key(self, *args, **kwargs):
7670
return self.cache.make_key(*args, **kwargs)
7771

72+
def validate_key(self, *args, **kwargs):
73+
self.cache.validate_key(*args, **kwargs)
74+
75+
def clear(self):
76+
return self.cache.clear()
77+
78+
@send_signal
7879
def add(self, *args, **kwargs):
7980
return self.cache.add(*args, **kwargs)
8081

82+
@send_signal
83+
def get(self, *args, **kwargs):
84+
return self.cache.get(*args, **kwargs)
85+
86+
@send_signal
87+
def set(self, *args, **kwargs):
88+
return self.cache.set(*args, **kwargs)
89+
90+
@send_signal
91+
def delete(self, *args, **kwargs):
92+
return self.cache.delete(*args, **kwargs)
93+
94+
@send_signal
8195
def has_key(self, *args, **kwargs):
8296
return self.cache.has_key(*args, **kwargs)
8397

98+
@send_signal
8499
def incr(self, *args, **kwargs):
85100
return self.cache.incr(*args, **kwargs)
86101

102+
@send_signal
87103
def decr(self, *args, **kwargs):
88104
return self.cache.decr(*args, **kwargs)
89105

90-
def __contains__(self, key):
91-
return self.cache.__contains__(key)
106+
@send_signal
107+
def get_many(self, *args, **kwargs):
108+
return self.cache.get_many(*args, **kwargs)
92109

110+
@send_signal
93111
def set_many(self, *args, **kwargs):
94112
self.cache.set_many(*args, **kwargs)
95113

114+
@send_signal
96115
def delete_many(self, *args, **kwargs):
97116
self.cache.delete_many(*args, **kwargs)
98117

99-
def clear(self):
100-
self.cache.clear()
101-
102-
def validate_key(self, *args, **kwargs):
103-
self.cache.validate_key(*args, **kwargs)
104-
118+
@send_signal
105119
def incr_version(self, *args, **kwargs):
106120
return self.cache.incr_version(*args, **kwargs)
107121

122+
@send_signal
108123
def decr_version(self, *args, **kwargs):
109124
return self.cache.decr_version(*args, **kwargs)
110125

@@ -119,29 +134,86 @@ class CacheDebugPanel(DebugPanel):
119134

120135
def __init__(self, *args, **kwargs):
121136
super(CacheDebugPanel, self).__init__(*args, **kwargs)
122-
cache.cache.reset()
137+
self.total_time = 0
138+
self.hits = 0
139+
self.misses = 0
140+
self.calls = []
141+
self.counts = SortedDict((
142+
('add', 0),
143+
('get', 0),
144+
('set', 0),
145+
('delete', 0),
146+
('get_many', 0),
147+
('set_many', 0),
148+
('delete_many', 0),
149+
('has_key', 0),
150+
('incr', 0),
151+
('decr', 0),
152+
('incr_version', 0),
153+
('decr_version', 0),
154+
))
155+
cache_called.connect(self._store_call_info)
156+
157+
def _store_call_info(self, sender, name=None, time_taken=0,
158+
return_value=None, args=None, kwargs=None, trace=None,
159+
template_info=None, backend=None, **kw):
160+
if name == 'get':
161+
if return_value is None:
162+
self.misses += 1
163+
else:
164+
self.hits += 1
165+
elif name == 'get_many':
166+
for key, value in return_value.iteritems():
167+
if value is None:
168+
self.misses += 1
169+
else:
170+
self.hits += 1
171+
self.total_time += time_taken * 1000
172+
self.counts[name] += 1
173+
self.calls.append({
174+
'time': time_taken,
175+
'name': name,
176+
'args': args,
177+
'kwargs': kwargs,
178+
'trace': render_stacktrace(trace),
179+
'template_info': template_info,
180+
'backend': backend
181+
})
123182

124183
def nav_title(self):
125184
return _('Cache')
126185

127186
def nav_subtitle(self):
128-
cache_calls = len(cache.cache.calls)
187+
cache_calls = len(self.calls)
129188
return ungettext('%(cache_calls)d call in %(time).2fms',
130189
'%(cache_calls)d calls in %(time).2fms',
131190
cache_calls) % {'cache_calls': cache_calls,
132-
'time': cache.cache.total_time}
191+
'time': self.total_time}
133192

134193
def title(self):
135-
return _('Cache Usage')
194+
count = len(getattr(settings, 'CACHES', ['default']))
195+
return ungettext('Cache calls from %(count)d backend',
196+
'Cache calls from %(count)d backends',
197+
count) % dict(count=count)
136198

137199
def url(self):
138200
return ''
139201

140202
def process_response(self, request, response):
141203
self.record_stats({
142-
'cache_calls': len(cache.cache.calls),
143-
'cache_time': cache.cache.total_time,
144-
'cache': cache.cache,
204+
'total_calls': len(self.calls),
205+
'calls': self.calls,
206+
'total_time': self.total_time,
207+
'hits': self.hits,
208+
'misses': self.misses,
209+
'counts': self.counts,
145210
})
146211

212+
213+
def get_cache_debug(*args, **kwargs):
214+
base_cache = base_get_cache(*args, **kwargs)
215+
return CacheStatTracker(base_cache)
216+
217+
147218
cache.cache = CacheStatTracker(cache.cache)
219+
cache.get_cache = get_cache_debug

debug_toolbar/panels/sql.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import os
21
import re
32
import uuid
43

54
from django.db.backends import BaseDatabaseWrapper
65
from django.utils.html import escape
7-
from django.utils.safestring import mark_safe
86
from django.utils.translation import ugettext_lazy as _, ungettext_lazy as __
97

108
from debug_toolbar.utils.compat.db import connections
119
from debug_toolbar.middleware import DebugToolbarMiddleware
1210
from debug_toolbar.panels import DebugPanel
13-
from debug_toolbar.utils import sqlparse
11+
from debug_toolbar.utils import sqlparse, render_stacktrace
1412
from debug_toolbar.utils.tracking.db import CursorWrapper
1513
from debug_toolbar.utils.tracking import replace_call
1614

@@ -183,16 +181,7 @@ def process_response(self, request, response):
183181
query['start_offset'] = width_ratio_tally
184182
query['end_offset'] = query['width_ratio'] + query['start_offset']
185183
width_ratio_tally += query['width_ratio']
186-
187-
stacktrace = []
188-
for frame in query['stacktrace']:
189-
params = map(escape, frame[0].rsplit(os.path.sep, 1) + list(frame[1:]))
190-
try:
191-
stacktrace.append(u'<span class="path">{0}/</span><span class="file">{1}</span> in <span class="func">{3}</span>(<span class="lineno">{2}</span>)\n <span class="code">{4}</span>'.format(*params))
192-
except IndexError:
193-
# This frame doesn't have the expected format, so skip it and move on to the next one
194-
continue
195-
query['stacktrace'] = mark_safe('\n'.join(stacktrace))
184+
query['stacktrace'] = render_stacktrace(query['stacktrace'])
196185
i += 1
197186

198187
if trans_id:

0 commit comments

Comments
 (0)