runprofileserver.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. # -*- coding: utf-8 -*-
  2. """
  3. runprofileserver.py
  4. Starts a lightweight Web server with profiling enabled.
  5. Credits for kcachegrind support taken from lsprofcalltree.py go to:
  6. David Allouche
  7. Jp Calderone & Itamar Shtull-Trauring
  8. Johan Dahlin
  9. """
  10. import sys
  11. from datetime import datetime
  12. from django.conf import settings
  13. from django.contrib.staticfiles.handlers import StaticFilesHandler
  14. from django.core.management.base import BaseCommand, CommandError
  15. from django.core.servers.basehttp import get_internal_wsgi_application
  16. from django_extensions.management.utils import signalcommand
  17. USE_STATICFILES = 'django.contrib.staticfiles' in settings.INSTALLED_APPS
  18. class KCacheGrind:
  19. def __init__(self, profiler):
  20. self.data = profiler.getstats()
  21. self.out_file = None
  22. def output(self, out_file):
  23. self.out_file = out_file
  24. self.out_file.write('events: Ticks\n')
  25. self._print_summary()
  26. for entry in self.data:
  27. self._entry(entry)
  28. def _print_summary(self):
  29. max_cost = 0
  30. for entry in self.data:
  31. totaltime = int(entry.totaltime * 1000)
  32. max_cost = max(max_cost, totaltime)
  33. self.out_file.write('summary: %d\n' % (max_cost,))
  34. def _entry(self, entry):
  35. out_file = self.out_file
  36. code = entry.code
  37. if isinstance(code, str):
  38. out_file.write('fn=%s\n' % code)
  39. else:
  40. out_file.write('fl=%s\n' % code.co_filename)
  41. out_file.write('fn=%s\n' % code.co_name)
  42. inlinetime = int(entry.inlinetime * 1000)
  43. if isinstance(code, str):
  44. out_file.write('0 %s\n' % inlinetime)
  45. else:
  46. out_file.write('%d %d\n' % (code.co_firstlineno, inlinetime))
  47. # recursive calls are counted in entry.calls
  48. if entry.calls:
  49. calls = entry.calls
  50. else:
  51. calls = []
  52. if isinstance(code, str):
  53. lineno = 0
  54. else:
  55. lineno = code.co_firstlineno
  56. for subentry in calls:
  57. self._subentry(lineno, subentry)
  58. out_file.write("\n")
  59. def _subentry(self, lineno, subentry):
  60. out_file = self.out_file
  61. code = subentry.code
  62. if isinstance(code, str):
  63. out_file.write('cfn=%s\n' % code)
  64. out_file.write('calls=%d 0\n' % (subentry.callcount,))
  65. else:
  66. out_file.write('cfl=%s\n' % code.co_filename)
  67. out_file.write('cfn=%s\n' % code.co_name)
  68. out_file.write('calls=%d %d\n' % (subentry.callcount, code.co_firstlineno))
  69. totaltime = int(subentry.totaltime * 1000)
  70. out_file.write('%d %d\n' % (lineno, totaltime))
  71. class Command(BaseCommand):
  72. help = "Starts a lightweight Web server with profiling enabled."
  73. args = '[optional port number, or ipaddr:port]'
  74. def add_arguments(self, parser):
  75. super().add_arguments(parser)
  76. parser.add_argument(
  77. 'addrport', nargs='?',
  78. help='Optional port number, or ipaddr:port'
  79. )
  80. parser.add_argument(
  81. '--noreload', action='store_false', dest='use_reloader',
  82. default=True,
  83. help='Tells Django to NOT use the auto-reloader.')
  84. parser.add_argument(
  85. '--nothreading', action='store_false', dest='use_threading', default=True,
  86. help='Tells Django to NOT use threading.',
  87. )
  88. parser.add_argument(
  89. '--prof-path', dest='prof_path', default='/tmp',
  90. help='Specifies the directory which to save profile information '
  91. 'in.'
  92. )
  93. parser.add_argument(
  94. '--prof-file', dest='prof_file',
  95. default='{path}.{duration:06d}ms.{time}',
  96. help='Set filename format, default if '
  97. '"{path}.{duration:06d}ms.{time}".'
  98. )
  99. parser.add_argument(
  100. '--nomedia', action='store_true', dest='no_media', default=False,
  101. help='Do not profile MEDIA_URL'
  102. )
  103. parser.add_argument(
  104. '--use-cprofile', action='store_true', dest='use_cprofile',
  105. default=False,
  106. help='Use cProfile if available, this is disabled per default '
  107. 'because of incompatibilities.'
  108. )
  109. parser.add_argument(
  110. '--kcachegrind', action='store_true', dest='use_lsprof',
  111. default=False,
  112. help='Create kcachegrind compatible lsprof files, this requires '
  113. 'and automatically enables cProfile.'
  114. )
  115. if USE_STATICFILES:
  116. parser.add_argument(
  117. '--nostatic', action="store_false", dest='use_static_handler',
  118. default=True,
  119. help='Tells Django to NOT automatically serve static files '
  120. 'at STATIC_URL.')
  121. parser.add_argument(
  122. '--insecure', action="store_true", dest='insecure_serving',
  123. default=False,
  124. help='Allows serving static files even if DEBUG is False.')
  125. @signalcommand
  126. def handle(self, addrport='', *args, **options):
  127. import django
  128. import socket
  129. import errno
  130. from django.core.servers.basehttp import run
  131. if not addrport:
  132. addr = ''
  133. port = '8000'
  134. else:
  135. try:
  136. addr, port = addrport.split(':')
  137. except ValueError:
  138. addr, port = '', addrport
  139. if not addr:
  140. addr = '127.0.0.1'
  141. if not port.isdigit():
  142. raise CommandError("%r is not a valid port number." % port)
  143. use_reloader = options['use_reloader']
  144. shutdown_message = options.get('shutdown_message', '')
  145. no_media = options['no_media']
  146. quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
  147. def inner_run():
  148. import os
  149. import time
  150. try:
  151. import hotshot
  152. HAS_HOTSHOT = True
  153. except ImportError:
  154. HAS_HOTSHOT = False # python 3.x
  155. USE_CPROFILE = options['use_cprofile']
  156. USE_LSPROF = options['use_lsprof']
  157. if USE_LSPROF:
  158. USE_CPROFILE = True
  159. if USE_CPROFILE:
  160. try:
  161. import cProfile
  162. USE_CPROFILE = True
  163. except ImportError:
  164. print("cProfile disabled, module cannot be imported!")
  165. USE_CPROFILE = False
  166. if USE_LSPROF and not USE_CPROFILE:
  167. raise CommandError("Kcachegrind compatible output format required cProfile from Python 2.5")
  168. if not HAS_HOTSHOT and not USE_CPROFILE:
  169. raise CommandError("Hotshot profile library not found. (and not using cProfile)")
  170. prof_path = options['prof_path']
  171. prof_file = options['prof_file']
  172. if not prof_file.format(path='1', duration=2, time=3):
  173. prof_file = '{path}.{duration:06d}ms.{time}'
  174. print("Filename format is wrong. Default format used: '{path}.{duration:06d}ms.{time}'.")
  175. def get_exclude_paths():
  176. exclude_paths = []
  177. media_url = getattr(settings, 'MEDIA_URL', None)
  178. if media_url:
  179. exclude_paths.append(media_url)
  180. static_url = getattr(settings, 'STATIC_URL', None)
  181. if static_url:
  182. exclude_paths.append(static_url)
  183. return exclude_paths
  184. def make_profiler_handler(inner_handler):
  185. def handler(environ, start_response):
  186. path_info = environ['PATH_INFO']
  187. # when using something like a dynamic site middleware is could be necessary
  188. # to refetch the exclude_paths every time since they could change per site.
  189. if no_media and any(path_info.startswith(p) for p in get_exclude_paths()):
  190. return inner_handler(environ, start_response)
  191. path_name = path_info.strip("/").replace('/', '.') or "root"
  192. profname = "%s.%d.prof" % (path_name, time.time())
  193. profname = os.path.join(prof_path, profname)
  194. if USE_CPROFILE:
  195. prof = cProfile.Profile()
  196. else:
  197. prof = hotshot.Profile(profname)
  198. start = datetime.now()
  199. try:
  200. return prof.runcall(inner_handler, environ, start_response)
  201. finally:
  202. # seeing how long the request took is important!
  203. elap = datetime.now() - start
  204. elapms = elap.seconds * 1000.0 + elap.microseconds / 1000.0
  205. if USE_LSPROF:
  206. kg = KCacheGrind(prof)
  207. with open(profname, 'w') as f:
  208. kg.output(f)
  209. elif USE_CPROFILE:
  210. prof.dump_stats(profname)
  211. profname2 = prof_file.format(path=path_name, duration=int(elapms), time=int(time.time()))
  212. profname2 = os.path.join(prof_path, "%s.prof" % profname2)
  213. if not USE_CPROFILE:
  214. prof.close()
  215. os.rename(profname, profname2)
  216. return handler
  217. print("Validating models...")
  218. if hasattr(self, 'check'):
  219. self.check(display_num_errors=True)
  220. else:
  221. self.validate(display_num_errors=True)
  222. print("\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE))
  223. print("Development server is running at http://%s:%s/" % (addr, port))
  224. print("Quit the server with %s." % quit_command)
  225. try:
  226. handler = get_internal_wsgi_application()
  227. if USE_STATICFILES:
  228. use_static_handler = options['use_static_handler']
  229. insecure_serving = options['insecure_serving']
  230. if use_static_handler and (settings.DEBUG or insecure_serving):
  231. handler = StaticFilesHandler(handler)
  232. handler = make_profiler_handler(handler)
  233. run(addr, int(port), handler, threading=options['use_threading'])
  234. except socket.error as e:
  235. # Use helpful error messages instead of ugly tracebacks.
  236. ERRORS = {
  237. errno.EACCES: "You don't have permission to access that port.",
  238. errno.EADDRINUSE: "That port is already in use.",
  239. errno.EADDRNOTAVAIL: "That IP address can't be assigned-to.",
  240. }
  241. try:
  242. error_text = ERRORS[e.errno]
  243. except (AttributeError, KeyError):
  244. error_text = str(e)
  245. sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n')
  246. # Need to use an OS exit because sys.exit doesn't work in a thread
  247. os._exit(1)
  248. except KeyboardInterrupt:
  249. if shutdown_message:
  250. print(shutdown_message)
  251. sys.exit(0)
  252. if use_reloader:
  253. try:
  254. from django.utils.autoreload import run_with_reloader
  255. run_with_reloader(inner_run)
  256. except ImportError:
  257. from django.utils import autoreload
  258. autoreload.main(inner_run)
  259. else:
  260. inner_run()