"""
Application Profiler
====================

This module provides a middleware that profiles each request with the
:mod:`cProfile` module. This can help identify bottlenecks in your code
that may be slowing down your application.

.. autoclass:: ProfilerMiddleware

:copyright: 2007 Pallets
:license: BSD-3-Clause
"""
from __future__ import print_function

import os.path
import sys
import time
from pstats import Stats

try:
    from cProfile import Profile
except ImportError:
    from profile import Profile


class ProfilerMiddleware(object):
    """Wrap a WSGI application and profile the execution of each
    request. Responses are buffered so that timings are more exact.

    If ``stream`` is given, :class:`pstats.Stats` are written to it
    after each request. If ``profile_dir`` is given, :mod:`cProfile`
    data files are saved to that directory, one file per request.

    The filename can be customized by passing ``filename_format``. If
    it is a string, it will be formatted using :meth:`str.format` with
    the following fields available:

    -   ``{method}`` - The request method; GET, POST, etc.
    -   ``{path}`` - The request path or 'root' should one not exist.
    -   ``{elapsed}`` - The elapsed time of the request.
    -   ``{time}`` - The time of the request.

    If it is a callable, it will be called with the WSGI ``environ``
    dict and should return a filename.

    :param app: The WSGI application to wrap.
    :param stream: Write stats to this stream. Disable with ``None``.
    :param sort_by: A tuple of columns to sort stats by. See
        :meth:`pstats.Stats.sort_stats`.
    :param restrictions: A tuple of restrictions to filter stats by. See
        :meth:`pstats.Stats.print_stats`.
    :param profile_dir: Save profile data files to this directory.
    :param filename_format: Format string for profile data file names,
        or a callable returning a name. See explanation above.

    .. code-block:: python

        from werkzeug.middleware.profile import ProfilerMiddleware
        app = ProfilerMiddleware(app)

    .. versionchanged:: 0.15
        Stats are written even if ``profile_dir`` is given, and can be
        disable by passing ``stream=None``.

    .. versionadded:: 0.15
        Added ``filename_format``.

    .. versionadded:: 0.9
        Added ``restrictions`` and ``profile_dir``.
    """

    def __init__(
        self,
        app,
        stream=sys.stdout,
        sort_by=("time", "calls"),
        restrictions=(),
        profile_dir=None,
        filename_format="{method}.{path}.{elapsed:06d}ms.{time:d}.prof",
    ):
        self._app = app
        self._stream = stream
        self._sort_by = sort_by
        self._restrictions = restrictions
        self._profile_dir = profile_dir
        self._filename_format = filename_format

    def __call__(self, environ, start_response):
        response_body = []

        def catching_start_response(status, headers, exc_info=None):
            start_response(status, headers, exc_info)
            return response_body.append

        def runapp():
            app_iter = self._app(environ, catching_start_response)
            response_body.extend(app_iter)

            if hasattr(app_iter, "close"):
                app_iter.close()

        profile = Profile()
        start = time.time()
        profile.runcall(runapp)
        body = b"".join(response_body)
        elapsed = time.time() - start

        if self._profile_dir is not None:
            if callable(self._filename_format):
                filename = self._filename_format(environ)
            else:
                filename = self._filename_format.format(
                    method=environ["REQUEST_METHOD"],
                    path=(
                        environ.get("PATH_INFO").strip("/").replace("/", ".") or "root"
                    ),
                    elapsed=elapsed * 1000.0,
                    time=time.time(),
                )
            filename = os.path.join(self._profile_dir, filename)
            profile.dump_stats(filename)

        if self._stream is not None:
            stats = Stats(profile, stream=self._stream)
            stats.sort_stats(*self._sort_by)
            print("-" * 80, file=self._stream)
            print("PATH: {!r}".format(environ.get("PATH_INFO", "")), file=self._stream)
            stats.print_stats(*self._restrictions)
            print("-" * 80 + "\n", file=self._stream)

        return [body]

src/werkzeug/middleware/profiler.py
"""
Application Profiler
====================

This module provides a middleware that profiles each request with the
:mod:`cProfile` module. This can help identify bottlenecks in your code
that may be slowing down your application.

.. autoclass:: ProfilerMiddleware

:copyright: 2007 Pallets
:license: BSD-3-Clause
"""
from __future__ import print_function

import os.path
import sys
import time
from pstats import Stats

try:
    from cProfile import Profile
except ImportError:
    from profile import Profile


