diff options
| author | David Cramer | 2011-09-16 01:09:01 -0700 | 
|---|---|---|
| committer | David Cramer | 2011-09-16 01:09:01 -0700 | 
| commit | 8278bb0f24d925288740c23a2315e9c5a27a3290 (patch) | |
| tree | 5dce59d9bd497a19e6cbaf8aebfc697200368b11 | |
| parent | 5a3813d2bb4fb63d66b1b8a13edb8354b36a28b4 (diff) | |
| parent | 69f1c7733e29ee9ea3e4670f2183deb52f58a871 (diff) | |
| download | django-debug-toolbar-8278bb0f24d925288740c23a2315e9c5a27a3290.tar.bz2 | |
Merge pull request #206 from lincolnloop/stats_decouple
Decouple statistics from presentation
| -rw-r--r-- | debug_toolbar/panels/__init__.py | 49 | ||||
| -rw-r--r-- | debug_toolbar/panels/cache.py | 32 | ||||
| -rw-r--r-- | debug_toolbar/panels/headers.py | 19 | ||||
| -rw-r--r-- | debug_toolbar/panels/logger.py | 42 | ||||
| -rw-r--r-- | debug_toolbar/panels/profiling.py | 72 | ||||
| -rw-r--r-- | debug_toolbar/panels/request_vars.py | 33 | ||||
| -rw-r--r-- | debug_toolbar/panels/settings_vars.py | 16 | ||||
| -rw-r--r-- | debug_toolbar/panels/signals.py | 23 | ||||
| -rw-r--r-- | debug_toolbar/panels/sql.py | 50 | ||||
| -rw-r--r-- | debug_toolbar/panels/template.py | 31 | ||||
| -rw-r--r-- | debug_toolbar/panels/timer.py | 105 | ||||
| -rw-r--r-- | debug_toolbar/panels/version.py | 20 | ||||
| -rw-r--r-- | debug_toolbar/toolbar/loader.py | 19 | ||||
| -rw-r--r-- | example/example.db | bin | 55296 -> 55296 bytes | |||
| -rw-r--r-- | example/settings.py | 8 | ||||
| -rw-r--r-- | example/templates/index.html | 20 | ||||
| -rw-r--r-- | runtests.py | 13 | ||||
| -rw-r--r-- | tests/tests.py | 10 | 
18 files changed, 302 insertions, 260 deletions
| diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index fa2e4b6..b4f11fb 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -1,48 +1,67 @@ -"""Base DebugPanel class""" +from django.template.defaultfilters import slugify +from django.template.loader import render_to_string +from debug_toolbar.middleware import DebugToolbarMiddleware +  class DebugPanel(object):      """      Base class for debug panels.      """ -    # name = Base +    # name = 'Base' +    # template = 'debug_toolbar/panels/base.html'      has_content = False # If content returns something, set to true in subclass - +          # We'll maintain a local context instance so we can expose our template      # context variables to panels which need them:      context = {} - +          # Panel methods      def __init__(self, context={}):          self.context.update(context) - +        self.slug = slugify(self.name) +          def dom_id(self):          return 'djDebug%sPanel' % (self.name.replace(' ', '')) - +          def nav_title(self):          """Title showing in toolbar"""          raise NotImplementedError - +          def nav_subtitle(self):          """Subtitle showing until title in toolbar"""          return '' - +          def title(self):          """Title showing in panel"""          raise NotImplementedError - +          def url(self):          raise NotImplementedError - +          def content(self): -        raise NotImplementedError - +        if self.has_content: +            context = self.context.copy() +            context.update(self.get_stats()) +            return render_to_string(self.template, context) +     +    def record_stats(self, stats): +        toolbar = DebugToolbarMiddleware.get_current() +        panel_stats = toolbar.stats.get(self.slug) +        if panel_stats: +            panel_stats.update(stats) +        else: +            toolbar.stats[self.slug] = stats +     +    def get_stats(self): +        toolbar = DebugToolbarMiddleware.get_current() +        return toolbar.stats.get(self.slug, {}) +          # Standard middleware methods      def process_request(self, request):          pass - +          def process_view(self, request, view_func, view_args, view_kwargs):          pass - +          def process_response(self, request, response):          pass - diff --git a/debug_toolbar/panels/cache.py b/debug_toolbar/panels/cache.py index 5617ec2..620be86 100644 --- a/debug_toolbar/panels/cache.py +++ b/debug_toolbar/panels/cache.py @@ -3,7 +3,6 @@ import inspect  from django.core import cache  from django.core.cache.backends.base import BaseCache -from django.template.loader import render_to_string  from django.utils.translation import ugettext_lazy as _  from debug_toolbar.panels import DebugPanel @@ -12,7 +11,7 @@ class CacheStatTracker(BaseCache):      def __init__(self, cache):          self.cache = cache          self.reset() - +          def reset(self):          self.calls = []          self.hits = 0 @@ -22,11 +21,11 @@ class CacheStatTracker(BaseCache):          self.get_many = 0          self.deletes = 0          self.total_time = 0 - +          def _get_func_info(self):          stack = inspect.stack()[2]          return (stack[1], stack[2], stack[3], stack[4]) - +          def get(self, key, default=None):          t = time.time()          value = self.cache.get(key, default) @@ -39,7 +38,7 @@ class CacheStatTracker(BaseCache):          self.gets += 1          self.calls.append((this_time, 'get', (key,), self._get_func_info()))          return value - +          def set(self, key, value, timeout=None):          t = time.time()          self.cache.set(key, value, timeout) @@ -47,7 +46,7 @@ class CacheStatTracker(BaseCache):          self.total_time += this_time * 1000          self.sets += 1          self.calls.append((this_time, 'set', (key, value, timeout), self._get_func_info())) - +          def delete(self, key):          t = time.time()          self.cache.delete(key) @@ -55,7 +54,7 @@ class CacheStatTracker(BaseCache):          self.total_time += this_time * 1000          self.deletes += 1          self.calls.append((this_time, 'delete', (key,), self._get_func_info())) - +          def get_many(self, keys):          t = time.time()          results = self.cache.get_many(keys) @@ -74,10 +73,11 @@ class CacheDebugPanel(DebugPanel):      Panel that displays the cache statistics.      """      name = 'Cache' +    template = 'debug_toolbar/panels/cache.html'      has_content = True - +          def __init__(self, *args, **kwargs): -        super(self.__class__, self).__init__(*args, **kwargs) +        super(CacheDebugPanel, self).__init__(*args, **kwargs)          # This is hackish but to prevent threading issues is somewhat needed          if isinstance(cache.cache, CacheStatTracker):              cache.cache.reset() @@ -85,21 +85,19 @@ class CacheDebugPanel(DebugPanel):          else:              self.cache = CacheStatTracker(cache.cache)              cache.cache = self.cache - +          def nav_title(self):          return _('Cache: %.2fms') % self.cache.total_time - +          def title(self):          return _('Cache Usage') - +          def url(self):          return '' - -    def content(self): -        context = self.context.copy() -        context.update({ +     +    def process_response(self, request, response): +        self.record_stats({              'cache_calls': len(self.cache.calls),              'cache_time': self.cache.total_time,              'cache': self.cache,          }) -        return render_to_string('debug_toolbar/panels/cache.html', context) diff --git a/debug_toolbar/panels/headers.py b/debug_toolbar/panels/headers.py index 1e929f6..181e88d 100644 --- a/debug_toolbar/panels/headers.py +++ b/debug_toolbar/panels/headers.py @@ -1,12 +1,13 @@ -from django.template.loader import render_to_string  from django.utils.translation import ugettext_lazy as _  from debug_toolbar.panels import DebugPanel +  class HeaderDebugPanel(DebugPanel):      """      A panel to display HTTP headers.      """      name = 'Header' +    template = 'debug_toolbar/panels/headers.html'      has_content = True      # List of headers we want to display      header_filter = ( @@ -31,24 +32,22 @@ class HeaderDebugPanel(DebugPanel):          'SERVER_PROTOCOL',          'SERVER_SOFTWARE',      ) - +          def nav_title(self):          return _('HTTP Headers') - +          def title(self):          return _('HTTP Headers') - +          def url(self):          return '' - +          def process_request(self, request):          self.headers = dict(              [(k, request.META[k]) for k in self.header_filter if k in request.META]          ) - -    def content(self): -        context = self.context.copy() -        context.update({ +     +    def process_response(self, request, response): +        self.record_stats({              'headers': self.headers          }) -        return render_to_string('debug_toolbar/panels/headers.html', context) diff --git a/debug_toolbar/panels/logger.py b/debug_toolbar/panels/logger.py index 5e82a13..55ae9bd 100644 --- a/debug_toolbar/panels/logger.py +++ b/debug_toolbar/panels/logger.py @@ -4,7 +4,6 @@ try:      import threading  except ImportError:      threading = None -from django.template.loader import render_to_string  from django.utils.translation import ugettext_lazy as _  from debug_toolbar.panels import DebugPanel @@ -15,15 +14,15 @@ class LogCollector(object):              raise NotImplementedError("threading module is not available, \                  the logging panel cannot be used without it")          self.records = {} # a dictionary that maps threads to log records - +          def add_record(self, record, thread=None):          # Avoid logging SQL queries since they are already in the SQL panel          # TODO: Make this check whether SQL panel is enabled          if record.get('channel', '') == 'django.db.backends':              return - +                  self.get_records(thread).append(record) - +          def get_records(self, thread=None):          """          Returns a list of records for the provided thread, of if none is provided, @@ -34,7 +33,7 @@ class LogCollector(object):          if thread not in self.records:              self.records[thread] = []          return self.records[thread] - +          def clear_records(self, thread=None):          if thread is None:              thread = threading.currentThread() @@ -46,7 +45,7 @@ class ThreadTrackingHandler(logging.Handler):      def __init__(self, collector):          logging.Handler.__init__(self)          self.collector = collector - +          def emit(self, record):          record = {              'message': record.getMessage(), @@ -76,7 +75,7 @@ if logbook_supported:          def __init__(self, collector):              logbook.handlers.Handler.__init__(self, bubble=True)              self.collector = collector - +                  def emit(self, record):              record = {                  'message': record.message, @@ -87,40 +86,37 @@ if logbook_supported:                  'channel': record.channel,              }              self.collector.add_record(record) - - +     +          logbook_handler = LogbookThreadTrackingHandler(collector)      logbook_handler.push_application()        # register with logbook  class LoggingPanel(DebugPanel):      name = 'Logging' +    template = 'debug_toolbar/panels/logger.html'      has_content = True - +          def process_request(self, request):          collector.clear_records() - +     +    def process_response(self, request, response): +        records = self.get_and_delete() +        self.record_stats({'records': records}) +          def get_and_delete(self):          records = collector.get_records()          collector.clear_records()          return records - +          def nav_title(self):          return _("Logging") - +          def nav_subtitle(self):          # FIXME l10n: use ngettext          return "%s message%s" % (len(collector.get_records()), (len(collector.get_records()) == 1) and '' or 's') - +          def title(self):          return _('Log Messages') - +          def url(self):          return '' - -    def content(self): -        records = self.get_and_delete() -        context = self.context.copy() -        context.update({'records': records}) - -        return render_to_string('debug_toolbar/panels/logger.html', context) - diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 289e1f7..8913621 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -1,6 +1,5 @@  from __future__ import division -from django.template.loader import render_to_string  from django.utils.translation import ugettext_lazy as _  from django.utils.safestring import mark_safe  from debug_toolbar.panels import DebugPanel @@ -8,9 +7,9 @@ from debug_toolbar.panels import DebugPanel  try:      from line_profiler import LineProfiler, show_func      DJ_PROFILE_USE_LINE_PROFILER = True -except ImportError:     +except ImportError:      DJ_PROFILE_USE_LINE_PROFILER = False -     +  from cStringIO import StringIO  import cProfile @@ -18,6 +17,7 @@ from pstats import Stats  from colorsys import hsv_to_rgb  import os +  class DjangoDebugToolbarStats(Stats):      __root = None @@ -28,9 +28,11 @@ class DjangoDebugToolbarStats(Stats):                      self.__root = func                      break          return self.__root -     + +  class FunctionCall(object): -    def __init__(self, statobj, func, depth=0, stats=None, id=0, parent_ids=[], hsv=(0,0.5,1)): +    def __init__(self, statobj, func, depth=0, stats=None, +                 id=0, parent_ids=[], hsv=(0,0.5,1)):          self.statobj = statobj          self.func = func          if stats: @@ -63,7 +65,7 @@ class FunctionCall(object):              file_name, line_num, method = self.func              idx = file_name.find('/site-packages/')              if idx > -1: -                file_name=file_name[idx+14:] +                file_name = file_name[idx+14:]              file_path, file_name = file_name.rsplit(os.sep, 1) @@ -76,7 +78,7 @@ class FunctionCall(object):      def subfuncs(self):          i=0 -        h,s,v = self.hsv +        h, s, v = self.hsv          count = len(self.statobj.all_callees[self.func])          for func, stats in self.statobj.all_callees[self.func].iteritems():              i += 1 @@ -86,8 +88,8 @@ class FunctionCall(object):              else:                  s1 = s*(stats[3]/self.stats[3])              yield FunctionCall(self.statobj, -                               func,  -                               self.depth+1,  +                               func, +                               self.depth+1,                                 stats=stats,                                 id=str(self.id) + '_' + str(i),                                 parent_ids=self.parent_ids + [self.id], @@ -105,25 +107,25 @@ class FunctionCall(object):      def tottime_per_call(self):          cc, nc, tt, ct = self.stats - +                  if nc == 0:              return 0 - +                  return tt/nc      def cumtime_per_call(self):          cc, nc, tt, ct = self.stats - +                  if cc == 0:              return 0 - +                  return ct/cc - +          def indent(self):          return 16 * self.depth -         +          def line_stats_text(self): -        if self._line_stats_text is None: +        if self._line_stats_text is None and DJ_PROFILE_USE_LINE_PROFILER:              lstats = self.statobj.line_stats              if self.func in lstats.timings:                  out = StringIO() @@ -139,17 +141,18 @@ class ProfilingDebugPanel(DebugPanel):      Panel that displays the Django version.      """      name = 'Profiling' +    template = 'debug_toolbar/panels/profiling.html'      has_content = True - +          def nav_title(self):          return _('Profiling') - +          def url(self):          return ''      def title(self):          return _('Profiling') - +          def _unwrap_closure_and_profile(self, func):          if not hasattr(func, 'func_code'):              return @@ -158,7 +161,7 @@ class ProfilingDebugPanel(DebugPanel):              for cell in func.func_closure:                  if hasattr(cell.cell_contents, 'func_code'):                      self._unwrap_closure_and_profile(cell.cell_contents) - +          def process_view(self, request, view_func, view_args, view_kwargs):          __traceback_hide__ = True          self.profiler = cProfile.Profile() @@ -173,33 +176,28 @@ class ProfilingDebugPanel(DebugPanel):              self.line_profiler = None              out = self.profiler.runcall(view_func, *args, **view_kwargs)          return out - -    def process_response(self, request, response): -        self.profiler.create_stats() -        self.stats = DjangoDebugToolbarStats(self.profiler) -        if DJ_PROFILE_USE_LINE_PROFILER: -            self.stats.line_stats = self.line_profiler.get_stats() -        return response - +          def add_node(self, func_list, func, max_depth, cum_time=0.1):          func_list.append(func)          func.has_subfuncs = False          if func.depth < max_depth:              for subfunc in func.subfuncs(): -                if subfunc.stats[3] >= cum_time or (subfunc.func in self.stats.line_stats.timings): +                if (subfunc.stats[3] >= cum_time or +                   (hasattr(self.stats, 'line_stats') and +                   (subfunc.func in self.stats.line_stats.timings))):                      func.has_subfuncs = True                      self.add_node(func_list, subfunc, max_depth, cum_time=cum_time) -    def content(self): -         +    def process_response(self, request, response): +        self.profiler.create_stats() +        self.stats = DjangoDebugToolbarStats(self.profiler) +        if DJ_PROFILE_USE_LINE_PROFILER: +            self.stats.line_stats = self.line_profiler.get_stats()          self.stats.calc_callees() +                  root = FunctionCall(self.stats, self.stats.get_root_func(), depth=0)          func_list = []          self.add_node(func_list, root, 10, root.stats[3]/8) -        context = self.context.copy() -        context.update({ -            'func_list': func_list, -        }) - -        return render_to_string('debug_toolbar/panels/profiling.html', context) +         +        self.stats_record({'func_list': func_list}) diff --git a/debug_toolbar/panels/request_vars.py b/debug_toolbar/panels/request_vars.py index 9033d48..ee9cdc5 100644 --- a/debug_toolbar/panels/request_vars.py +++ b/debug_toolbar/panels/request_vars.py @@ -1,4 +1,3 @@ -from django.template.loader import render_to_string  from django.utils.translation import ugettext_lazy as _  from debug_toolbar.panels import DebugPanel @@ -9,54 +8,52 @@ class RequestVarsDebugPanel(DebugPanel):      A panel to display request variables (POST/GET, session, cookies).      """      name = 'RequestVars' +    template = 'debug_toolbar/panels/request_vars.html'      has_content = True - +          def __init__(self, *args, **kwargs):          DebugPanel.__init__(self, *args, **kwargs)          self.view_func = None          self.view_args = None          self.view_kwargs = None - +          def nav_title(self):          return _('Request Vars') - +          def title(self):          return _('Request Vars') - +          def url(self):          return '' - +          def process_request(self, request):          self.request = request - +          def process_view(self, request, view_func, view_args, view_kwargs):          self.view_func = view_func          self.view_args = view_args          self.view_kwargs = view_kwargs - -    def content(self): -        context = self.context.copy() - -        context.update({ +     +    def process_response(self, request, response): +        self.record_stats({              'get': [(k, self.request.GET.getlist(k)) for k in self.request.GET],              'post': [(k, self.request.POST.getlist(k)) for k in self.request.POST],              'cookies': [(k, self.request.COOKIES.get(k)) for k in self.request.COOKIES],          }) +                  if hasattr(self, 'view_func'):              if self.view_func is not None:                  name = get_name_from_obj(self.view_func)              else:                  name = '<no view>' - -            context.update({ +             +            self.record_stats({                  'view_func': name,                  'view_args': self.view_args,                  'view_kwargs': self.view_kwargs              }) - +                  if hasattr(self.request, 'session'): -            context.update({ +            self.record_stats({                  'session': [(k, self.request.session.get(k)) for k in self.request.session.iterkeys()]              }) - -        return render_to_string('debug_toolbar/panels/request_vars.html', context) diff --git a/debug_toolbar/panels/settings_vars.py b/debug_toolbar/panels/settings_vars.py index ebb4464..e154dda 100644 --- a/debug_toolbar/panels/settings_vars.py +++ b/debug_toolbar/panels/settings_vars.py @@ -1,5 +1,4 @@  from django.conf import settings -from django.template.loader import render_to_string  from django.views.debug import get_safe_settings  from django.utils.translation import ugettext_lazy as _  from debug_toolbar.panels import DebugPanel @@ -10,20 +9,19 @@ class SettingsVarsDebugPanel(DebugPanel):      A panel to display all variables in django.conf.settings      """      name = 'SettingsVars' +    template = 'debug_toolbar/panels/settings_vars.html'      has_content = True - +          def nav_title(self):          return _('Settings') - +          def title(self):          return _('Settings from <code>%s</code>') % settings.SETTINGS_MODULE - +          def url(self):          return '' - -    def content(self): -        context = self.context.copy() -        context.update({ +     +    def process_response(self, request, response): +        self.record_stats({              'settings': get_safe_settings(),          }) -        return render_to_string('debug_toolbar/panels/settings_vars.html', context) diff --git a/debug_toolbar/panels/signals.py b/debug_toolbar/panels/signals.py index dbd3725..205b3c5 100644 --- a/debug_toolbar/panels/signals.py +++ b/debug_toolbar/panels/signals.py @@ -6,7 +6,6 @@ from django.core.signals import request_started, request_finished, \  from django.db.models.signals import class_prepared, pre_init, post_init, \      pre_save, post_save, pre_delete, post_delete, post_syncdb  from django.dispatch.dispatcher import WEAKREF_TYPES -from django.template.loader import render_to_string  from django.utils.translation import ugettext_lazy as _  try: @@ -18,8 +17,9 @@ from debug_toolbar.panels import DebugPanel  class SignalDebugPanel(DebugPanel):      name = "Signals" +    template = 'debug_toolbar/panels/signals.html'      has_content = True - +          SIGNALS = {          'request_started': request_started,          'request_finished': request_finished, @@ -34,16 +34,16 @@ class SignalDebugPanel(DebugPanel):          'post_delete': post_delete,          'post_syncdb': post_syncdb,      } - +          def nav_title(self):          return _("Signals") - +          def title(self):          return _("Signals") - +          def url(self):          return '' - +          def signals(self):          signals = self.SIGNALS.copy()          if hasattr(settings, 'DEBUG_TOOLBAR_CONFIG'): @@ -57,8 +57,8 @@ class SignalDebugPanel(DebugPanel):              signals[parts[-1]] = getattr(sys.modules[path], parts[-1])          return signals      signals = property(signals) - -    def content(self): +     +    def process_response(self, request, response):          signals = []          keys = self.signals.keys()          keys.sort() @@ -80,8 +80,5 @@ class SignalDebugPanel(DebugPanel):                      text = "function %s" % receiver.__name__                  receivers.append(text)              signals.append((name, signal, receivers)) - -        context = self.context.copy() -        context.update({'signals': signals}) - -        return render_to_string('debug_toolbar/panels/signals.html', context) +         +        self.record_stats({'signals': signals}) diff --git a/debug_toolbar/panels/sql.py b/debug_toolbar/panels/sql.py index 841aaac..b27e6c0 100644 --- a/debug_toolbar/panels/sql.py +++ b/debug_toolbar/panels/sql.py @@ -2,7 +2,6 @@ import re  import uuid  from django.db.backends import BaseDatabaseWrapper -from django.template.loader import render_to_string  from django.utils.html import escape  from django.utils.safestring import mark_safe  from django.utils.translation import ugettext_lazy as _, ungettext_lazy as __ @@ -14,11 +13,12 @@ from debug_toolbar.utils import sqlparse  from debug_toolbar.utils.tracking.db import CursorWrapper  from debug_toolbar.utils.tracking import replace_call +  # Inject our tracking cursor  @replace_call(BaseDatabaseWrapper.cursor)  def cursor(func, self):      result = func(self) - +          djdt = DebugToolbarMiddleware.get_current()      if not djdt:          return result @@ -26,6 +26,7 @@ def cursor(func, self):      return CursorWrapper(result, self, logger=logger) +  def get_isolation_level_display(engine, level):      if engine == 'psycopg2':          import psycopg2.extensions @@ -41,6 +42,7 @@ def get_isolation_level_display(engine, level):      return choices.get(level) +  def get_transaction_status_display(engine, level):      if engine == 'psycopg2':          import psycopg2.extensions @@ -56,16 +58,18 @@ def get_transaction_status_display(engine, level):      return choices.get(level) +  class SQLDebugPanel(DebugPanel):      """      Panel that displays information about the SQL queries run while processing      the request.      """      name = 'SQL' +    template = 'debug_toolbar/panels/sql.html'      has_content = True - +          def __init__(self, *args, **kwargs): -        super(self.__class__, self).__init__(*args, **kwargs) +        super(SQLDebugPanel, self).__init__(*args, **kwargs)          self._offset = dict((k, len(connections[k].queries)) for k in connections)          self._sql_time = 0          self._num_queries = 0 @@ -78,20 +82,20 @@ class SQLDebugPanel(DebugPanel):          conn = connections[alias].connection          if not conn:              return None - +                  engine = conn.__class__.__module__.split('.', 1)[0]          if engine == 'psycopg2':              cur_status = conn.get_transaction_status()          else:              raise ValueError(engine) - +                  last_status = self._transaction_status.get(alias)          self._transaction_status[alias] = cur_status - +                  if not cur_status:              # No available state              return None - +                  if cur_status != last_status:              if cur_status:                  self._transaction_ids[alias] = uuid.uuid4().hex @@ -112,10 +116,10 @@ class SQLDebugPanel(DebugPanel):              self._databases[alias]['num_queries'] += 1          self._sql_time += kwargs['duration']          self._num_queries += 1 - +          def nav_title(self):          return _('SQL') - +          def nav_subtitle(self):          # TODO l10n: use ngettext          return "%d %s in %.2fms" % ( @@ -123,18 +127,18 @@ class SQLDebugPanel(DebugPanel):              (self._num_queries == 1) and 'query' or 'queries',              self._sql_time          ) - +          def title(self):          count = len(self._databases)          return __('SQL Queries from %(count)d connection', 'SQL Queries from %(count)d connections', count) % dict(              count=count,          ) - +          def url(self):          return '' - -    def content(self): +     +    def process_response(self, request, response):          if self._queries:              width_ratio_tally = 0              colors = [ @@ -157,7 +161,7 @@ class SQLDebugPanel(DebugPanel):                          nn = 0                      rgb[nn] = nc                  db['rgb_color'] = rgb -         +                          trans_ids = {}              trans_id = None              i = 0 @@ -190,25 +194,27 @@ class SQLDebugPanel(DebugPanel):                  query['start_offset'] = width_ratio_tally                  query['end_offset'] = query['width_ratio'] + query['start_offset']                  width_ratio_tally += query['width_ratio'] -             +                                  stacktrace = []                  for frame in query['stacktrace']:                      params = map(escape, frame[0].rsplit('/', 1) + list(frame[1:])) -                    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)) +                    try: +                        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)) +                    except IndexError: +                        # This frame doesn't have the expected format, so skip it and move on to the next one +                        continue                  query['stacktrace'] = mark_safe('\n'.join(stacktrace))                  i += 1 - +                          if trans_id:                  self._queries[i-1][1]['ends_trans'] = True -        context = self.context.copy() -        context.update({ +        self.record_stats({              'databases': sorted(self._databases.items(), key=lambda x: -x[1]['time_spent']),              'queries': [q for a, q in self._queries],              'sql_time': self._sql_time,          }) -        return render_to_string('debug_toolbar/panels/sql.html', context)  class BoldKeywordFilter(sqlparse.filters.Filter):      """sqlparse filter to bold SQL keywords""" @@ -222,10 +228,12 @@ class BoldKeywordFilter(sqlparse.filters.Filter):              if is_keyword:                  yield sqlparse.tokens.Text, '</strong>' +  def swap_fields(sql):      return re.sub('SELECT</strong> (.*) <strong>FROM', 'SELECT</strong> <a class="djDebugUncollapsed djDebugToggle" href="#">•••</a> ' +          '<a class="djDebugCollapsed djDebugToggle" href="#">\g<1></a> <strong>FROM', sql) +  def reformat_sql(sql):      stack = sqlparse.engine.FilterStack()      stack.preprocess.append(BoldKeywordFilter()) # add our custom filter diff --git a/debug_toolbar/panels/template.py b/debug_toolbar/panels/template.py index d620a38..79a4871 100644 --- a/debug_toolbar/panels/template.py +++ b/debug_toolbar/panels/template.py @@ -4,7 +4,6 @@ from pprint import pformat  from django import http  from django.conf import settings  from django.template.context import get_standard_processors -from django.template.loader import render_to_string  from django.test.signals import template_rendered  from django.utils.translation import ugettext_lazy as _  from debug_toolbar.panels import DebugPanel @@ -27,6 +26,7 @@ else:          Template.original_render = Template._render          Template._render = instrumented_test_render +  # MONSTER monkey-patch  old_template_init = Template.__init__  def new_template_init(self, template_string, origin=None, name='<Unknown Template>'): @@ -34,21 +34,23 @@ def new_template_init(self, template_string, origin=None, name='<Unknown Templat      self.origin = origin  Template.__init__ = new_template_init +  class TemplateDebugPanel(DebugPanel):      """      A panel that lists all templates used during processing of a response.      """      name = 'Template' +    template = 'debug_toolbar/panels/templates.html'      has_content = True - +          def __init__(self, *args, **kwargs): -        super(self.__class__, self).__init__(*args, **kwargs) +        super(TemplateDebugPanel, self).__init__(*args, **kwargs)          self.templates = []          template_rendered.connect(self._store_template_info) - +          def _store_template_info(self, sender, **kwargs):          context_data = kwargs['context'] - +                  context_list = []          for context_layer in context_data.dicts:              temp_layer = {} @@ -74,22 +76,22 @@ class TemplateDebugPanel(DebugPanel):                  pass          kwargs['context'] = context_list          self.templates.append(kwargs) - +          def nav_title(self):          return _('Templates') - +          def title(self):          num_templates = len([t for t in self.templates              if not (t['template'].name and t['template'].name.startswith('debug_toolbar/'))])          return _('Templates (%(num_templates)s rendered)') % {'num_templates': num_templates} - +          def url(self):          return '' - +          def process_request(self, request):          self.request = request - -    def content(self): +     +    def process_response(self, request, response):          context_processors = dict(              [                  ("%s.%s" % (k.__module__, k.__name__), @@ -116,12 +118,9 @@ class TemplateDebugPanel(DebugPanel):                  context_list = template_data.get('context', [])                  info['context'] = '\n'.join(context_list)              template_context.append(info) - -        context = self.context.copy() -        context.update({ +         +        self.record_stats({              'templates': template_context,              'template_dirs': [normpath(x) for x in settings.TEMPLATE_DIRS],              'context_processors': context_processors,          }) - -        return render_to_string('debug_toolbar/panels/templates.html', context) diff --git a/debug_toolbar/panels/timer.py b/debug_toolbar/panels/timer.py index d866166..7c0febf 100644 --- a/debug_toolbar/panels/timer.py +++ b/debug_toolbar/panels/timer.py @@ -7,11 +7,13 @@ from django.template.loader import render_to_string  from django.utils.translation import ugettext_lazy as _  from debug_toolbar.panels import DebugPanel +  class TimerDebugPanel(DebugPanel):      """      Panel that displays the time a response took in milliseconds.      """      name = 'Timer' +    template = 'debug_toolbar/panels/timer.html'      try: # if resource module not available, don't show content panel          resource      except NameError: @@ -20,47 +22,23 @@ class TimerDebugPanel(DebugPanel):      else:          has_content = True          has_resource = True - +          def process_request(self, request):          self._start_time = time.time()          if self.has_resource:              self._start_rusage = resource.getrusage(resource.RUSAGE_SELF) - +          def process_response(self, request, response): -        self.total_time = (time.time() - self._start_time) * 1000 +        total_time = (time.time() - self._start_time) * 1000          if self.has_resource:              self._end_rusage = resource.getrusage(resource.RUSAGE_SELF) - -    def nav_title(self): -        return _('Time') - -    def nav_subtitle(self): -        # TODO l10n -        if self.has_resource: -            utime = self._end_rusage.ru_utime - self._start_rusage.ru_utime -            stime = self._end_rusage.ru_stime - self._start_rusage.ru_stime -            return 'CPU: %0.2fms (%0.2fms)' % ((utime + stime) * 1000.0, self.total_time) -        else: -            return 'TOTAL: %0.2fms' % (self.total_time) - -    def title(self): -        return _('Resource Usage') - -    def url(self): -        return '' - -    def _elapsed_ru(self, name): -        return getattr(self._end_rusage, name) - getattr(self._start_rusage, name) - -    def content(self): - +                  utime = 1000 * self._elapsed_ru('ru_utime')          stime = 1000 * self._elapsed_ru('ru_stime')          vcsw = self._elapsed_ru('ru_nvcsw')          ivcsw = self._elapsed_ru('ru_nivcsw')          minflt = self._elapsed_ru('ru_minflt')          majflt = self._elapsed_ru('ru_majflt') -  # these are documented as not meaningful under Linux.  If you're running BSD  # feel free to enable them, and add any others that I hadn't gotten to before  # I noticed that I was getting nothing but zeroes and that the docs agreed. :-( @@ -72,22 +50,63 @@ class TimerDebugPanel(DebugPanel):  #        srss = self._end_rusage.ru_ixrss  #        urss = self._end_rusage.ru_idrss  #        usrss = self._end_rusage.ru_isrss - +         +        self.record_stats({ +            'total_time': total_time, +            'utime': utime, +            'stime': stime, +            'vcsw': vcsw, +            'ivcsw': ivcsw, +            'minflt': minflt, +            'majflt': majflt, +#            'blkin': blkin, +#            'blkout': blkout, +#            'swap': swap, +#            'rss': rss, +#            'urss': urss, +#            'srss': srss, +#            'usrss': usrss, +        }) +     +    def nav_title(self): +        return _('Time') +     +    def nav_subtitle(self): +        stats = self.get_stats() +         +        # TODO l10n +        if self.has_resource: +            utime = self._end_rusage.ru_utime - self._start_rusage.ru_utime +            stime = self._end_rusage.ru_stime - self._start_rusage.ru_stime +            return 'CPU: %0.2fms (%0.2fms)' % ((utime + stime) * 1000.0, stats['total_time']) +        else: +            return 'TOTAL: %0.2fms' % (stats['total_time']) +     +    def title(self): +        return _('Resource Usage') +     +    def url(self): +        return '' +     +    def _elapsed_ru(self, name): +        return getattr(self._end_rusage, name) - getattr(self._start_rusage, name) +     +    def content(self): +        stats = self.get_stats() +                  # TODO l10n on values          rows = ( -            (_('User CPU time'), '%0.3f msec' % utime), -            (_('System CPU time'), '%0.3f msec' % stime), -            (_('Total CPU time'), '%0.3f msec' % (utime + stime)), -            (_('Elapsed time'), '%0.3f msec' % self.total_time), -            (_('Context switches'), '%d voluntary, %d involuntary' % (vcsw, ivcsw)), -#            ('Memory use', '%d max RSS, %d shared, %d unshared' % (rss, srss, urss + usrss)), -#            ('Page faults', '%d no i/o, %d requiring i/o' % (minflt, majflt)), -#            ('Disk operations', '%d in, %d out, %d swapout' % (blkin, blkout, swap)), +            (_('User CPU time'), '%0.3f msec' % stats['utime']), +            (_('System CPU time'), '%0.3f msec' % stats['stime']), +            (_('Total CPU time'), '%0.3f msec' % (stats['utime'] + stats['stime'])), +            (_('Elapsed time'), '%0.3f msec' % stats['total_time']), +            (_('Context switches'), '%d voluntary, %d involuntary' % (stats['vcsw'], stats['ivcsw'])), +#            ('Memory use', '%d max RSS, %d shared, %d unshared' % (stats['rss'], stats.['srss'], +#                                                                   stats['urss'] + stats['usrss'])), +#            ('Page faults', '%d no i/o, %d requiring i/o' % (stats['minflt'], stats['majflt'])), +#            ('Disk operations', '%d in, %d out, %d swapout' % (stats['blkin'], stats['blkout'], stats['swap'])),          ) - +                  context = self.context.copy() -        context.update({ -            'rows': rows, -        }) - -        return render_to_string('debug_toolbar/panels/timer.html', context) +        context.update({'rows': rows,}) +        return render_to_string(self.template, context) diff --git a/debug_toolbar/panels/version.py b/debug_toolbar/panels/version.py index f0d8fbc..4e58971 100644 --- a/debug_toolbar/panels/version.py +++ b/debug_toolbar/panels/version.py @@ -2,10 +2,8 @@ import sys  import django  from django.conf import settings -from django.template.loader import render_to_string  from django.utils.translation import ugettext_lazy as _ -import debug_toolbar  from debug_toolbar.panels import DebugPanel @@ -14,21 +12,22 @@ class VersionDebugPanel(DebugPanel):      Panel that displays the Django version.      """      name = 'Version' +    template = 'debug_toolbar/panels/versions.html'      has_content = True - +          def nav_title(self):          return _('Versions') - +          def nav_subtitle(self):          return 'Django %s' % django.get_version() - +          def url(self):          return ''      def title(self):          return _('Versions') - -    def content(self): +     +    def process_response(self, request, response):          versions = {}          versions['Python'] = '%d.%d.%d' % sys.version_info[:3]          for app in settings.INSTALLED_APPS + ['django']: @@ -50,11 +49,8 @@ class VersionDebugPanel(DebugPanel):              if isinstance(version, (list, tuple)):                  version = '.'.join(str(o) for o in version)              versions[name] = version - -        context = self.context.copy() -        context.update({ +         +        self.record_stats({              'versions': versions,              'paths': sys.path,          }) - -        return render_to_string('debug_toolbar/panels/versions.html', context) diff --git a/debug_toolbar/toolbar/loader.py b/debug_toolbar/toolbar/loader.py index 3501c69..cf7a243 100644 --- a/debug_toolbar/toolbar/loader.py +++ b/debug_toolbar/toolbar/loader.py @@ -9,7 +9,7 @@ from django.utils.datastructures import SortedDict  from django.utils.safestring import mark_safe  class DebugToolbar(object): - +          def __init__(self, request):          self.request = request          self._panels = SortedDict() @@ -38,25 +38,26 @@ class DebugToolbar(object):              'debug_toolbar.panels.logger.LoggingPanel',          )          self.load_panels() +        self.stats = {}      def _get_panels(self):          return self._panels.values()      panels = property(_get_panels) - +          def get_panel(self, cls):          return self._panels[cls] - +          def load_panels(self):          """          Populate debug panels          """          from django.conf import settings          from django.core import exceptions - +                  # Check if settings has a DEBUG_TOOLBAR_PANELS, otherwise use default          if hasattr(settings, 'DEBUG_TOOLBAR_PANELS'):              self.default_panels = settings.DEBUG_TOOLBAR_PANELS - +                  for panel_path in self.default_panels:              try:                  dot = panel_path.rindex('.') @@ -71,14 +72,14 @@ class DebugToolbar(object):                  panel_class = getattr(mod, panel_classname)              except AttributeError:                  raise exceptions.ImproperlyConfigured, 'Toolbar Panel module "%s" does not define a "%s" class' % (panel_module, panel_classname) - +                          try:                  panel_instance = panel_class(context=self.template_context)              except:                  raise # Bubble up problem loading panel - +                          self._panels[panel_class] = panel_instance - +          def render_toolbar(self):          """          Renders the overall Toolbar with panels inside. @@ -91,5 +92,5 @@ class DebugToolbar(object):              'js': mark_safe(open(os.path.join(media_path, 'js', 'toolbar.min.js'), 'r').read()),              'css': mark_safe(open(os.path.join(media_path, 'css', 'toolbar.min.css'), 'r').read()),          }) - +                  return render_to_string('debug_toolbar/base.html', context) diff --git a/example/example.db b/example/example.dbBinary files differ index e0048d8..7acdc0d 100644 --- a/example/example.db +++ b/example/example.db diff --git a/example/settings.py b/example/settings.py index 11b0117..fac99af 100644 --- a/example/settings.py +++ b/example/settings.py @@ -41,7 +41,11 @@ DEBUG_TOOLBAR_PANELS = (      'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',      'debug_toolbar.panels.sql.SQLDebugPanel',      'debug_toolbar.panels.template.TemplateDebugPanel', -    #'debug_toolbar.panels.cache.CacheDebugPanel', +    'debug_toolbar.panels.cache.CacheDebugPanel',      'debug_toolbar.panels.signals.SignalDebugPanel',      'debug_toolbar.panels.logger.LoggingPanel', -)
\ No newline at end of file +) + +CACHE_BACKEND = 'dummy://' +#CACHE_BACKEND = 'memcached://127.0.0.1:11211' + diff --git a/example/templates/index.html b/example/templates/index.html index 6fd3a92..d7d8a2b 100644 --- a/example/templates/index.html +++ b/example/templates/index.html @@ -1,16 +1,18 @@ +{% load cache %}  <html>  <head> -	<meta http-equiv="content-type" content="text/html; charset=utf-8"> -	<title>Index of Tests</title> +    <meta http-equiv="content-type" content="text/html; charset=utf-8"> +    <title>Index of Tests</title>  </head>  <body> -	<h1>Index of Tests</h1> -	<ul> -		<li><a href="/jquery/index/">jQuery 1.2.6</a></li> -		<li><a href="/mootools/index/">MooTools 1.2.4</a></li> -		<li><a href="/prototype/index/">Prototype 1.6.1</a></li> -	</ul> - +    <h1>Index of Tests</h1> +    {% cache 10 index_cache %} +    <ul> +        <li><a href="/jquery/index/">jQuery 1.2.6</a></li> +        <li><a href="/mootools/index/">MooTools 1.2.4</a></li> +        <li><a href="/prototype/index/">Prototype 1.6.1</a></li> +    </ul> +    {% endcache %}  </body>  </html> diff --git a/runtests.py b/runtests.py index b8abc5d..dc5f6d0 100644 --- a/runtests.py +++ b/runtests.py @@ -3,7 +3,7 @@ import sys  from os.path import dirname, abspath  from optparse import OptionParser -from django.conf import settings +from django.conf import settings, global_settings  if not settings.configured:      settings.configure( @@ -17,11 +17,14 @@ if not settings.configured:              'django.contrib.contenttypes',              'django.contrib.sessions',              'django.contrib.sites', - +                          'debug_toolbar',              'tests',          ], +        MIDDLEWARE_CLASSES = global_settings.MIDDLEWARE_CLASSES + ( +            'debug_toolbar.middleware.DebugToolbarMiddleware', +        ),          ROOT_URLCONF='',          DEBUG=False,          SITE_ID=1, @@ -33,7 +36,7 @@ def runtests(*test_args, **kwargs):      if 'south' in settings.INSTALLED_APPS:          from south.management.commands import patch_for_test_db_setup          patch_for_test_db_setup() - +          if not test_args:          test_args = ['tests']      parent = dirname(abspath(__file__)) @@ -44,7 +47,7 @@ def runtests(*test_args, **kwargs):  if __name__ == '__main__':      parser = OptionParser()      parser.add_option('--failfast', action='store_true', default=False, dest='failfast') - +          (options, args) = parser.parse_args() - +          runtests(failfast=options.failfast, *args)
\ No newline at end of file diff --git a/tests/tests.py b/tests/tests.py index 60620bb..b03e167 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -38,18 +38,22 @@ class Settings(object):  class BaseTestCase(TestCase):      def setUp(self):          request = Dingus('request') +        response = Dingus('response')          toolbar = DebugToolbar(request)          DebugToolbarMiddleware.debug_toolbars[thread.get_ident()] = toolbar          self.request = request +        self.response = response          self.toolbar = toolbar +        self.toolbar.stats = {}  class DebugToolbarTestCase(BaseTestCase):      urls = 'tests.urls'      def test_middleware(self): -        resp = self.client.get('/execute_sql/') +        with Settings(INTERNAL_IPS=['127.0.0.1'], DEBUG=True): +            resp = self.client.get('/execute_sql/')          self.assertEquals(resp.status_code, 200)      def test_show_toolbar_DEBUG(self): @@ -143,6 +147,7 @@ class DebugToolbarTestCase(BaseTestCase):      def test_with_process_view(self):          request = self.request +        response = self.response          def _test_view(request):              return HttpResponse('') @@ -151,15 +156,18 @@ class DebugToolbarTestCase(BaseTestCase):              panel = self.toolbar.get_panel(RequestVarsDebugPanel)              panel.process_request(request)              panel.process_view(request, _test_view, [], {}) +            panel.process_response(request, response)              content = panel.content()              self.assertTrue('tests.tests._test_view' in content, content)      def test_without_process_view(self):          request = self.request +        response = self.response          with Settings(DEBUG=True):              panel = self.toolbar.get_panel(RequestVarsDebugPanel)              panel.process_request(request) +            panel.process_response(request, response)              content = panel.content()              self.assertTrue('<no view>' in content, content) | 
