prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Elapsed and timestamp values are not available to filename_format function
  When the `ProfilerMiddleware` `filename_format` argument is a callable, the callable will only receive the WSGI environ variable. However, the default format string can use the `elapsed` and `time` values. This means that one using a format function cannot recreate a similar output pattern. In my case, I'd like to use both, but format the timestamp differently.
  
  
  Example: I'd like a filename of the form `2023-08-21:14:05:05.POST.myurl.13ms.prof`. I can get the timestamp by doing my own `datetime.now()` call, but the elapsed time is not available.
  
  This problem is solvable by subclassing `ProfilerMiddleware` or writing ones own profiling middleware, but this seems like a useful thing to have in the core library so library users don't need to duplicate the `__call__()` code in their own projects.
  
  I can submit a PR if this change is welcome.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/werkzeug/middleware/profiler.py`
  Content:
  ```
  1 """
  2 Application Profiler
  3 ====================
  4 
  5 This module provides a middleware that profiles each request with the
  6 :mod:`cProfile` module. This can help identify bottlenecks in your code
  7 that may be slowing down your application.
  8 
  9 .. autoclass:: ProfilerMiddleware
  10 
  11 :copyright: 2007 Pallets
  12 :license: BSD-3-Clause
  13 """
  14 from __future__ import annotations
  15 
  16 import os.path
  17 import sys
  18 import time
  19 import typing as t
  20 from pstats import Stats
  21 
  22 try:
  23     from cProfile import Profile
  24 except ImportError:
  25     from profile import Profile  # type: ignore
  26 
  27 if t.TYPE_CHECKING:
  28     from _typeshed.wsgi import StartResponse
  29     from _typeshed.wsgi import WSGIApplication
  30     from _typeshed.wsgi import WSGIEnvironment
  31 
  32 
  33 class ProfilerMiddleware:
  34     """Wrap a WSGI application and profile the execution of each
  35     request. Responses are buffered so that timings are more exact.
  36 
  37     If ``stream`` is given, :class:`pstats.Stats` are written to it
  38     after each request. If ``profile_dir`` is given, :mod:`cProfile`
  39     data files are saved to that directory, one file per request.
  40 
  41     The filename can be customized by passing ``filename_format``. If
  42     it is a string, it will be formatted using :meth:`str.format` with
  43     the following fields available:
  44 
  45     -   ``{method}`` - The request method; GET, POST, etc.
  46     -   ``{path}`` - The request path or 'root' should one not exist.
  47     -   ``{elapsed}`` - The elapsed time of the request.
  48     -   ``{time}`` - The time of the request.
  49 
  50     If it is a callable, it will be called with the WSGI ``environ``
  51     dict and should return a filename.
  52 
  53     :param app: The WSGI application to wrap.
  54     :param stream: Write stats to this stream. Disable with ``None``.
  55     :param sort_by: A tuple of columns to sort stats by. See
  56         :meth:`pstats.Stats.sort_stats`.
  57     :param restrictions: A tuple of restrictions to filter stats by. See
  58         :meth:`pstats.Stats.print_stats`.
  59     :param profile_dir: Save profile data files to this directory.
  60     :param filename_format: Format string for profile data file names,
  61         or a callable returning a name. See explanation above.
  62 
  63     .. code-block:: python
  64 
  65         from werkzeug.middleware.profiler import ProfilerMiddleware
  66         app = ProfilerMiddleware(app)
  67 
  68     .. versionchanged:: 0.15
  69         Stats are written even if ``profile_dir`` is given, and can be
  70         disable by passing ``stream=None``.
  71 
  72     .. versionadded:: 0.15
  73         Added ``filename_format``.
  74 
  75     .. versionadded:: 0.9
  76         Added ``restrictions`` and ``profile_dir``.
  77     """
  78 
  79     def __init__(
  80         self,
  81         app: WSGIApplication,
  82         stream: t.IO[str] | None = sys.stdout,
  83         sort_by: t.Iterable[str] = ("time", "calls"),
  84         restrictions: t.Iterable[str | int | float] = (),
  85         profile_dir: str | None = None,
  86         filename_format: str = "{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof",
  87     ) -> None:
  88         self._app = app
  89         self._stream = stream
  90         self._sort_by = sort_by
  91         self._restrictions = restrictions
  92         self._profile_dir = profile_dir
  93         self._filename_format = filename_format
  94 
  95     def __call__(
  96         self, environ: WSGIEnvironment, start_response: StartResponse
  97     ) -> t.Iterable[bytes]:
  98         response_body: list[bytes] = []
  99 
  100         def catching_start_response(status, headers, exc_info=None):  # type: ignore
  101             start_response(status, headers, exc_info)
  102             return response_body.append
  103 
  104         def runapp() -> None:
  105             app_iter = self._app(
  106                 environ, t.cast("StartResponse", catching_start_response)
  107             )
  108             response_body.extend(app_iter)
  109 
  110             if hasattr(app_iter, "close"):
  111                 app_iter.close()
  112 
  113         profile = Profile()
  114         start = time.time()
  115         profile.runcall(runapp)
  116         body = b"".join(response_body)
  117         elapsed = time.time() - start
  118 
  119         if self._profile_dir is not None:
  120             if callable(self._filename_format):
  121                 filename = self._filename_format(environ)
  122             else:
  123                 filename = self._filename_format.format(
  124                     method=environ["REQUEST_METHOD"],
  125                     path=environ["PATH_INFO"].strip("/").replace("/", ".") or "root",
  126                     elapsed=elapsed * 1000.0,
  127                     time=time.time(),
  128                 )
  129             filename = os.path.join(self._profile_dir, filename)
  130             profile.dump_stats(filename)
  131 
  132         if self._stream is not None:
  133             stats = Stats(profile, stream=self._stream)
  134             stats.sort_stats(*self._sort_by)
  135             print("-" * 80, file=self._stream)
  136             path_info = environ.get("PATH_INFO", "")
  137             print(f"PATH: {path_info!r}", file=self._stream)
  138             stats.print_stats(*self._restrictions)
  139             print(f"{'-' * 80}\n", file=self._stream)
  140 
  141         return [body]
  142
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/src/werkzeug/middleware/profiler.py b/src/werkzeug/middleware/profiler.py
  --- a/src/werkzeug/middleware/profiler.py
  +++ b/src/werkzeug/middleware/profiler.py
  @@ -44,11 +44,16 @@
   
       -   ``{method}`` - The request method; GET, POST, etc.
       -   ``{path}`` - The request path or 'root' should one not exist.
  -    -   ``{elapsed}`` - The elapsed time of the request.
  +    -   ``{elapsed}`` - The elapsed time of the request in milliseconds.
       -   ``{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.
  +    If it is a callable, it will be called with the WSGI ``environ`` and
  +    be expected to return a filename string. The ``environ`` dictionary
  +    will also have the ``"werkzeug.profiler"`` key populated with a
  +    dictionary containing the following fields (more may be added in the
  +    future):
  +    -   ``{elapsed}`` - The elapsed time of the request in milliseconds.
  +    -   ``{time}`` - The time of the request.
   
       :param app: The WSGI application to wrap.
       :param stream: Write stats to this stream. Disable with ``None``.
  @@ -65,6 +70,10 @@
           from werkzeug.middleware.profiler import ProfilerMiddleware
           app = ProfilerMiddleware(app)
   
  +    .. versionchanged:: 3.0
  +        Added the ``"werkzeug.profiler"`` key to the ``filename_format(environ)``
  +        parameter with the  ``elapsed`` and ``time`` fields.
  +
       .. versionchanged:: 0.15
           Stats are written even if ``profile_dir`` is given, and can be
           disable by passing ``stream=None``.
  @@ -118,6 +127,10 @@
   
           if self._profile_dir is not None:
               if callable(self._filename_format):
  +                environ["werkzeug.profiler"] = {
  +                    "elapsed": elapsed * 1000.0,
  +                    "time": time.time(),
  +                }
                   filename = self._filename_format(environ)
               else:
                   filename = self._filename_format.format(
  
verification_info: {"golden_diff": "diff --git a/src/werkzeug/middleware/profiler.py b/src/werkzeug/middleware/profiler.py\n--- a/src/werkzeug/middleware/profiler.py\n+++ b/src/werkzeug/middleware/profiler.py\n@@ -44,11 +44,16 @@\n \n     -   ``{method}`` - The request method; GET, POST, etc.\n     -   ``{path}`` - The request path or 'root' should one not exist.\n-    -   ``{elapsed}`` - The elapsed time of the request.\n+    -   ``{elapsed}`` - The elapsed time of the request in milliseconds.\n     -   ``{time}`` - The time of the request.\n \n-    If it is a callable, it will be called with the WSGI ``environ``\n-    dict and should return a filename.\n+    If it is a callable, it will be called with the WSGI ``environ`` and\n+    be expected to return a filename string. The ``environ`` dictionary\n+    will also have the ``\"werkzeug.profiler\"`` key populated with a\n+    dictionary containing the following fields (more may be added in the\n+    future):\n+    -   ``{elapsed}`` - The elapsed time of the request in milliseconds.\n+    -   ``{time}`` - The time of the request.\n \n     :param app: The WSGI application to wrap.\n     :param stream: Write stats to this stream. Disable with ``None``.\n@@ -65,6 +70,10 @@\n         from werkzeug.middleware.profiler import ProfilerMiddleware\n         app = ProfilerMiddleware(app)\n \n+    .. versionchanged:: 3.0\n+        Added the ``\"werkzeug.profiler\"`` key to the ``filename_format(environ)``\n+        parameter with the  ``elapsed`` and ``time`` fields.\n+\n     .. versionchanged:: 0.15\n         Stats are written even if ``profile_dir`` is given, and can be\n         disable by passing ``stream=None``.\n@@ -118,6 +127,10 @@\n \n         if self._profile_dir is not None:\n             if callable(self._filename_format):\n+                environ[\"werkzeug.profiler\"] = {\n+                    \"elapsed\": elapsed * 1000.0,\n+                    \"time\": time.time(),\n+                }\n                 filename = self._filename_format(environ)\n             else:\n                 filename = self._filename_format.format(\n", "issue": "Elapsed and timestamp values are not available to filename_format function\nWhen the `ProfilerMiddleware` `filename_format` argument is a callable, the callable will only receive the WSGI environ variable. However, the default format string can use the `elapsed` and `time` values. This means that one using a format function cannot recreate a similar output pattern. In my case, I'd like to use both, but format the timestamp differently.\r\n\r\n\r\nExample: I'd like a filename of the form `2023-08-21:14:05:05.POST.myurl.13ms.prof`. I can get the timestamp by doing my own `datetime.now()` call, but the elapsed time is not available.\r\n\r\nThis problem is solvable by subclassing `ProfilerMiddleware` or writing ones own profiling middleware, but this seems like a useful thing to have in the core library so library users don't need to duplicate the `__call__()` code in their own projects.\r\n\r\nI can submit a PR if this change is welcome.\r\n\n", "before_files": [{"content": "\"\"\"\nApplication Profiler\n====================\n\nThis module provides a middleware that profiles each request with the\n:mod:`cProfile` module. This can help identify bottlenecks in your code\nthat may be slowing down your application.\n\n.. autoclass:: ProfilerMiddleware\n\n:copyright: 2007 Pallets\n:license: BSD-3-Clause\n\"\"\"\nfrom __future__ import annotations\n\nimport os.path\nimport sys\nimport time\nimport typing as t\nfrom pstats import Stats\n\ntry:\n    from cProfile import Profile\nexcept ImportError:\n    from profile import Profile  # type: ignore\n\nif t.TYPE_CHECKING:\n    from _typeshed.wsgi import StartResponse\n    from _typeshed.wsgi import WSGIApplication\n    from _typeshed.wsgi import WSGIEnvironment\n\n\nclass ProfilerMiddleware:\n    \"\"\"Wrap a WSGI application and profile the execution of each\n    request. Responses are buffered so that timings are more exact.\n\n    If ``stream`` is given, :class:`pstats.Stats` are written to it\n    after each request. If ``profile_dir`` is given, :mod:`cProfile`\n    data files are saved to that directory, one file per request.\n\n    The filename can be customized by passing ``filename_format``. If\n    it is a string, it will be formatted using :meth:`str.format` with\n    the following fields available:\n\n    -   ``{method}`` - The request method; GET, POST, etc.\n    -   ``{path}`` - The request path or 'root' should one not exist.\n    -   ``{elapsed}`` - The elapsed time of the request.\n    -   ``{time}`` - The time of the request.\n\n    If it is a callable, it will be called with the WSGI ``environ``\n    dict and should return a filename.\n\n    :param app: The WSGI application to wrap.\n    :param stream: Write stats to this stream. Disable with ``None``.\n    :param sort_by: A tuple of columns to sort stats by. See\n        :meth:`pstats.Stats.sort_stats`.\n    :param restrictions: A tuple of restrictions to filter stats by. See\n        :meth:`pstats.Stats.print_stats`.\n    :param profile_dir: Save profile data files to this directory.\n    :param filename_format: Format string for profile data file names,\n        or a callable returning a name. See explanation above.\n\n    .. code-block:: python\n\n        from werkzeug.middleware.profiler import ProfilerMiddleware\n        app = ProfilerMiddleware(app)\n\n    .. versionchanged:: 0.15\n        Stats are written even if ``profile_dir`` is given, and can be\n        disable by passing ``stream=None``.\n\n    .. versionadded:: 0.15\n        Added ``filename_format``.\n\n    .. versionadded:: 0.9\n        Added ``restrictions`` and ``profile_dir``.\n    \"\"\"\n\n    def __init__(\n        self,\n        app: WSGIApplication,\n        stream: t.IO[str] | None = sys.stdout,\n        sort_by: t.Iterable[str] = (\"time\", \"calls\"),\n        restrictions: t.Iterable[str | int | float] = (),\n        profile_dir: str | None = None,\n        filename_format: str = \"{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof\",\n    ) -> None:\n        self._app = app\n        self._stream = stream\n        self._sort_by = sort_by\n        self._restrictions = restrictions\n        self._profile_dir = profile_dir\n        self._filename_format = filename_format\n\n    def __call__(\n        self, environ: WSGIEnvironment, start_response: StartResponse\n    ) -> t.Iterable[bytes]:\n        response_body: list[bytes] = []\n\n        def catching_start_response(status, headers, exc_info=None):  # type: ignore\n            start_response(status, headers, exc_info)\n            return response_body.append\n\n        def runapp() -> None:\n            app_iter = self._app(\n                environ, t.cast(\"StartResponse\", catching_start_response)\n            )\n            response_body.extend(app_iter)\n\n            if hasattr(app_iter, \"close\"):\n                app_iter.close()\n\n        profile = Profile()\n        start = time.time()\n        profile.runcall(runapp)\n        body = b\"\".join(response_body)\n        elapsed = time.time() - start\n\n        if self._profile_dir is not None:\n            if callable(self._filename_format):\n                filename = self._filename_format(environ)\n            else:\n                filename = self._filename_format.format(\n                    method=environ[\"REQUEST_METHOD\"],\n                    path=environ[\"PATH_INFO\"].strip(\"/\").replace(\"/\", \".\") or \"root\",\n                    elapsed=elapsed * 1000.0,\n                    time=time.time(),\n                )\n            filename = os.path.join(self._profile_dir, filename)\n            profile.dump_stats(filename)\n\n        if self._stream is not None:\n            stats = Stats(profile, stream=self._stream)\n            stats.sort_stats(*self._sort_by)\n            print(\"-\" * 80, file=self._stream)\n            path_info = environ.get(\"PATH_INFO\", \"\")\n            print(f\"PATH: {path_info!r}\", file=self._stream)\n            stats.print_stats(*self._restrictions)\n            print(f\"{'-' * 80}\\n\", file=self._stream)\n\n        return [body]\n", "path": "src/werkzeug/middleware/profiler.py"}], "after_files": [{"content": "\"\"\"\nApplication Profiler\n====================\n\nThis module provides a middleware that profiles each request with the\n:mod:`cProfile` module. This can help identify bottlenecks in your code\nthat may be slowing down your application.\n\n.. autoclass:: ProfilerMiddleware\n\n:copyright: 2007 Pallets\n:license: BSD-3-Clause\n\"\"\"\nfrom __future__ import annotations\n\nimport os.path\nimport sys\nimport time\nimport typing as t\nfrom pstats import Stats\n\ntry:\n    from cProfile import Profile\nexcept ImportError:\n    from profile import Profile  # type: ignore\n\nif t.TYPE_CHECKING:\n    from _typeshed.wsgi import StartResponse\n    from _typeshed.wsgi import WSGIApplication\n    from _typeshed.wsgi import WSGIEnvironment\n\n\nclass ProfilerMiddleware:\n    \"\"\"Wrap a WSGI application and profile the execution of each\n    request. Responses are buffered so that timings are more exact.\n\n    If ``stream`` is given, :class:`pstats.Stats` are written to it\n    after each request. If ``profile_dir`` is given, :mod:`cProfile`\n    data files are saved to that directory, one file per request.\n\n    The filename can be customized by passing ``filename_format``. If\n    it is a string, it will be formatted using :meth:`str.format` with\n    the following fields available:\n\n    -   ``{method}`` - The request method; GET, POST, etc.\n    -   ``{path}`` - The request path or 'root' should one not exist.\n    -   ``{elapsed}`` - The elapsed time of the request in milliseconds.\n    -   ``{time}`` - The time of the request.\n\n    If it is a callable, it will be called with the WSGI ``environ`` and\n    be expected to return a filename string. The ``environ`` dictionary\n    will also have the ``\"werkzeug.profiler\"`` key populated with a\n    dictionary containing the following fields (more may be added in the\n    future):\n    -   ``{elapsed}`` - The elapsed time of the request in milliseconds.\n    -   ``{time}`` - The time of the request.\n\n    :param app: The WSGI application to wrap.\n    :param stream: Write stats to this stream. Disable with ``None``.\n    :param sort_by: A tuple of columns to sort stats by. See\n        :meth:`pstats.Stats.sort_stats`.\n    :param restrictions: A tuple of restrictions to filter stats by. See\n        :meth:`pstats.Stats.print_stats`.\n    :param profile_dir: Save profile data files to this directory.\n    :param filename_format: Format string for profile data file names,\n        or a callable returning a name. See explanation above.\n\n    .. code-block:: python\n\n        from werkzeug.middleware.profiler import ProfilerMiddleware\n        app = ProfilerMiddleware(app)\n\n    .. versionchanged:: 3.0\n        Added the ``\"werkzeug.profiler\"`` key to the ``filename_format(environ)``\n        parameter with the  ``elapsed`` and ``time`` fields.\n\n    .. versionchanged:: 0.15\n        Stats are written even if ``profile_dir`` is given, and can be\n        disable by passing ``stream=None``.\n\n    .. versionadded:: 0.15\n        Added ``filename_format``.\n\n    .. versionadded:: 0.9\n        Added ``restrictions`` and ``profile_dir``.\n    \"\"\"\n\n    def __init__(\n        self,\n        app: WSGIApplication,\n        stream: t.IO[str] | None = sys.stdout,\n        sort_by: t.Iterable[str] = (\"time\", \"calls\"),\n        restrictions: t.Iterable[str | int | float] = (),\n        profile_dir: str | None = None,\n        filename_format: str = \"{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof\",\n    ) -> None:\n        self._app = app\n        self._stream = stream\n        self._sort_by = sort_by\n        self._restrictions = restrictions\n        self._profile_dir = profile_dir\n        self._filename_format = filename_format\n\n    def __call__(\n        self, environ: WSGIEnvironment, start_response: StartResponse\n    ) -> t.Iterable[bytes]:\n        response_body: list[bytes] = []\n\n        def catching_start_response(status, headers, exc_info=None):  # type: ignore\n            start_response(status, headers, exc_info)\n            return response_body.append\n\n        def runapp() -> None:\n            app_iter = self._app(\n                environ, t.cast(\"StartResponse\", catching_start_response)\n            )\n            response_body.extend(app_iter)\n\n            if hasattr(app_iter, \"close\"):\n                app_iter.close()\n\n        profile = Profile()\n        start = time.time()\n        profile.runcall(runapp)\n        body = b\"\".join(response_body)\n        elapsed = time.time() - start\n\n        if self._profile_dir is not None:\n            if callable(self._filename_format):\n                environ[\"werkzeug.profiler\"] = {\n                    \"elapsed\": elapsed * 1000.0,\n                    \"time\": time.time(),\n                }\n                filename = self._filename_format(environ)\n            else:\n                filename = self._filename_format.format(\n                    method=environ[\"REQUEST_METHOD\"],\n                    path=environ[\"PATH_INFO\"].strip(\"/\").replace(\"/\", \".\") or \"root\",\n                    elapsed=elapsed * 1000.0,\n                    time=time.time(),\n                )\n            filename = os.path.join(self._profile_dir, filename)\n            profile.dump_stats(filename)\n\n        if self._stream is not None:\n            stats = Stats(profile, stream=self._stream)\n            stats.sort_stats(*self._sort_by)\n            print(\"-\" * 80, file=self._stream)\n            path_info = environ.get(\"PATH_INFO\", \"\")\n            print(f\"PATH: {path_info!r}\", file=self._stream)\n            stats.print_stats(*self._restrictions)\n            print(f\"{'-' * 80}\\n\", file=self._stream)\n\n        return [body]\n", "path": "src/werkzeug/middleware/profiler.py"}]}