Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cache.py 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. from __future__ import absolute_import, unicode_literals
  2. import inspect
  3. import sys
  4. import time
  5. from collections import OrderedDict
  6. from django.conf import settings
  7. from django.core import cache
  8. from django.core.cache import CacheHandler, caches as original_caches
  9. from django.core.cache.backends.base import BaseCache
  10. from django.dispatch import Signal
  11. from django.middleware import cache as middleware_cache
  12. from django.utils.translation import ugettext_lazy as _, ungettext
  13. from debug_toolbar import settings as dt_settings
  14. from debug_toolbar.panels import Panel
  15. from debug_toolbar.utils import (
  16. get_stack, get_template_info, render_stacktrace, tidy_stacktrace,
  17. )
  18. cache_called = Signal(providing_args=[
  19. "time_taken", "name", "return_value", "args", "kwargs", "trace"])
  20. def send_signal(method):
  21. def wrapped(self, *args, **kwargs):
  22. t = time.time()
  23. value = method(self, *args, **kwargs)
  24. t = time.time() - t
  25. if dt_settings.get_config()['ENABLE_STACKTRACES']:
  26. stacktrace = tidy_stacktrace(reversed(get_stack()))
  27. else:
  28. stacktrace = []
  29. template_info = get_template_info()
  30. cache_called.send(sender=self.__class__, time_taken=t,
  31. name=method.__name__, return_value=value,
  32. args=args, kwargs=kwargs, trace=stacktrace,
  33. template_info=template_info, backend=self.cache)
  34. return value
  35. return wrapped
  36. class CacheStatTracker(BaseCache):
  37. """A small class used to track cache calls."""
  38. def __init__(self, cache):
  39. self.cache = cache
  40. def __repr__(self):
  41. return str("<CacheStatTracker for %s>") % repr(self.cache)
  42. def _get_func_info(self):
  43. frame = sys._getframe(3)
  44. info = inspect.getframeinfo(frame)
  45. return (info[0], info[1], info[2], info[3])
  46. def __contains__(self, key):
  47. return self.cache.__contains__(key)
  48. def __getattr__(self, name):
  49. return getattr(self.cache, name)
  50. @send_signal
  51. def add(self, *args, **kwargs):
  52. return self.cache.add(*args, **kwargs)
  53. @send_signal
  54. def get(self, *args, **kwargs):
  55. return self.cache.get(*args, **kwargs)
  56. @send_signal
  57. def set(self, *args, **kwargs):
  58. return self.cache.set(*args, **kwargs)
  59. @send_signal
  60. def delete(self, *args, **kwargs):
  61. return self.cache.delete(*args, **kwargs)
  62. @send_signal
  63. def clear(self, *args, **kwargs):
  64. return self.cache.clear(*args, **kwargs)
  65. @send_signal
  66. def has_key(self, *args, **kwargs):
  67. # Ignore flake8 rules for has_key since we need to support caches
  68. # that may be using has_key.
  69. return self.cache.has_key(*args, **kwargs) # noqa
  70. @send_signal
  71. def incr(self, *args, **kwargs):
  72. return self.cache.incr(*args, **kwargs)
  73. @send_signal
  74. def decr(self, *args, **kwargs):
  75. return self.cache.decr(*args, **kwargs)
  76. @send_signal
  77. def get_many(self, *args, **kwargs):
  78. return self.cache.get_many(*args, **kwargs)
  79. @send_signal
  80. def set_many(self, *args, **kwargs):
  81. self.cache.set_many(*args, **kwargs)
  82. @send_signal
  83. def delete_many(self, *args, **kwargs):
  84. self.cache.delete_many(*args, **kwargs)
  85. @send_signal
  86. def incr_version(self, *args, **kwargs):
  87. return self.cache.incr_version(*args, **kwargs)
  88. @send_signal
  89. def decr_version(self, *args, **kwargs):
  90. return self.cache.decr_version(*args, **kwargs)
  91. class CacheHandlerPatch(CacheHandler):
  92. def __getitem__(self, alias):
  93. actual_cache = super(CacheHandlerPatch, self).__getitem__(alias)
  94. return CacheStatTracker(actual_cache)
  95. middleware_cache.caches = CacheHandlerPatch()
  96. class CachePanel(Panel):
  97. """
  98. Panel that displays the cache statistics.
  99. """
  100. template = 'debug_toolbar/panels/cache.html'
  101. def __init__(self, *args, **kwargs):
  102. super(CachePanel, self).__init__(*args, **kwargs)
  103. self.total_time = 0
  104. self.hits = 0
  105. self.misses = 0
  106. self.calls = []
  107. self.counts = OrderedDict((
  108. ('add', 0),
  109. ('get', 0),
  110. ('set', 0),
  111. ('delete', 0),
  112. ('clear', 0),
  113. ('get_many', 0),
  114. ('set_many', 0),
  115. ('delete_many', 0),
  116. ('has_key', 0),
  117. ('incr', 0),
  118. ('decr', 0),
  119. ('incr_version', 0),
  120. ('decr_version', 0),
  121. ))
  122. cache_called.connect(self._store_call_info)
  123. def _store_call_info(self, sender, name=None, time_taken=0,
  124. return_value=None, args=None, kwargs=None,
  125. trace=None, template_info=None, backend=None, **kw):
  126. if name == 'get':
  127. if return_value is None:
  128. self.misses += 1
  129. else:
  130. self.hits += 1
  131. elif name == 'get_many':
  132. for key, value in return_value.items():
  133. if value is None:
  134. self.misses += 1
  135. else:
  136. self.hits += 1
  137. time_taken *= 1000
  138. self.total_time += time_taken
  139. self.counts[name] += 1
  140. self.calls.append({
  141. 'time': time_taken,
  142. 'name': name,
  143. 'args': args,
  144. 'kwargs': kwargs,
  145. 'trace': render_stacktrace(trace),
  146. 'template_info': template_info,
  147. 'backend': backend
  148. })
  149. # Implement the Panel API
  150. nav_title = _("Cache")
  151. @property
  152. def nav_subtitle(self):
  153. cache_calls = len(self.calls)
  154. return ungettext("%(cache_calls)d call in %(time).2fms",
  155. "%(cache_calls)d calls in %(time).2fms",
  156. cache_calls) % {'cache_calls': cache_calls,
  157. 'time': self.total_time}
  158. @property
  159. def title(self):
  160. count = len(getattr(settings, 'CACHES', ['default']))
  161. return ungettext("Cache calls from %(count)d backend",
  162. "Cache calls from %(count)d backends",
  163. count) % dict(count=count)
  164. def enable_instrumentation(self):
  165. if isinstance(middleware_cache.caches, CacheHandlerPatch):
  166. cache.caches = middleware_cache.caches
  167. else:
  168. cache.caches = CacheHandlerPatch()
  169. def disable_instrumentation(self):
  170. cache.caches = original_caches
  171. # While it can be restored to the original, any views that were
  172. # wrapped with the cache_page decorator will continue to use a
  173. # monkey patched cache.
  174. middleware_cache.caches = original_caches
  175. def generate_stats(self, request, response):
  176. self.record_stats({
  177. 'total_calls': len(self.calls),
  178. 'calls': self.calls,
  179. 'total_time': self.total_time,
  180. 'hits': self.hits,
  181. 'misses': self.misses,
  182. 'counts': self.counts,
  183. })
  184. def generate_server_timing(self, request, response):
  185. stats = self.get_stats()
  186. value = stats.get('total_time', 0)
  187. title = 'Cache {} Calls'.format(stats.get('total_calls', 0))
  188. self.record_server_timing('total_time', title, value)