profiler.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. """
  2. Application Profiler
  3. ====================
  4. This module provides a middleware that profiles each request with the
  5. :mod:`cProfile` module. This can help identify bottlenecks in your code
  6. that may be slowing down your application.
  7. .. autoclass:: ProfilerMiddleware
  8. :copyright: 2007 Pallets
  9. :license: BSD-3-Clause
  10. """
  11. from __future__ import print_function
  12. import os.path
  13. import sys
  14. import time
  15. from pstats import Stats
  16. try:
  17. from cProfile import Profile
  18. except ImportError:
  19. from profile import Profile
  20. class ProfilerMiddleware(object):
  21. """Wrap a WSGI application and profile the execution of each
  22. request. Responses are buffered so that timings are more exact.
  23. If ``stream`` is given, :class:`pstats.Stats` are written to it
  24. after each request. If ``profile_dir`` is given, :mod:`cProfile`
  25. data files are saved to that directory, one file per request.
  26. The filename can be customized by passing ``filename_format``. If
  27. it is a string, it will be formatted using :meth:`str.format` with
  28. the following fields available:
  29. - ``{method}`` - The request method; GET, POST, etc.
  30. - ``{path}`` - The request path or 'root' should one not exist.
  31. - ``{elapsed}`` - The elapsed time of the request.
  32. - ``{time}`` - The time of the request.
  33. If it is a callable, it will be called with the WSGI ``environ``
  34. dict and should return a filename.
  35. :param app: The WSGI application to wrap.
  36. :param stream: Write stats to this stream. Disable with ``None``.
  37. :param sort_by: A tuple of columns to sort stats by. See
  38. :meth:`pstats.Stats.sort_stats`.
  39. :param restrictions: A tuple of restrictions to filter stats by. See
  40. :meth:`pstats.Stats.print_stats`.
  41. :param profile_dir: Save profile data files to this directory.
  42. :param filename_format: Format string for profile data file names,
  43. or a callable returning a name. See explanation above.
  44. .. code-block:: python
  45. from werkzeug.middleware.profiler import ProfilerMiddleware
  46. app = ProfilerMiddleware(app)
  47. .. versionchanged:: 0.15
  48. Stats are written even if ``profile_dir`` is given, and can be
  49. disable by passing ``stream=None``.
  50. .. versionadded:: 0.15
  51. Added ``filename_format``.
  52. .. versionadded:: 0.9
  53. Added ``restrictions`` and ``profile_dir``.
  54. """
  55. def __init__(
  56. self,
  57. app,
  58. stream=sys.stdout,
  59. sort_by=("time", "calls"),
  60. restrictions=(),
  61. profile_dir=None,
  62. filename_format="{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof",
  63. ):
  64. self._app = app
  65. self._stream = stream
  66. self._sort_by = sort_by
  67. self._restrictions = restrictions
  68. self._profile_dir = profile_dir
  69. self._filename_format = filename_format
  70. def __call__(self, environ, start_response):
  71. response_body = []
  72. def catching_start_response(status, headers, exc_info=None):
  73. start_response(status, headers, exc_info)
  74. return response_body.append
  75. def runapp():
  76. app_iter = self._app(environ, catching_start_response)
  77. response_body.extend(app_iter)
  78. if hasattr(app_iter, "close"):
  79. app_iter.close()
  80. profile = Profile()
  81. start = time.time()
  82. profile.runcall(runapp)
  83. body = b"".join(response_body)
  84. elapsed = time.time() - start
  85. if self._profile_dir is not None:
  86. if callable(self._filename_format):
  87. filename = self._filename_format(environ)
  88. else:
  89. filename = self._filename_format.format(
  90. method=environ["REQUEST_METHOD"],
  91. path=(
  92. environ.get("PATH_INFO").strip("/").replace("/", ".") or "root"
  93. ),
  94. elapsed=elapsed * 1000.0,
  95. time=time.time(),
  96. )
  97. filename = os.path.join(self._profile_dir, filename)
  98. profile.dump_stats(filename)
  99. if self._stream is not None:
  100. stats = Stats(profile, stream=self._stream)
  101. stats.sort_stats(*self._sort_by)
  102. print("-" * 80, file=self._stream)
  103. print("PATH: {!r}".format(environ.get("PATH_INFO", "")), file=self._stream)
  104. stats.print_stats(*self._restrictions)
  105. print("-" * 80 + "\n", file=self._stream)
  106. return [body]