From 2fabe659c3c5259b50ac90c3106df7d41b8a3c74 Mon Sep 17 00:00:00 2001 From: claws Date: Thu, 28 Dec 2023 10:23:32 +1030 Subject: [PATCH] minor updates and fix for websockets in asgi (#97) --- .../decorators/decorator_count_exceptions.py | 11 ++++++----- examples/decorators/decorator_inprogress.py | 18 ++++++++++++------ examples/decorators/decorator_timer.py | 11 ++++++----- examples/service/app-service-example.py | 2 ++ requirements.dev.txt | 2 +- src/aioprometheus/__init__.py | 2 +- src/aioprometheus/asgi/middleware.py | 8 ++++---- src/aioprometheus/collectors.py | 3 +-- src/aioprometheus/mypy_types.py | 2 +- src/aioprometheus/renderer.py | 3 +-- src/aioprometheus/service.py | 12 ++++++------ tests/test_aiohttp.py | 4 ++-- tests/test_dist.bash | 4 ++-- 13 files changed, 45 insertions(+), 37 deletions(-) diff --git a/examples/decorators/decorator_count_exceptions.py b/examples/decorators/decorator_count_exceptions.py index 8f0f9c4..1a38494 100644 --- a/examples/decorators/decorator_count_exceptions.py +++ b/examples/decorators/decorator_count_exceptions.py @@ -10,7 +10,7 @@ .. code-block:: console - $ curl :8000/metrics + $ curl localhost:8000/metrics # HELP request_handler_exceptions Number of exceptions in requests # TYPE request_handler_exceptions counter request_handler_exceptions{route="/"} 3 @@ -43,8 +43,6 @@ async def handle_request(duration): async def handle_requests(): - # Start up the server to expose the metrics. - await svr.start(port=8000) # Generate some requests. while True: try: @@ -56,13 +54,16 @@ async def handle_requests(): if __name__ == "__main__": loop = asyncio.get_event_loop() - svr = Service() + svc = Service() + + # Start up the server to expose the metrics. + loop.run_until_complete(svc.start(port=8000)) try: loop.run_until_complete(handle_requests()) except KeyboardInterrupt: pass finally: - loop.run_until_complete(svr.stop()) + loop.run_until_complete(svc.stop()) loop.stop() loop.close() diff --git a/examples/decorators/decorator_inprogress.py b/examples/decorators/decorator_inprogress.py index 0cf70c1..bf25159 100644 --- a/examples/decorators/decorator_inprogress.py +++ b/examples/decorators/decorator_inprogress.py @@ -10,7 +10,7 @@ .. code-block:: console - $ curl :8000/metrics + $ curl localhost:8000/metrics # HELP request_in_progress Number of requests in progress # TYPE request_in_progress gauge request_in_progress{route="/"} 1 @@ -37,23 +37,29 @@ async def handle_request(duration): async def handle_requests(): - # Start up the server to expose the metrics. - await svr.start(port=8000) # Generate some requests. while True: - await handle_request(random.random()) + # Perform two requests to increase likelihood of observing two + # requests in progress when fetching metrics. + await asyncio.gather( + handle_request(random.random()), + handle_request(random.random()), + ) if __name__ == "__main__": loop = asyncio.get_event_loop() - svr = Service() + svc = Service() + + # Start up the server to expose the metrics. + loop.run_until_complete(svc.start(port=8000)) try: loop.run_until_complete(handle_requests()) except KeyboardInterrupt: pass finally: - loop.run_until_complete(svr.stop()) + loop.run_until_complete(svc.stop()) loop.stop() loop.close() diff --git a/examples/decorators/decorator_timer.py b/examples/decorators/decorator_timer.py index a87cca2..32cbecf 100644 --- a/examples/decorators/decorator_timer.py +++ b/examples/decorators/decorator_timer.py @@ -9,7 +9,7 @@ .. code-block:: console - $ curl :8000/metrics + $ curl localhost:8000/metrics # HELP request_processing_seconds Time spent processing request # TYPE request_processing_seconds summary request_processing_seconds_count 77 @@ -40,8 +40,6 @@ async def handle_request(duration): async def handle_requests(): - # Start up the server to expose the metrics. - await svr.start(port=8000) # Generate some requests. while True: await handle_request(random.random()) @@ -50,13 +48,16 @@ async def handle_requests(): if __name__ == "__main__": loop = asyncio.get_event_loop() - svr = Service() + svc = Service() + + # Start up the server to expose the metrics. + loop.run_until_complete(svc.start(port=8000)) try: loop.run_until_complete(handle_requests()) except KeyboardInterrupt: pass finally: - loop.run_until_complete(svr.stop()) + loop.run_until_complete(svc.stop()) loop.stop() loop.close() diff --git a/examples/service/app-service-example.py b/examples/service/app-service-example.py index 1762dc1..1512300 100644 --- a/examples/service/app-service-example.py +++ b/examples/service/app-service-example.py @@ -16,6 +16,8 @@ from aioprometheus import Counter, Gauge, Histogram, Summary from aioprometheus.service import Service +logger = logging.getLogger(__name__) + class ExampleApp: """ diff --git a/requirements.dev.txt b/requirements.dev.txt index 9610a24..db3031f 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -16,5 +16,5 @@ fastapi uvicorn quart; python_version > '3.7' -# psutil is used in example code +# psutil is used by examples psutil diff --git a/src/aioprometheus/__init__.py b/src/aioprometheus/__init__.py index 27b7c0f..bb1c086 100644 --- a/src/aioprometheus/__init__.py +++ b/src/aioprometheus/__init__.py @@ -9,4 +9,4 @@ # The 'pusher' and 'service' modules must be explicitly imported by package # users as they depend on optional extras. -__version__ = "23.3.0" +__version__ = "23.12.0" diff --git a/src/aioprometheus/asgi/middleware.py b/src/aioprometheus/asgi/middleware.py index 4527548..5bcba83 100644 --- a/src/aioprometheus/asgi/middleware.py +++ b/src/aioprometheus/asgi/middleware.py @@ -1,7 +1,7 @@ from typing import Any, Awaitable, Callable, Dict, Optional, Sequence -from aioprometheus import REGISTRY, Counter, Registry -from aioprometheus.mypy_types import LabelsType +from ..collectors import REGISTRY, Counter, Registry +from ..mypy_types import LabelsType Scope = Dict[str, Any] Message = Dict[str, Any] @@ -148,7 +148,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send): await self.asgi_callable(scope, receive, send) return - if scope["type"] == "http": + if scope["type"] in ("http", "websocket"): def wrapped_send(response): """ @@ -171,7 +171,7 @@ def wrapped_send(response): # Store HTTP path and method so they can be used later in the send # method to complete metrics updates. - method = scope["method"] + method = scope.get("method") path = self.get_full_or_template_path(scope) labels = {"method": method, "path": path} diff --git a/src/aioprometheus/collectors.py b/src/aioprometheus/collectors.py index bfb2d23..fb8c3d4 100644 --- a/src/aioprometheus/collectors.py +++ b/src/aioprometheus/collectors.py @@ -6,10 +6,9 @@ import orjson import quantile -from aioprometheus.mypy_types import LabelsType, NumericValueType - from . import histogram from .metricdict import MetricDict +from .mypy_types import LabelsType, NumericValueType METRIC_NAME_RE = re.compile(r"^[a-zA-Z_:][a-zA-Z0-9_:]*$") RESTRICTED_LABELS_NAMES = ("job",) diff --git a/src/aioprometheus/mypy_types.py b/src/aioprometheus/mypy_types.py index a69e7f8..79abc1f 100644 --- a/src/aioprometheus/mypy_types.py +++ b/src/aioprometheus/mypy_types.py @@ -5,7 +5,7 @@ import quantile -from aioprometheus import histogram +from . import histogram LabelsType = Dict[str, str] NumericValueType = Union[int, float, histogram.Histogram, quantile.Estimator] diff --git a/src/aioprometheus/renderer.py b/src/aioprometheus/renderer.py index 8f524c1..01b678c 100644 --- a/src/aioprometheus/renderer.py +++ b/src/aioprometheus/renderer.py @@ -1,7 +1,6 @@ from typing import Sequence, Tuple -from aioprometheus import Registry - +from .collectors import Registry from .formats.base import IFormatter from .negotiator import negotiate diff --git a/src/aioprometheus/service.py b/src/aioprometheus/service.py index f715381..95afa69 100644 --- a/src/aioprometheus/service.py +++ b/src/aioprometheus/service.py @@ -25,6 +25,8 @@ DEFAULT_METRICS_PATH = "/metrics" +METRICS_URL_KEY: aiohttp.web.AppKey = aiohttp.web.AppKey("metrics_url") + class Service: """ @@ -135,18 +137,16 @@ async def start( self._app = aiohttp.web.Application() self._metrics_url = metrics_url - self._app["metrics_url"] = metrics_url + self._app[METRICS_URL_KEY] = metrics_url self._app.router.add_route(GET, metrics_url, self.handle_metrics) self._app.router.add_route(GET, self._root_url, self.handle_root) self._app.router.add_route(GET, "/robots.txt", self.handle_robots) - self._runner = aiohttp.web.AppRunner(self._app) + self._runner = aiohttp.web.AppRunner(self._app, shutdown_timeout=2.0) await self._runner.setup() self._https = ssl is not None try: - self._site = aiohttp.web.TCPSite( - self._runner, addr, port, ssl_context=ssl, shutdown_timeout=2.0 - ) + self._site = aiohttp.web.TCPSite(self._runner, addr, port, ssl_context=ssl) await self._site.start() except Exception: logger.exception("error creating metrics server") @@ -193,7 +193,7 @@ async def handle_root( Serves a trivial page with a link to the metrics. Use this if ever you need to point a health check at your the service. """ - metrics_url = request.app["metrics_url"] + metrics_url = request.app[METRICS_URL_KEY] return aiohttp.web.Response( content_type="text/html", text=f"metrics", diff --git a/tests/test_aiohttp.py b/tests/test_aiohttp.py index 46a0fe5..6d21097 100644 --- a/tests/test_aiohttp.py +++ b/tests/test_aiohttp.py @@ -43,10 +43,10 @@ async def handle_metrics(request): [aiohttp.web.get("/", index), aiohttp.web.get("/metrics", handle_metrics)] ) - runner = aiohttp.web.AppRunner(app) + runner = aiohttp.web.AppRunner(app, shutdown_timeout=1.0) await runner.setup() - site = aiohttp.web.TCPSite(runner, "127.0.0.1", 0, shutdown_timeout=1.0) + site = aiohttp.web.TCPSite(runner, "127.0.0.1", 0) await site.start() # Fetch ephemeral port that was bound. diff --git a/tests/test_dist.bash b/tests/test_dist.bash index 8dac0f3..ee6e47f 100755 --- a/tests/test_dist.bash +++ b/tests/test_dist.bash @@ -13,7 +13,7 @@ echo "Removing any old artefacts" rm -rf test_venv echo "Creating test virtual environment" -python -m venv test_venv +python3.11 -m venv test_venv echo "Entering test virtual environment" source test_venv/bin/activate @@ -22,7 +22,7 @@ echo "Upgrading pip" pip install pip --upgrade echo "Install test dependencies and extras to check integrations" -pip install asynctest requests aiohttp fastapi quart httpx +pip install asynctest requests aiohttp fastapi quart httpx aiohttp_basicauth echo "Installing $RELEASE_ARCHIVE" pip install $RELEASE_ARCHIVE[aiohttp,starlette,quart]