profilemiddleware.py 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. # http://www.no-ack.org/2010/12/yet-another-profiling-middleware-for.html
  2. import os
  3. import re
  4. import tempfile
  5. from cStringIO import StringIO
  6. from django.conf import settings
  7. import hotshot
  8. import hotshot.stats
  9. COMMENT_SYNTAX = ((re.compile(r'^application/(.*\+)?xml|text/html$', re.I), '<!--', '-->'),
  10. (re.compile(r'^application/j(avascript|son)$', re.I), '/*', '*/'))
  11. class ProfileMiddleware(object):
  12. def process_view(self, request, callback, args, kwargs):
  13. # Create a profile, writing into a temporary file.
  14. filename = tempfile.mktemp()
  15. profile = hotshot.Profile(filename)
  16. try:
  17. try:
  18. # Profile the call of the view function.
  19. response = profile.runcall(callback, request, *args, **kwargs)
  20. # If we have got a 3xx status code, further
  21. # action needs to be taken by the user agent
  22. # in order to fulfill the request. So don't
  23. # attach any stats to the content, because of
  24. # the content is supposed to be empty and is
  25. # ignored by the user agent.
  26. if response.status_code // 100 == 3:
  27. return response
  28. # Detect the appropriate syntax based on the
  29. # Content-Type header.
  30. for regex, begin_comment, end_comment in COMMENT_SYNTAX:
  31. if regex.match(response['Content-Type'].split(';')[0].strip()):
  32. break
  33. else:
  34. # If the given Content-Type is not
  35. # supported, don't attach any stats to
  36. # the content and return the unchanged
  37. # response.
  38. return response
  39. # The response can hold an iterator, that
  40. # is executed when the content property
  41. # is accessed. So we also have to profile
  42. # the call of the content property.
  43. content = profile.runcall(response.__class__.content.fget, response)
  44. finally:
  45. profile.close()
  46. # Load the stats from the temporary file and
  47. # write them in a human readable format,
  48. # respecting some optional settings into a
  49. # StringIO object.
  50. stats = hotshot.stats.load(filename)
  51. if getattr(settings, 'PROFILE_MIDDLEWARE_STRIP_DIRS', False):
  52. stats.strip_dirs()
  53. if getattr(settings, 'PROFILE_MIDDLEWARE_SORT', None):
  54. stats.sort_stats(*settings.PROFILE_MIDDLEWARE_SORT)
  55. stats.stream = StringIO()
  56. stats.print_stats(*getattr(settings, 'PROFILE_MIDDLEWARE_RESTRICTIONS', []))
  57. finally:
  58. os.unlink(filename)
  59. # Construct an HTML/XML or Javascript comment, with
  60. # the formatted stats, written to the StringIO object
  61. # and attach it to the content of the response.
  62. comment = '\n%s\n\n%s\n\n%s\n' % (begin_comment, stats.stream.getvalue().strip(), end_comment)
  63. response.content = content + comment
  64. # If the Content-Length header is given, add the
  65. # number of bytes we have added to it. If the
  66. # Content-Length header is ommited or incorrect,
  67. # it remains so in order to don't change the
  68. # behaviour of the web server or user agent.
  69. if response.has_header('Content-Length'):
  70. response['Content-Length'] = int(response['Content-Length']) + len(comment)
  71. return response