diff options
| author | Aymeric Augustin | 2013-11-15 22:43:59 +0100 |
|---|---|---|
| committer | Aymeric Augustin | 2013-11-15 22:43:59 +0100 |
| commit | 6334983458abd4380c21275d1229527778cf93a6 (patch) | |
| tree | 1b3e9304f93600fa7b38bfa8a6cc20b3857d7375 /debug_toolbar/panels/sql.py | |
| parent | f0d0ddbada065ec0ff4fc64aed9d2f9ba48ba5a3 (diff) | |
| download | django-debug-toolbar-6334983458abd4380c21275d1229527778cf93a6.tar.bz2 | |
Continue moving panel-specific code within panels.
Structure the SQL and template panels as packages as they're growing.
Diffstat (limited to 'debug_toolbar/panels/sql.py')
| -rw-r--r-- | debug_toolbar/panels/sql.py | 319 |
1 files changed, 0 insertions, 319 deletions
diff --git a/debug_toolbar/panels/sql.py b/debug_toolbar/panels/sql.py deleted file mode 100644 index 226778b..0000000 --- a/debug_toolbar/panels/sql.py +++ /dev/null @@ -1,319 +0,0 @@ -from __future__ import unicode_literals - -import uuid -from copy import copy - -from django.conf.urls import patterns, url -from django.db import connections -from django.http import HttpResponseBadRequest -from django.shortcuts import render -from django.utils.translation import ugettext_lazy as _, ungettext_lazy as __ -from django.views.decorators.csrf import csrf_exempt - -from debug_toolbar.forms import SQLSelectForm -from debug_toolbar.panels import DebugPanel -from debug_toolbar.utils import render_stacktrace -from debug_toolbar.utils.sql import reformat_sql -from debug_toolbar.utils.tracking.db import CursorWrapper - - -def get_isolation_level_display(engine, level): - if engine == 'psycopg2': - import psycopg2.extensions - choices = { - psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT: _('Autocommit'), - psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED: _('Read uncommitted'), - psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED: _('Read committed'), - psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ: _('Repeatable read'), - psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE: _('Serializable'), - } - else: - raise ValueError(engine) - return choices.get(level) - - -def get_transaction_status_display(engine, level): - if engine == 'psycopg2': - import psycopg2.extensions - choices = { - psycopg2.extensions.TRANSACTION_STATUS_IDLE: _('Idle'), - psycopg2.extensions.TRANSACTION_STATUS_ACTIVE: _('Active'), - psycopg2.extensions.TRANSACTION_STATUS_INTRANS: _('In transaction'), - psycopg2.extensions.TRANSACTION_STATUS_INERROR: _('In error'), - psycopg2.extensions.TRANSACTION_STATUS_UNKNOWN: _('Unknown'), - } - else: - raise ValueError(engine) - 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(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 - self._queries = [] - self._databases = {} - self._transaction_status = {} - self._transaction_ids = {} - - def get_transaction_id(self, alias): - if alias not in connections: - return - conn = connections[alias].connection - if not conn: - return - - 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 - else: - self._transaction_ids[alias] = None - - return self._transaction_ids[alias] - - def record(self, alias, **kwargs): - self._queries.append((alias, kwargs)) - if alias not in self._databases: - self._databases[alias] = { - 'time_spent': kwargs['duration'], - 'num_queries': 1, - } - else: - self._databases[alias]['time_spent'] += kwargs['duration'] - self._databases[alias]['num_queries'] += 1 - self._sql_time += kwargs['duration'] - self._num_queries += 1 - - @classmethod - def get_urls(cls): - return patterns('debug_toolbar.panels.sql', # noqa - url(r'^sql_select/$', 'sql_select', name='sql_select'), - url(r'^sql_explain/$', 'sql_explain', name='sql_explain'), - url(r'^sql_profile/$', 'sql_profile', name='sql_profile'), - ) - - def nav_title(self): - return _('SQL') - - def nav_subtitle(self): - return __("%d query in %.2fms", "%d queries in %.2fms", - self._num_queries) % (self._num_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 enable_instrumentation(self): - # This is thread-safe because database connections are thread-local. - for connection in connections.all(): - if not hasattr(connection, '_djdt_cursor'): - connection._djdt_cursor = connection.cursor - connection.cursor = lambda: CursorWrapper( - connection._djdt_cursor(), connection, self) - - def disable_instrumentation(self): - for connection in connections.all(): - if hasattr(connection, '_djdt_cursor'): - del connection._djdt_cursor - del connection.cursor - - def process_response(self, request, response): - if self._queries: - width_ratio_tally = 0 - factor = int(256.0 / (len(self._databases) * 2.5)) - for n, db in enumerate(self._databases.values()): - rgb = [0, 0, 0] - color = n % 3 - rgb[color] = 256 - n / 3 * factor - nn = color - # XXX: pretty sure this is horrible after so many aliases - while rgb[color] < factor: - nc = min(256 - rgb[color], 256) - rgb[color] += nc - nn += 1 - if nn > 2: - nn = 0 - rgb[nn] = nc - db['rgb_color'] = rgb - - trans_ids = {} - trans_id = None - i = 0 - for alias, query in self._queries: - trans_id = query.get('trans_id') - last_trans_id = trans_ids.get(alias) - - if trans_id != last_trans_id: - if last_trans_id: - self._queries[(i - 1)][1]['ends_trans'] = True - trans_ids[alias] = trans_id - if trans_id: - query['starts_trans'] = True - if trans_id: - query['in_trans'] = True - - query['alias'] = alias - if 'iso_level' in query: - query['iso_level'] = get_isolation_level_display(query['engine'], - query['iso_level']) - if 'trans_status' in query: - query['trans_status'] = get_transaction_status_display(query['engine'], - query['trans_status']) - - query['form'] = SQLSelectForm(auto_id=None, initial=copy(query)) - - if query['sql']: - query['sql'] = reformat_sql(query['sql']) - query['rgb_color'] = self._databases[alias]['rgb_color'] - try: - query['width_ratio'] = (query['duration'] / self._sql_time) * 100 - query['width_ratio_relative'] = ( - 100.0 * query['width_ratio'] / (100.0 - width_ratio_tally)) - except ZeroDivisionError: - query['width_ratio'] = 0 - query['width_ratio_relative'] = 0 - query['start_offset'] = width_ratio_tally - query['end_offset'] = query['width_ratio'] + query['start_offset'] - width_ratio_tally += query['width_ratio'] - query['stacktrace'] = render_stacktrace(query['stacktrace']) - i += 1 - - if trans_id: - self._queries[(i - 1)][1]['ends_trans'] = True - - 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, - }) - - -@csrf_exempt -def sql_select(request): - """Returns the output of the SQL SELECT statement""" - form = SQLSelectForm(request.POST or None) - - if form.is_valid(): - sql = form.cleaned_data['raw_sql'] - params = form.cleaned_data['params'] - cursor = form.cursor - cursor.execute(sql, params) - headers = [d[0] for d in cursor.description] - result = cursor.fetchall() - cursor.close() - context = { - 'result': result, - 'sql': form.reformat_sql(), - 'duration': form.cleaned_data['duration'], - 'headers': headers, - 'alias': form.cleaned_data['alias'], - } - return render(request, 'debug_toolbar/panels/sql_select.html', context) - return HttpResponseBadRequest('Form errors') - - -@csrf_exempt -def sql_explain(request): - """Returns the output of the SQL EXPLAIN on the given query""" - form = SQLSelectForm(request.POST or None) - - if form.is_valid(): - sql = form.cleaned_data['raw_sql'] - params = form.cleaned_data['params'] - cursor = form.cursor - - conn = form.connection - engine = conn.__class__.__module__.split('.', 1)[0] - - if engine == "sqlite3": - # SQLite's EXPLAIN dumps the low-level opcodes generated for a query; - # EXPLAIN QUERY PLAN dumps a more human-readable summary - # See http://www.sqlite.org/lang_explain.html for details - cursor.execute("EXPLAIN QUERY PLAN %s" % (sql,), params) - elif engine == "psycopg2": - cursor.execute("EXPLAIN ANALYZE %s" % (sql,), params) - else: - cursor.execute("EXPLAIN %s" % (sql,), params) - - headers = [d[0] for d in cursor.description] - result = cursor.fetchall() - cursor.close() - context = { - 'result': result, - 'sql': form.reformat_sql(), - 'duration': form.cleaned_data['duration'], - 'headers': headers, - 'alias': form.cleaned_data['alias'], - } - return render(request, 'debug_toolbar/panels/sql_explain.html', context) - return HttpResponseBadRequest('Form errors') - - -@csrf_exempt -def sql_profile(request): - """Returns the output of running the SQL and getting the profiling statistics""" - form = SQLSelectForm(request.POST or None) - - if form.is_valid(): - sql = form.cleaned_data['raw_sql'] - params = form.cleaned_data['params'] - cursor = form.cursor - result = None - headers = None - result_error = None - try: - cursor.execute("SET PROFILING=1") # Enable profiling - cursor.execute(sql, params) # Execute SELECT - cursor.execute("SET PROFILING=0") # Disable profiling - # The Query ID should always be 1 here but I'll subselect to get - # the last one just in case... - cursor.execute(""" - SELECT * - FROM information_schema.profiling - WHERE query_id = ( - SELECT query_id - FROM information_schema.profiling - ORDER BY query_id DESC - LIMIT 1 - ) -""") - headers = [d[0] for d in cursor.description] - result = cursor.fetchall() - except Exception: - result_error = "Profiling is either not available or not supported by your database." - cursor.close() - context = { - 'result': result, - 'result_error': result_error, - 'sql': form.reformat_sql(), - 'duration': form.cleaned_data['duration'], - 'headers': headers, - 'alias': form.cleaned_data['alias'], - } - return render(request, 'debug_toolbar/panels/sql_profile.html', context) - return HttpResponseBadRequest('Form errors') |
