Skip to content

Commit 67307bd

Browse files
banlorruizhang0101
andauthored
[Feat][Router] Add per-model request latency histogram (#940)
vllm:request_latency_seconds with server/model/status labels. observed once per request in finally, so http 4xx/5xx and exceptions both land as status=error. Refs #699. Signed-off-by: Mikhail Basov <Michael.S.Sinclair@protonmail.com> Co-authored-by: Rui Zhang <51696593+ruizhang0101@users.noreply.github.com>
1 parent 2211261 commit 67307bd

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

src/tests/test_metrics.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from vllm_router.services.metrics_service import request_latency_seconds
2+
3+
4+
def test_records_both_status_labels():
5+
request_latency_seconds.labels(
6+
server="http://test-engine:8000", model="test-model", status="success"
7+
).observe(0.5)
8+
request_latency_seconds.labels(
9+
server="http://test-engine:8000", model="test-model", status="error"
10+
).observe(30.0)
11+
12+
statuses_seen = {
13+
s.labels.get("status")
14+
for metric in request_latency_seconds.collect()
15+
for s in metric.samples
16+
if s.name == "vllm:request_latency_seconds_bucket"
17+
and s.labels.get("server") == "http://test-engine:8000"
18+
}
19+
20+
assert {"success", "error"} <= statuses_seen

src/vllm_router/services/metrics_service/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from prometheus_client import Counter, Gauge
1+
from prometheus_client import Counter, Gauge, Histogram
22

33
# --- Prometheus Gauges ---
44
# Existing metrics
@@ -63,3 +63,9 @@
6363
"Total request errors",
6464
["server", "model", "error_type"],
6565
)
66+
request_latency_seconds = Histogram(
67+
"vllm:request_latency_seconds",
68+
"End-to-end request latency observed at the router",
69+
["server", "model", "status"],
70+
buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, 120.0),
71+
)

src/vllm_router/services/request_service/request.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
num_incoming_requests_total,
7575
output_tokens_total,
7676
request_errors_total,
77+
request_latency_seconds,
7778
)
7879

7980
logger = init_logger(__name__)
@@ -298,6 +299,9 @@ async def process_request(
298299
# For non-streaming requests, collect the full response to cache it properly
299300
full_response = bytearray()
300301

302+
request_status = "success"
303+
http_status_code = None
304+
301305
try:
302306
async with request.app.state.aiohttp_client_wrapper().request(
303307
method=request.method,
@@ -306,6 +310,7 @@ async def process_request(
306310
data=body,
307311
timeout=aiohttp.ClientTimeout(total=None),
308312
) as backend_response:
313+
http_status_code = backend_response.status
309314
# Set response status on span if tracing
310315
if span is not None:
311316
span.set_attribute("http.status_code", backend_response.status)
@@ -325,10 +330,14 @@ async def process_request(
325330
full_response.extend(chunk)
326331
yield chunk
327332

333+
end_time = time.time()
328334
request.app.state.request_stats_monitor.on_request_complete(
329-
backend_url, request_id, time.time()
335+
backend_url, request_id, end_time
330336
)
331337

338+
if http_status_code is not None and http_status_code >= 400:
339+
request_status = "error"
340+
332341
# Track token usage for non-streaming requests
333342
if not is_streaming and full_response:
334343
try:
@@ -357,13 +366,17 @@ async def process_request(
357366
request.app.state.callbacks.post_request, request, full_response
358367
)
359368
except Exception as e:
369+
request_status = "error"
360370
# Track other errors
361371
request_errors_total.labels(
362372
server=backend_url, model=model_name, error_type=type(e).__name__
363373
).inc()
364374
end_span(span, error=e) if tracing_active else None
365375
raise
366376
finally:
377+
request_latency_seconds.labels(
378+
server=backend_url, model=model_name, status=request_status
379+
).observe(time.time() - start_time)
367380
end_span(span) if tracing_active else None
368381

369382

0 commit comments

Comments
 (0)