123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- from __future__ import absolute_import, unicode_literals
-
- import inspect
- import sys
- import time
- from collections import OrderedDict
-
- from django.conf import settings
- from django.core import cache
- from django.core.cache import CacheHandler, caches as original_caches
- from django.core.cache.backends.base import BaseCache
- from django.dispatch import Signal
- from django.middleware import cache as middleware_cache
- from django.utils.translation import ugettext_lazy as _, ungettext
-
- from debug_toolbar import settings as dt_settings
- from debug_toolbar.panels import Panel
- from debug_toolbar.utils import (
- get_stack, get_template_info, render_stacktrace, tidy_stacktrace,
- )
-
- cache_called = Signal(providing_args=[
- "time_taken", "name", "return_value", "args", "kwargs", "trace"])
-
-
- def send_signal(method):
- def wrapped(self, *args, **kwargs):
- t = time.time()
- value = method(self, *args, **kwargs)
- t = time.time() - t
-
- if dt_settings.get_config()['ENABLE_STACKTRACES']:
- stacktrace = tidy_stacktrace(reversed(get_stack()))
- else:
- stacktrace = []
-
- template_info = get_template_info()
- cache_called.send(sender=self.__class__, time_taken=t,
- name=method.__name__, return_value=value,
- args=args, kwargs=kwargs, trace=stacktrace,
- template_info=template_info, backend=self.cache)
- return value
- return wrapped
-
-
- class CacheStatTracker(BaseCache):
- """A small class used to track cache calls."""
- def __init__(self, cache):
- self.cache = cache
-
- def __repr__(self):
- return str("<CacheStatTracker for %s>") % repr(self.cache)
-
- def _get_func_info(self):
- frame = sys._getframe(3)
- info = inspect.getframeinfo(frame)
- return (info[0], info[1], info[2], info[3])
-
- def __contains__(self, key):
- return self.cache.__contains__(key)
-
- def __getattr__(self, name):
- return getattr(self.cache, name)
-
- @send_signal
- def add(self, *args, **kwargs):
- return self.cache.add(*args, **kwargs)
-
- @send_signal
- def get(self, *args, **kwargs):
- return self.cache.get(*args, **kwargs)
-
- @send_signal
- def set(self, *args, **kwargs):
- return self.cache.set(*args, **kwargs)
-
- @send_signal
- def delete(self, *args, **kwargs):
- return self.cache.delete(*args, **kwargs)
-
- @send_signal
- def clear(self, *args, **kwargs):
- return self.cache.clear(*args, **kwargs)
-
- @send_signal
- def has_key(self, *args, **kwargs):
- # Ignore flake8 rules for has_key since we need to support caches
- # that may be using has_key.
- return self.cache.has_key(*args, **kwargs) # noqa
-
- @send_signal
- def incr(self, *args, **kwargs):
- return self.cache.incr(*args, **kwargs)
-
- @send_signal
- def decr(self, *args, **kwargs):
- return self.cache.decr(*args, **kwargs)
-
- @send_signal
- def get_many(self, *args, **kwargs):
- return self.cache.get_many(*args, **kwargs)
-
- @send_signal
- def set_many(self, *args, **kwargs):
- self.cache.set_many(*args, **kwargs)
-
- @send_signal
- def delete_many(self, *args, **kwargs):
- self.cache.delete_many(*args, **kwargs)
-
- @send_signal
- def incr_version(self, *args, **kwargs):
- return self.cache.incr_version(*args, **kwargs)
-
- @send_signal
- def decr_version(self, *args, **kwargs):
- return self.cache.decr_version(*args, **kwargs)
-
-
- class CacheHandlerPatch(CacheHandler):
- def __getitem__(self, alias):
- actual_cache = super(CacheHandlerPatch, self).__getitem__(alias)
- return CacheStatTracker(actual_cache)
-
-
- middleware_cache.caches = CacheHandlerPatch()
-
-
- class CachePanel(Panel):
- """
- Panel that displays the cache statistics.
- """
- template = 'debug_toolbar/panels/cache.html'
-
- def __init__(self, *args, **kwargs):
- super(CachePanel, self).__init__(*args, **kwargs)
- self.total_time = 0
- self.hits = 0
- self.misses = 0
- self.calls = []
- self.counts = OrderedDict((
- ('add', 0),
- ('get', 0),
- ('set', 0),
- ('delete', 0),
- ('clear', 0),
- ('get_many', 0),
- ('set_many', 0),
- ('delete_many', 0),
- ('has_key', 0),
- ('incr', 0),
- ('decr', 0),
- ('incr_version', 0),
- ('decr_version', 0),
- ))
- cache_called.connect(self._store_call_info)
-
- def _store_call_info(self, sender, name=None, time_taken=0,
- return_value=None, args=None, kwargs=None,
- trace=None, template_info=None, backend=None, **kw):
- if name == 'get':
- if return_value is None:
- self.misses += 1
- else:
- self.hits += 1
- elif name == 'get_many':
- for key, value in return_value.items():
- if value is None:
- self.misses += 1
- else:
- self.hits += 1
- time_taken *= 1000
-
- self.total_time += time_taken
- self.counts[name] += 1
- self.calls.append({
- 'time': time_taken,
- 'name': name,
- 'args': args,
- 'kwargs': kwargs,
- 'trace': render_stacktrace(trace),
- 'template_info': template_info,
- 'backend': backend
- })
-
- # Implement the Panel API
-
- nav_title = _("Cache")
-
- @property
- def nav_subtitle(self):
- cache_calls = len(self.calls)
- return ungettext("%(cache_calls)d call in %(time).2fms",
- "%(cache_calls)d calls in %(time).2fms",
- cache_calls) % {'cache_calls': cache_calls,
- 'time': self.total_time}
-
- @property
- def title(self):
- count = len(getattr(settings, 'CACHES', ['default']))
- return ungettext("Cache calls from %(count)d backend",
- "Cache calls from %(count)d backends",
- count) % dict(count=count)
-
- def enable_instrumentation(self):
- if isinstance(middleware_cache.caches, CacheHandlerPatch):
- cache.caches = middleware_cache.caches
- else:
- cache.caches = CacheHandlerPatch()
-
- def disable_instrumentation(self):
- cache.caches = original_caches
- # While it can be restored to the original, any views that were
- # wrapped with the cache_page decorator will continue to use a
- # monkey patched cache.
- middleware_cache.caches = original_caches
-
- def generate_stats(self, request, response):
- self.record_stats({
- 'total_calls': len(self.calls),
- 'calls': self.calls,
- 'total_time': self.total_time,
- 'hits': self.hits,
- 'misses': self.misses,
- 'counts': self.counts,
- })
-
- def generate_server_timing(self, request, response):
- stats = self.get_stats()
- value = stats.get('total_time', 0)
- title = 'Cache {} Calls'.format(stats.get('total_calls', 0))
- self.record_server_timing('total_time', title, value)
|