diff --git a/vllm/entrypoints/serve/instrumentator/metrics.py b/vllm/entrypoints/serve/instrumentator/metrics.py index 5231451383a..5cba364f5d9 100644 --- a/vllm/entrypoints/serve/instrumentator/metrics.py +++ b/vllm/entrypoints/serve/instrumentator/metrics.py @@ -7,11 +7,48 @@ import regex as re from fastapi import FastAPI, Response from prometheus_client import make_asgi_app from prometheus_fastapi_instrumentator import Instrumentator -from starlette.routing import Mount +from prometheus_fastapi_instrumentator import routing as _pfi_routing +from starlette.routing import Match, Mount +from starlette.types import Scope from vllm.v1.metrics.prometheus import get_prometheus_registry +def _patch_instrumentator_route_walk() -> None: + """Make prometheus-fastapi-instrumentator's route walk tolerate routes + without a ``.path``. + + FastAPI >= 0.137 stores lazy ``_IncludedRouter`` objects in ``app.routes``; + these are ``BaseRoute`` subclasses with no ``.path`` attribute. The + instrumentator's ``_get_route_name`` (up to 8.0.0) reads ``route.path`` + unconditionally, so every request raises ``AttributeError`` in the metrics + middleware and the server returns 500 (e.g. ``/health`` never goes ready). + Skip path-less routes; this only affects the metric handler label, not + request routing. Idempotent. + """ + + def _get_route_name(scope: Scope, routes, route_name=None): + for route in routes: + if getattr(route, "path", None) is None: + continue + match, child_scope = route.matches(scope) + if match == Match.FULL: + route_name = route.path + child_scope = {**scope, **child_scope} + if isinstance(route, Mount) and route.routes: + child = _get_route_name(child_scope, route.routes, route_name) + route_name = None if child is None else route_name + child + return route_name + elif match == Match.PARTIAL and route_name is None: + route_name = route.path + return None + + _pfi_routing._get_route_name = _get_route_name + + +_patch_instrumentator_route_walk() + + class PrometheusResponse(Response): media_type = prometheus_client.CONTENT_TYPE_LATEST