class ProfilerMiddleware(object):
    """Wrap a WSGI application and profile the execution of each
    request. Responses are buffered so that timings are more exact.

    If ``stream`` is given, :class:`pstats.Stats` are written to it
    after each request. If ``profile_dir`` is given, :mod:`cProfile`
    data files are saved to that directory, one file per request.

    The filename can be customized by passing ``filename_format``. If
    it is a string, it will be formatted using :meth:`str.format` with
    the following fields available:

    -   ``{method}`` - The request method; GET, POST, etc.
    -   ``{path}`` - The request path or 'root' should one not exist.
    -   ``{elapsed}`` - The elapsed time of the request.
    -   ``{time}`` - The time of the request.

    If it is a callable, it will be called with the WSGI ``environ``
    dict and should return a filename.

    :param app: The WSGI application to wrap.
    :param stream: Write stats to this stream. Disable with ``None``.
    :param sort_by: A tuple of columns to sort stats by. See
        :meth:`pstats.Stats.sort_stats`.
    :param restrictions: A tuple of restrictions to filter stats by. See
        :meth:`pstats.Stats.print_stats`.
    :param profile_dir: Save profile data files to this directory.
    :param filename_format: Format string for profile data file names,
        or a callable returning a name. See explanation above.

    .. code-block:: python

        from werkzeug.middleware.profiler import ProfilerMiddleware
        app = ProfilerMiddleware(app)

    .. versionchanged:: 0.15
        Stats are written even if ``profile_dir`` is given, and can be
        disable by passing ``stream=None``.

    .. versionadded:: 0.15
        Added ``filename_format``.

    .. versionadded:: 0.9
        Added ``restrictions`` and ``profile_dir``.
    """

    def __init__(
        self,
        app,
        stream=sys.stdout,
        sort_by=("time", "calls"),
        restrictions=(),
        profile_dir=None,
        filename_format="{method}.{path}.{elapsed:06d}ms.{time:d}.prof",
    ):
        self._app = app
        self._stream = stream
        self._sort_by = sort_by
        self._restrictions = restrictions
        self._profile_dir = profile_dir
        self._filename_format = filename_format

    def __call__(self, environ, start_response):
        response_body = []

        def catching_start_response(status, headers, exc_info=None):
            start_response(status, headers, exc_info)
            return response_body.append

        def runapp():
            app_iter = self._app(environ, catching_start_response)
            response_body.extend(app_iter)

            if hasattr(app_iter, "close"):
                app_iter.close()

        profile = Profile()
        start = time.time()
        profile.runcall(runapp)
        body = b"".join(response_body)
        elapsed = time.time() - start

        if self._profile_dir is not None:
            if callable(self._filename_format):
                filename = self._filename_format(environ)
            else:
                filename = self._filename_format.format(
                    method=environ["REQUEST_METHOD"],
                    path=(
                        environ.get("PATH_INFO").strip("/").replace("/", ".") or "root"
                    ),
                    elapsed=elapsed * 1000.0,
                    time=time.time(),
                )
            filename = os.path.join(self._profile_dir, filename)
            profile.dump_stats(filename)

        if self._stream is not None:
            stats = Stats(profile, stream=self._stream)
            stats.sort_stats(*self._sort_by)
            print("-" * 80, file=self._stream)
            print("PATH: {!r}".format(environ.get("PATH_INFO", "")), file=self._stream)
            stats.print_stats(*self._restrictions)
            print("-" * 80 + "\n", file=self._stream)

        return [body]

src/werkzeug/middleware/profiler.py
diff --git a/src/werkzeug/middleware/profiler.py b/src/werkzeug/middleware/profiler.py
index 8e2edc20a..289879d94 100644
--- a/src/werkzeug/middleware/profiler.py
+++ b/src/werkzeug/middleware/profiler.py
@@ -56,7 +56,7 @@ class ProfilerMiddleware(object):
 
     .. code-block:: python
 
-        from werkzeug.middleware.profile import ProfilerMiddleware
+        from werkzeug.middleware.profiler import ProfilerMiddleware
         app = ProfilerMiddleware(app)
 
     .. versionchanged:: 0.15

ProfilerMiddleware Missing in Latest Release
### Environment

```
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.4
BuildVersion:	18E226

$ python --version
Python 3.7.3

$ pip freeze
Werkzeug==0.15.2
```

### Observed Behavior
Inability to import the ProfilerMiddleware from werkzeug as described in [the documentation](https://werkzeug.palletsprojects.com/en/0.15.x/middleware/profiler/)

```
>>> from werkzeug.middleware.profile import ProfilerMiddleware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'werkzeug.middleware.profile'
```

### Steps to Reproduce
```
[~/git] $ mkdir test_venv
[~/git] $ cd test_venv/
[~/git/test_venv] $ python3 -m venv venv
[~/git/test_venv] $ source venv/bin/activate
(venv) [~/git/test_venv] $ pip --version
pip 19.0.3 from /Users/cchapline/git/test_venv/venv/lib/python3.7/site-packages/pip (python 3.7)
(venv) [~/git/test_venv] $ pip install werkzeug
Collecting werkzeug
  Using cached https://files.pythonhosted.org/packages/18/79/84f02539cc181cdbf5ff5a41b9f52cae870b6f632767e43ba6ac70132e92/Werkzeug-0.15.2-py2.py3-none-any.whl
Installing collected packages: werkzeug
Successfully installed werkzeug-0.15.2
(venv) [~/git/test_venv] $ python
Python 3.7.3 (default, Apr  4 2019, 10:56:22)
[Clang 10.0.1 (clang-1001.0.46.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from werkzeug.middleware.profile import ProfilerMiddleware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'werkzeug.middleware.profile'
>>> import werkzeug.middleware as mw
>>> dir(mw)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'dispatcher', 'http_proxy', 'shared_data']
```

### Expected Behavior

The `ImportError` should not occur as I can see the code in `site-packages`:

```
(venv) [~/git/test_venv] $ ls venv/lib/python3.7/site-packages/werkzeug/middleware/profiler.py
venv/lib/python3.7/site-packages/werkzeug/middleware/profiler.py
```
