-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🎨 Improve profiling middleware (#5935)
- Loading branch information
1 parent
641b328
commit ab8bb89
Showing
7 changed files
with
191 additions
and
94 deletions.
There are no files selected for viewing
29 changes: 0 additions & 29 deletions
29
packages/service-library/src/servicelib/_utils_profiling_middleware.py
This file was deleted.
Oops, something went wrong.
64 changes: 35 additions & 29 deletions
64
packages/service-library/src/servicelib/aiohttp/profiler_middleware.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,49 @@ | ||
from aiohttp.web import HTTPInternalServerError, Request, StreamResponse, middleware | ||
from pyinstrument import Profiler | ||
from servicelib.mimetype_constants import ( | ||
MIMETYPE_APPLICATION_JSON, | ||
MIMETYPE_APPLICATION_ND_JSON, | ||
) | ||
|
||
from .._utils_profiling_middleware import append_profile | ||
from ..utils_profiling_middleware import _is_profiling, _profiler, append_profile | ||
|
||
|
||
@middleware | ||
async def profiling_middleware(request: Request, handler): | ||
profiler: Profiler | None = None | ||
if request.headers.get("x-profile") is not None: | ||
profiler = Profiler(async_mode="enabled") | ||
profiler.start() | ||
try: | ||
if _profiler.is_running or (_profiler.last_session is not None): | ||
raise HTTPInternalServerError( | ||
reason="Profiler is already running. Only a single request can be profiled at any given time.", | ||
headers={}, | ||
) | ||
_profiler.reset() | ||
_is_profiling.set(True) | ||
|
||
response = await handler(request) | ||
with _profiler: | ||
response = await handler(request) | ||
|
||
if profiler is None: | ||
return response | ||
if response.content_type != MIMETYPE_APPLICATION_JSON: | ||
raise HTTPInternalServerError( | ||
reason=f"Profiling middleware is not compatible with {response.content_type=}", | ||
headers={}, | ||
) | ||
if response.content_type != MIMETYPE_APPLICATION_JSON: | ||
raise HTTPInternalServerError( | ||
reason=f"Profiling middleware is not compatible with {response.content_type=}", | ||
headers={}, | ||
) | ||
|
||
stream_response = StreamResponse( | ||
status=response.status, | ||
reason=response.reason, | ||
headers=response.headers, | ||
) | ||
stream_response.content_type = MIMETYPE_APPLICATION_ND_JSON | ||
await stream_response.prepare(request) | ||
await stream_response.write(response.body) | ||
profiler.stop() | ||
await stream_response.write( | ||
append_profile( | ||
"\n", profiler.output_text(unicode=True, color=True, show_all=True) | ||
).encode() | ||
) | ||
await stream_response.write_eof() | ||
return stream_response | ||
stream_response = StreamResponse( | ||
status=response.status, | ||
reason=response.reason, | ||
headers=response.headers, | ||
) | ||
stream_response.content_type = MIMETYPE_APPLICATION_ND_JSON | ||
await stream_response.prepare(request) | ||
await stream_response.write(response.body) | ||
await stream_response.write( | ||
append_profile( | ||
"\n", _profiler.output_text(unicode=True, color=True, show_all=True) | ||
).encode() | ||
) | ||
await stream_response.write_eof() | ||
finally: | ||
_profiler.reset() | ||
return stream_response | ||
|
||
return await handler(request) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
packages/service-library/src/servicelib/utils_profiling_middleware.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import contextvars | ||
import json | ||
from contextlib import contextmanager | ||
from typing import Iterator | ||
|
||
from pyinstrument import Profiler | ||
from servicelib.mimetype_constants import ( | ||
MIMETYPE_APPLICATION_JSON, | ||
MIMETYPE_APPLICATION_ND_JSON, | ||
) | ||
|
||
_profiler = Profiler(async_mode="enabled") | ||
_is_profiling = contextvars.ContextVar("_is_profiling", default=False) | ||
|
||
|
||
def is_profiling() -> bool: | ||
return _is_profiling.get() | ||
|
||
|
||
@contextmanager | ||
def profile(do_profile: bool | None = None) -> Iterator[None]: | ||
"""Context manager which temporarily removes request profiler from context""" | ||
if do_profile is None: | ||
do_profile = _is_profiling.get() | ||
if do_profile: | ||
try: | ||
_profiler.start() | ||
yield | ||
finally: | ||
_profiler.stop() | ||
else: | ||
yield None | ||
|
||
|
||
@contextmanager | ||
def dont_profile() -> Iterator[None]: | ||
if _is_profiling.get(): | ||
try: | ||
_profiler.stop() | ||
yield | ||
finally: | ||
_profiler.start() | ||
else: | ||
yield | ||
|
||
|
||
def append_profile(body: str, profile_text: str) -> str: | ||
try: | ||
json.loads(body) | ||
body += "\n" if not body.endswith("\n") else "" | ||
except json.decoder.JSONDecodeError: | ||
pass | ||
body += json.dumps({"profile": profile_text}) | ||
return body | ||
|
||
|
||
def check_response_headers( | ||
response_headers: dict[bytes, bytes] | ||
) -> list[tuple[bytes, bytes]]: | ||
original_content_type: str = response_headers[b"content-type"].decode() | ||
assert original_content_type in { | ||
MIMETYPE_APPLICATION_ND_JSON, | ||
MIMETYPE_APPLICATION_JSON, | ||
} # nosec | ||
headers: dict = {} | ||
headers[b"content-type"] = MIMETYPE_APPLICATION_ND_JSON.encode() | ||
return list(headers.items()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
\"http://10.43.103.193.nip.io:8006/v0/me\" -X GET -H "accept: application/json" -H "Authorization: Basic dGVzdF9iODkxNjUwZmViZjY2OTNlZjc3MToxNzliM2E4OTRiNTY0ZGY5NjExYzY5ZmE4NDcxNjNiYzhmYzdkMGY0" |