Skip to content

Instantly share code, notes, and snippets.

@radupotop
Forked from mmalone/Reviewing profiles
Last active January 9, 2023 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save radupotop/9cae8b0709e4e961524643e23fac59d7 to your computer and use it in GitHub Desktop.
Save radupotop/9cae8b0709e4e961524643e23fac59d7 to your computer and use it in GitHub Desktop.
Replacement Django runserver command that does profiling... because I've rewritten this too many times.
>>> import pstats
>>> p = pstats.Stats('p.1258156459.52174278XcQE.prof')
>>> p.strip_dirs().sort_stats(-1).print_stats(5)
Fri Nov 13 15:54:20 2009 p.1258156459.52174278XcQE.prof
124278 function calls (122386 primitive calls) in 0.589 CPU seconds
Ordered by: standard name
List reduced from 1014 to 5 due to restriction <5>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.003 0.003 0.003 0.003 <string>:1(connect)
1 0.000 0.000 0.000 0.000 <string>:1(getpeername)
1 0.000 0.000 0.000 0.000 <string>:1(gettimeout)
2 0.000 0.000 0.000 0.000 <string>:1(settimeout)
29 0.000 0.000 0.000 0.000 <string>:8(__new__)
<pstats.Stats instance at 0x501260>
>>> p.strip_dirs().sort_stats('cumulative').print_stats(5)
Fri Nov 13 15:54:20 2009 p.1258156459.52174278XcQE.prof
124278 function calls (122386 primitive calls) in 0.589 CPU seconds
Ordered by: cumulative time
List reduced from 1014 to 5 due to restriction <5>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.589 0.589 basehttp.py:644(__call__)
1 0.000 0.000 0.589 0.589 wsgi.py:221(__call__)
1 0.000 0.000 0.580 0.580 base.py:66(get_response)
1 0.000 0.000 0.577 0.577 views.py:72(home)
1 0.000 0.000 0.577 0.577 base.py:311(__init__)
<pstats.Stats instance at 0x501260>
>>> p.strip_dirs().sort_stats('time').print_stats(5)
Fri Nov 13 15:54:20 2009 p.1258156459.52174278XcQE.prof
124278 function calls (122386 primitive calls) in 0.589 CPU seconds
Ordered by: internal time
List reduced from 1014 to 5 due to restriction <5>
ncalls tottime percall cumtime percall filename:lineno(function)
358 0.409 0.001 0.409 0.001 {built-in method read}
4098 0.026 0.000 0.039 0.000 posixpath.py:59(join)
4098 0.025 0.000 0.025 0.000 {posix.lstat}
1 0.015 0.015 0.015 0.015 {built-in method do_handshake}
387 0.012 0.000 0.095 0.000 posixpath.py:344(realpath)
<pstats.Stats instance at 0x501260>
$ ./manage.py runserver --profile --profdir=./profiles/ --noprofmedia
"""
./manage.py runserver --profile --profdir=./profiles/ --noprofmedia
# See http://www.vrplumber.com/programming/runsnakerun/
$ runsnake p.1258156462.721626cTJ6rF.prof
# OR
$ runsnake p.*
"""
import cProfile
import os
import sys
import tempfile
import time
from optparse import make_option
import django
from django.conf import settings
from django.core.handlers.wsgi import WSGIHandler
from django.core.management.base import BaseCommand, CommandError
from django.core.servers.basehttp import (
AdminMediaHandler,
WSGIServerException,
run,
)
from django.utils import autoreload, translation
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option(
'--noreload',
action='store_false',
dest='use_reloader',
default=True,
help='Tells Django to NOT use the auto-reloader.',
),
make_option(
'--adminmedia',
dest='admin_media_path',
default='',
help='Specifies the directory from which to serve admin media.',
),
make_option(
'--profile',
action='store_true',
dest='profile',
help='Enable profiling. Write profiles into system\'s temporary directory',
),
make_option(
'--profdir',
dest='profile_temp_dir',
default=None,
help='Specifies the directory in which to store profile data.',
),
make_option(
'--noprofmedia',
action='store_false',
dest='profile_media',
default=True,
help='Disable profiling for requests if request path starts with settings.MEDIA_URL',
),
)
help = "Starts a lightweight Web server for development."
args = '[optional port number, or ipaddr:port]'
# Validation is called explicitly each time the server is reloaded.
requires_model_validation = False
def handle(self, addrport='', *args, **options):
if args:
raise CommandError('Usage is runserver %s' % self.args)
if not addrport:
addr = ''
port = '8000'
else:
try:
addr, port = addrport.split(':')
except ValueError:
addr, port = '', addrport
if not addr:
addr = '127.0.0.1'
if not port.isdigit():
raise CommandError("%r is not a valid port number." % port)
use_reloader = options.get('use_reloader', True)
admin_media_path = options.get('admin_media_path', '')
shutdown_message = options.get('shutdown_message', '')
profile = options.get('profile', False)
profile_temp_dir = options.get('profile_temp_dir', None)
profile_media = options.get('profile_media', True)
quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
def inner_run():
print("Validating models...")
self.validate(display_num_errors=True)
print(
"\nDjango version %s, using settings %r"
% (django.get_version(), settings.SETTINGS_MODULE)
)
print("Development server is running at http://%s:%s/" % (addr, port))
print("Quit the server with %s." % quit_command)
# django.core.management.base forces the locale to en-us. We should
# set it up correctly for the first request (particularly important
# in the "--noreload" case).
translation.activate(settings.LANGUAGE_CODE)
try:
if profile:
if profile_temp_dir is not None:
tempfile.tempdir = profile_temp_dir
def make_profiler_handler(inner_handler):
def handler(environ, start_response):
path = environ['PATH_INFO']
if not profile_media and path.startswith(
settings.MEDIA_URL
):
return inner_handler(environ, start_response)
path = path.strip('/').replace('/', '.')
if path:
prefix = 'p.%s.%3f' % (path, time.time())
else:
prefix = 'p.%3f' % time.time()
fd, profname = tempfile.mkstemp('.prof', prefix)
os.close(fd)
prof = cProfile.Profile()
try:
return prof.runcall(
inner_handler, environ, start_response
)
finally:
prof.dump_stats(profname)
return handler
handler = make_profiler_handler(
AdminMediaHandler(WSGIHandler(), admin_media_path)
)
else:
handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
run(addr, int(port), handler)
except WSGIServerException as e:
# Use helpful error messages instead of ugly tracebacks.
ERRORS = {
13: "You don't have permission to access that port.",
98: "That port is already in use.",
99: "That IP address can't be assigned-to.",
}
try:
error_text = ERRORS[e.args[0].args[0]]
except (AttributeError, KeyError):
error_text = str(e)
sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n')
# Need to use an OS exit because sys.exit doesn't work in a thread
os._exit(1)
except KeyboardInterrupt:
if shutdown_message:
print(shutdown_message)
sys.exit(0)
if use_reloader:
autoreload.main(inner_run)
else:
inner_run()
# See http://www.vrplumber.com/programming/runsnakerun/
$ runsnake p.1258156462.721626cTJ6rF.prof
# OR
$ runsnake p.*
INSTALLED_APPS += ('profiler',)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment