Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
from debug_toolbar.panels import Panel
from django.conf import settings
from django.template import base as template_base
from django.template import Template, Library
from django.template.loader_tags import BlockNode
from debug_toolbar.panels import sql
from django.core.exceptions import ImproperlyConfigured
import threading
import functools
import time
import re
import collections
import logging
logger = logging.getLogger(__name__)
# A set of nodes found by the tag_compiler. Used to patch during next request
# and to unpatch the nodes.
FOUND_GENERIC_NODES = set()
if not "debug_toolbar.panels.sql.SQLPanel" in settings.DEBUG_TOOLBAR_PANELS:
raise ImproperlyConfigured("debug_toolbar.panels.sql.SQLPanel must be present in DEBUG_TOOLBAR_PANELS")
def replace_method(klass, method_name):
original = getattr(klass, method_name)
def inner(callback):
def wrapped(*args, **kwargs):
return callback(original, *args, **kwargs)
actual = getattr(original, '__wrapped__', original)
wrapped.__wrapped__ = actual
wrapped.__doc__ = getattr(actual, '__doc__', None)
wrapped.__name__ = actual.__name__
setattr(klass, method_name, wrapped)
return wrapped
return inner
def record_query(**kwargs):
if hasattr(results, "_current_template"):
if not results._current_key in results.timings or \
not results._current_template in results.timings[results._current_key]:
return
part = results.timings[results._current_key][results._current_template]
part["queries"] += 1
part["query_duration"] += kwargs["duration"]
logger.debug("Template: %s executed query %s" % (results._current_template, kwargs["raw_sql"]))
@replace_method(sql.SQLPanel, "record")
def record(func, self, **kwargs):
record_query(**kwargs)
return func(self, **kwargs)
results = threading.local()
TEMPLATE_TIMINGS_SETTINGS = {
'IGNORED_TEMPLATES': ["debug_toolbar/*"]
}
for k in TEMPLATE_TIMINGS_SETTINGS.keys():
if hasattr(settings, k):
TEMPLATE_TIMINGS_SETTINGS[k] = getattr(settings, k)
def _tag_compiler(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if 'node_class' in kwargs:
wrap_generic_node(kwargs['node_class'], kwargs['name'])
FOUND_GENERIC_NODES.add((kwargs['node_class'], kwargs['name']))
return func(*args, **kwargs)
return wrapper
def wrap_generic_node(node, name):
if not hasattr(node.render, 'original'):
node.render = _template_render_wrapper(
node.render, node.__name__, name=lambda unused_: name)
try:
template_base.generic_tag_compiler = _tag_compiler(template_base.generic_tag_compiler)
except:
Library.simple_tag = _tag_compiler(Library.simple_tag)
def _template_render_wrapper(func, key, should_add=lambda n: True, name=lambda s: s.name if s.name else ''):
@functools.wraps(func)
def timing_hook(self, *args, **kwargs):
# Set up our thread-local results cache
if not hasattr(results, "timings"):
results.timings = collections.defaultdict(dict)
# This is like a stack. It gets incremented before a template is rendered and decremented when
# its finished rendering, because this function is recursive. So if it is 1 then this is the first
# template being rendered, and its total execution time encompasses all of the sub-templates rendering
# time. We use this to display the rendering time on the sidebar
results._count = 0
# These two values are used with the SQL counter. We store the current key and template name
# so that the _logger can get to the current template being rendered.
results._current_template = name(self)
results._current_key = key
name_self = name(self)
# Issue #11, sometimes for some reason accessing results.timings causes a KeyError.
if key not in results.timings:
results.timings[key] = {}
if name_self not in results.timings[key] and should_add(name_self):
results.timings.setdefault(key, {})[name_self] = {
'count': 0,
'min': 0,
'max': 0,
'total': 0,
'avg': 0,
'is_base': False,
'queries': 0,
'query_duration': 0
}
results._count += 1
_old_current = results._current_template
_old_key = results._current_key
results._current_template = name_self
results._current_key = key
start_time = time.time()
result = func(self, *args, **kwargs)
time_taken = (time.time() - start_time) * 1000.0
results._current_template = _old_current
results._current_key = _old_key
if should_add(name_self):
results_part = results.timings[key][name_self]
results_part["min"] = min(time_taken, results_part["min"])
results_part["max"] = max(time_taken, results_part["max"])
results_part['count'] += 1
results_part['total'] += time_taken
results_part['avg'] = results_part['total'] / results_part['count']
results_part["is_base"] = results._count == 1
if results_part["queries"] > 0:
try:
results_part["sql_percentage"] = "%.2f%%" % ((float(results_part["query_duration"]) /
float(results_part["total"])) * 100)
except ZeroDivisionError:
results_part["sql_percentage"] = "0%"
logger.debug("%s %s took %.1f" % (key, name_self, time_taken))
results._count -= 1
return result
timing_hook.original = func
return timing_hook
class TemplateTimings(Panel):
name = "TemplateTimingPanel"
template = "debug_toolbar_template_timings.html"
title = 'Template Timings'
has_content = True
def _get_timings(self):
return getattr(results, "timings", collections.defaultdict(dict))
def _results_to_list(self, results):
returner = {}
for key, value in results.items():
returner[key] = []
for name, timings in value.items():
new_timings = {"name": name}
new_timings.update(timings)
returner[key].append(new_timings)
return returner
@property
def nav_title(self):
return 'Template Timings'
def enable_instrumentation(self):
Template.render = _template_render_wrapper(Template.render, "templates",
lambda n: not any([re.match(pattern, n)
for pattern in TEMPLATE_TIMINGS_SETTINGS["IGNORED_TEMPLATES"]]))
BlockNode.render = _template_render_wrapper(BlockNode.render, "blocks")
# Wrap the nodes which were found by earlier requests
for node, name in FOUND_GENERIC_NODES:
wrap_generic_node(node, name)
def disable_instrumentation(self):
if hasattr(Template.render, "original"):
Template.render = Template.render.original
BlockNode.render = BlockNode.render.original
for node, name in FOUND_GENERIC_NODES:
if hasattr(node.render, 'original'):
node.render = node.render.original
@property
def nav_subtitle(self):
results = self._get_timings()
total_template_queries = sum(
sum(results[name][template_name]["queries"] for template_name in results[name])
for name in results
)
total_template_query_time = sum(
sum(results[name][template_name]["query_duration"] for template_name in results[name])
for name in results
)
base_template = list(filter(lambda i: results["templates"][i]["is_base"] == True, results["templates"].keys()))
if not len(base_template) == 1:
logger.info("Found more than one base template: %s" % str(base_template))
else:
base_template = base_template[0]
base_time = results["templates"][base_template]["total"]
query_percentage_time = ""
if total_template_query_time > 0 and base_time > 0:
query_percentage_time = "(%.2f%% SQL)" % ((float(total_template_query_time) / float(base_time)) * 100)
return "%.0f ms with %s queries %s" % (base_time, total_template_queries, query_percentage_time)
def process_response(self, request, response):
timings = self._get_timings()
# Setting default_factory to None allows us to access
# template_timings.iteritems in the template.
if timings is not None:
timings.default_factory = None
timings = self._results_to_list(timings)
self.record_stats({"template_timings": timings})