diff options
Diffstat (limited to 'debug_toolbar/panels/sql.py')
| -rw-r--r-- | debug_toolbar/panels/sql.py | 108 | 
1 files changed, 101 insertions, 7 deletions
| diff --git a/debug_toolbar/panels/sql.py b/debug_toolbar/panels/sql.py index e1e9bdf..c6e73a0 100644 --- a/debug_toolbar/panels/sql.py +++ b/debug_toolbar/panels/sql.py @@ -6,14 +6,29 @@ import traceback  import django  from django.conf import settings -from django.db import connection +try: +    from django.db import connections +except ImportError: +    # Compatibility with Django < 1.2 +    from django.db import connection +    connections = {'default': connection} +    connection.alias = 'default' +  from django.db.backends import util  from django.views.debug import linebreak_iter  from django.template import Node  from django.template.loader import render_to_string  from django.utils import simplejson -from django.utils.encoding import force_unicode +from django.utils.encoding import force_unicode, DjangoUnicodeDecodeError  from django.utils.hashcompat import sha_constructor +try: +    from django.utils.log import getLogger +    logger = getLogger('django.db.backends') +    has_logger = True +except ImportError: +    # Compatibility with Django < 1.2 +    has_logger = False +      from django.utils.translation import ugettext_lazy as _  from debug_toolbar.panels import DebugPanel @@ -85,12 +100,73 @@ class DatabaseStatTracker(util.CursorDebugWrapper):      Replacement for CursorDebugWrapper which stores additional information      in `connection.queries`.      """ +    def clean_params(self, params): +        clean_params = () +        for x in params: +            try: +                force_unicode(x, strings_only=True) +            except DjangoUnicodeDecodeError: +                clean_params += ("<non unicode object>", ) +            else: +                clean_params += (x, ) +        return clean_params +      def execute(self, sql, params=()):          start = datetime.now()          try:              return self.cursor.execute(sql, params)          finally:              stop = datetime.now() +            params = self.clean_params(params) +            duration = ms_from_timedelta(stop - start) +            stacktrace = tidy_stacktrace(traceback.extract_stack()) +            _params = '' +            try: +                _params = simplejson.dumps([force_unicode(x, strings_only=True) for x in params]) +            except TypeError: +                pass # object not JSON serializable + +            template_info = None +            cur_frame = sys._getframe().f_back +            try: +                while cur_frame is not None: +                    if cur_frame.f_code.co_name == 'render': +                        node = cur_frame.f_locals['self'] +                        if isinstance(node, Node): +                            template_info = get_template_info(node.source) +                            break +                    cur_frame = cur_frame.f_back +            except: +                pass +            del cur_frame + +            # Logging was added in Django 1.3 +            if has_logger: +                logger.debug('(%.3f) %s; args=%s' % (duration, sql, params), +                    extra={'duration':duration, 'sql':sql, 'params':params} +                ) + +            # 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), +                'is_select': sql.lower().strip().startswith('select'), +                'template_info': template_info, +            }) + +    def executemany(self, sql, params=()): +        start = datetime.now() +        try: +            return self.cursor.executemany(sql, params) +        finally: +            stop = datetime.now()              duration = ms_from_timedelta(stop - start)              stacktrace = tidy_stacktrace(traceback.extract_stack())              _params = '' @@ -113,6 +189,11 @@ class DatabaseStatTracker(util.CursorDebugWrapper):                  pass              del cur_frame +            if has_logger: +                logger.debug('(%.3f) %s; args=%s' % (duration, sql, params), +                    extra={'duration':duration, 'sql':sql, 'params':params} +                ) +              # We keep `sql` to maintain backwards compatibility              self.db.queries.append({                  'sql': self.db.ops.last_executed_query(self.cursor, sql, params), @@ -139,16 +220,27 @@ class SQLDebugPanel(DebugPanel):      def __init__(self, *args, **kwargs):          super(self.__class__, self).__init__(*args, **kwargs) -        self._offset = len(connection.queries) +        self._offset = dict((conn, len(connections[conn].queries)) for conn in connections)          self._sql_time = 0          self._queries = [] +        self._databases = {}      def nav_title(self):          return _('SQL')      def nav_subtitle(self): -        self._queries = connection.queries[self._offset:] -        self._sql_time = sum([q['duration'] for q in self._queries]) +        self._queries = [] +        self._databases = {} +        for alias in connections: +            db_queries = connections[alias].queries[self._offset[alias]:] +            self._databases[alias] = { +                'time_spent': sum(q['duration'] for q in db_queries), +                'queries': len(db_queries), +            } +            self._queries.extend([(alias, q) for q in db_queries]) + +        self._queries.sort(key=lambda x: x[1]['start_time']) +        self._sql_time = sum([d['time_spent'] for d in self._databases.itervalues()])          num_queries = len(self._queries)          # TODO l10n: use ngettext          return "%d %s in %.2fms" % ( @@ -165,7 +257,8 @@ class SQLDebugPanel(DebugPanel):      def content(self):          width_ratio_tally = 0 -        for query in self._queries: +        for alias, query in self._queries: +            query['alias'] = alias              query['sql'] = reformat_sql(query['sql'])              try:                  query['width_ratio'] = (query['duration'] / self._sql_time) * 100 @@ -176,7 +269,8 @@ class SQLDebugPanel(DebugPanel):          context = self.context.copy()          context.update({ -            'queries': self._queries, +            '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,              'is_mysql': settings.DATABASE_ENGINE == 'mysql',          }) | 
