Skip to content

Instantly share code, notes, and snippets.

@ginogervasio
Last active October 4, 2017 08:40
Show Gist options
  • Save ginogervasio/cb753f65754e171529dc430fbef88565 to your computer and use it in GitHub Desktop.
Save ginogervasio/cb753f65754e171529dc430fbef88565 to your computer and use it in GitHub Desktop.
Updated profiling middleware for Django v1.11 and Python 3.6 using cProfile
"""Original version taken from http://www.djangosnippets.org/snippets/186/.
Original author: udfalkso
Modified by: Shwagroo Team and Gun.io
Further modified by: Jakub Skalecki (https://rock-it.pl)
Updated by: G. Gervasio for (https://baseup.co)
"""
import cProfile
import textwrap
import pstats
import re
from django.conf import settings
from io import StringIO
class ProfileMiddleware(object):
"""
Displays cProfile profiling for any view.
http://yoursite.com/yourview/?prof
Add the "prof" key to query string by appending ?prof (or &prof=)
and you'll see the profiling results in your browser.
It's set up to only be available in django's debug mode, is available for
superuser otherwise.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def should_show_stats(self, request):
is_superuser = hasattr(request, 'user') and request.user.is_superuser
return 'prof' in request.GET and (settings.DEBUG or is_superuser)
def process_view(self, request, callback, callback_args, callback_kwargs):
if self.should_show_stats(request):
# we need temp file to store stats
prof = cProfile.Profile()
prof.enable()
response = prof.runcall(
callback, request, *callback_args, **callback_kwargs).render()
prof.disable()
if response and hasattr(response, 'content') and response.content:
s = StringIO()
stats = pstats.Stats(prof, stream=s)
formatter = ProfileStatsFormatter(
stats, raw_records=200, group_records=50)
response.content = formatter.get_html()
return response
class ProfileStatsFormatter(object):
"""
Helper for formatting cProfile stats to HTML with grouping
"""
DEFAULT_SORTING = ('cumtime', 'calls')
group_prefix_re = [
re.compile("^.*/django/[^/]+"),
re.compile("^(.*)/[^/]+$"), # extract module path
re.compile(".*"), # catch strange entries
]
def __init__(self, stats, sort_by=None, group_records=40, raw_records=100):
self.total_time = 0
self.time_by_file = {}
self.time_by_group = {}
self.num_of_group_records = group_records
self.num_of_records = raw_records
self.stats_str = self._get_stats_string(
stats, sort_by=sort_by or self.DEFAULT_SORTING)
# fill total_time, time_by_file, time_by_group fields
self._preprocess_stats()
@staticmethod
def _get_stats_string(stats, sort_by):
stats.stream = StringIO()
stats.sort_stats(*sort_by)
stats.print_stats()
return stats.stream.getvalue()
def _preprocess_stats(self):
for filename, time in self._iterate_records(self.stats_str):
group = self._get_group(filename)
self.time_by_group[group] = self.time_by_group.get(group, 0) + time
self.time_by_file[filename] = self.time_by_file.get(filename, 0) \
+ time
self.total_time += time
@staticmethod
def _get_group(filename):
for group_prefix in ProfileStatsFormatter.group_prefix_re:
name = group_prefix.findall(filename)
if name:
return name[0]
@staticmethod
def _iterate_records(stats_str):
# first 5 lines contains summary
records = stats_str.split("\n")[5:]
for record in records:
fields = record.split()
if len(fields) == 6:
record_time = float(fields[1])
filename = fields[5].split(":")[0]
if filename:
yield filename, record_time
def _get_summary(self, time_by_name):
time_name = time_by_name.items()
time_name = sorted(time_name, reverse=True, key=lambda x: x[-1])
res = " tottime\n"
for name, time in time_name[:self.num_of_group_records]:
percent = 100 * time / self.total_time if self.total_time else 0
res += "%4.1f%% %7.3f %s\n" % (percent, time, name)
return res
def get_html(self):
raw_stats = '\n'.join(self.stats_str.split('\n')[:self.num_of_records])
format_string = textwrap.dedent("""
{raw_stats}
---- By filename ----
{stats_by_filename}
---- By group ---
{stats_by_group}
""")
return ("<pre >" + format_string + "</pre>").format(
raw_stats=raw_stats,
stats_by_filename=self._get_summary(self.time_by_file),
stats_by_group=self._get_summary(self.time_by_group)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment