diff options
Diffstat (limited to 'debug_toolbar/utils')
| -rw-r--r-- | debug_toolbar/utils/__init__.py | 68 | ||||
| -rw-r--r-- | debug_toolbar/utils/compat/__init__.py | 0 | ||||
| -rw-r--r-- | debug_toolbar/utils/compat/db.py | 6 | ||||
| -rw-r--r-- | debug_toolbar/utils/tracking/__init__.py | 90 | ||||
| -rw-r--r-- | debug_toolbar/utils/tracking/db.py | 85 |
5 files changed, 249 insertions, 0 deletions
diff --git a/debug_toolbar/utils/__init__.py b/debug_toolbar/utils/__init__.py index e69de29..61bb717 100644 --- a/debug_toolbar/utils/__init__.py +++ b/debug_toolbar/utils/__init__.py @@ -0,0 +1,68 @@ +import os.path +import django +import SocketServer + +from django.conf import settings +from django.views.debug import linebreak_iter + +# Figure out some paths +django_path = os.path.realpath(os.path.dirname(django.__file__)) +socketserver_path = os.path.realpath(os.path.dirname(SocketServer.__file__)) + +def ms_from_timedelta(td): + """ + Given a timedelta object, returns a float representing milliseconds + """ + return (td.seconds * 1000) + (td.microseconds / 1000.0) + +def tidy_stacktrace(strace): + """ + Clean up stacktrace and remove all entries that: + 1. Are part of Django (except contrib apps) + 2. Are part of SocketServer (used by Django's dev server) + 3. Are the last entry (which is part of our stacktracing code) + """ + trace = [] + for s in strace[:-1]: + s_path = os.path.realpath(s[0]) + if getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}).get('HIDE_DJANGO_SQL', True) \ + and django_path in s_path and not 'django/contrib' in s_path: + continue + if socketserver_path in s_path: + continue + trace.append((s[0], s[1], s[2], s[3])) + return trace + +def get_template_info(source, context_lines=3): + line = 0 + upto = 0 + source_lines = [] + before = during = after = "" + + origin, (start, end) = source + template_source = origin.reload() + + for num, next in enumerate(linebreak_iter(template_source)): + if start >= upto and end <= next: + line = num + before = template_source[upto:start] + during = template_source[start:end] + after = template_source[end:next] + source_lines.append((num, template_source[upto:next])) + upto = next + + top = max(1, line - context_lines) + bottom = min(len(source_lines), line + 1 + context_lines) + + context = [] + for num, content in source_lines[top:bottom]: + context.append({ + 'num': num, + 'content': content, + 'highlight': (num == line), + }) + + return { + 'name': origin.name, + 'context': context, + }
\ No newline at end of file diff --git a/debug_toolbar/utils/compat/__init__.py b/debug_toolbar/utils/compat/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/debug_toolbar/utils/compat/__init__.py diff --git a/debug_toolbar/utils/compat/db.py b/debug_toolbar/utils/compat/db.py new file mode 100644 index 0000000..f3b37e6 --- /dev/null +++ b/debug_toolbar/utils/compat/db.py @@ -0,0 +1,6 @@ +try: + from django.db import connections +except ImportError: + # Compat with < Django 1.2 + from django.db import connection + connections = {'default': connection}
\ No newline at end of file diff --git a/debug_toolbar/utils/tracking/__init__.py b/debug_toolbar/utils/tracking/__init__.py new file mode 100644 index 0000000..db8ff18 --- /dev/null +++ b/debug_toolbar/utils/tracking/__init__.py @@ -0,0 +1,90 @@ +import logging +import time +import types + +def post_dispatch(func): + def wrapped(callback): + register_hook(func, 'after', callback) + return callback + return wrapped + +def pre_dispatch(func): + def wrapped(callback): + register_hook(func, 'before', callback) + return callback + return wrapped + +def replace_call(func): + def inner(callback): + def wrapped(*args, **kwargs): + return callback(func, *args, **kwargs) + + actual = getattr(func, '__wrapped__', func) + wrapped.__wrapped__ = actual + wrapped.__doc__ = getattr(actual, '__doc__', None) + wrapped.__name__ = actual.__name__ + + _replace_function(func, wrapped) + return wrapped + return inner + +def fire_hook(hook, sender, **kwargs): + try: + for callback in callbacks[hook].get(id(sender), []): + callback(sender=sender, **kwargs) + except Exception, e: + # Log the exception, dont mess w/ the underlying function + logging.exception(e) + +def _replace_function(func, wrapped): + if isinstance(func, types.FunctionType): + if func.__module__ == '__builtin__': + # oh shit + __builtins__[func] = wrapped + else: + module = __import__(func.__module__, {}, {}, [func.__module__], 0) + setattr(module, func.__name__, wrapped) + elif getattr(func, 'im_self', None): + # TODO: classmethods + raise NotImplementedError + elif hasattr(func, 'im_class'): + # for unbound methods + setattr(func.im_class, func.__name__, wrapped) + else: + raise NotImplementedError + +callbacks = { + 'before': {}, + 'after': {}, +} + +def register_hook(func, hook, callback): + """ + def myhook(sender, args, kwargs): + print func, "executed + print "args:", args + print "kwargs:", kwargs + register_hook(BaseDatabaseWrapper.cursor, 'before', myhook) + """ + + assert hook in ('before', 'after') + + def wrapped(*args, **kwargs): + start = time.time() + fire_hook('before', sender=wrapped.__wrapped__, args=args, kwargs=kwargs, + start=start) + result = wrapped.__wrapped__(*args, **kwargs) + stop = time.time() + fire_hook('after', sender=wrapped.__wrapped__, args=args, kwargs=kwargs, + result=result, start=start, stop=stop) + actual = getattr(func, '__wrapped__', func) + wrapped.__wrapped__ = actual + wrapped.__doc__ = getattr(actual, '__doc__', None) + wrapped.__name__ = actual.__name__ + + id_ = id(actual) + if id_ not in callbacks[hook]: + callbacks[hook][id_] = [] + callbacks[hook][id_].append(callback) + + _replace_function(func, wrapped)
\ No newline at end of file diff --git a/debug_toolbar/utils/tracking/db.py b/debug_toolbar/utils/tracking/db.py new file mode 100644 index 0000000..4c9ee53 --- /dev/null +++ b/debug_toolbar/utils/tracking/db.py @@ -0,0 +1,85 @@ +import sys +import traceback + +from datetime import datetime + +from django.conf import settings +from django.template import Node +from django.utils import simplejson +from django.utils.encoding import force_unicode +from django.utils.hashcompat import sha_constructor + +from debug_toolbar.utils import ms_from_timedelta, tidy_stacktrace, get_template_info + +# TODO:This should be set in the toolbar loader as a default and panels should +# get a copy of the toolbar object with access to its config dictionary +SQL_WARNING_THRESHOLD = getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}) \ + .get('SQL_WARNING_THRESHOLD', 500) + +class CursorWrapper(object): + """ + Wraps a cursor and logs queries. + """ + + def __init__(self, cursor, db, logger): + self.cursor = cursor + # Instance of a BaseDatabaseWrapper subclass + self.db = db + # logger must implement a ``record`` method + self.logger = logger + + def execute(self, sql, params=()): + start = datetime.now() + try: + return self.cursor.execute(sql, params) + finally: + stop = datetime.now() + 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 + + # We keep `sql` to maintain backwards compatibility + self.logger.record(**{ + 'alias': getattr(self, 'alias', 'default'), + '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, param_list): + return self.cursor.executemany(sql, param_list) + + def __getattr__(self, attr): + if attr in self.__dict__: + return self.__dict__[attr] + else: + return getattr(self.cursor, attr) + + def __iter__(self): + return iter(self.cursor)
\ No newline at end of file |
