Skip to content

[Fix] metrics: record HTTP request metrics in tokenizer workers - #44663

Merged
hnyls2002 merged 5 commits into
sgl-project:mainfrom
hope1262946533:fix-http-metrics-multi-tokenizer
Oct 19, 2026
Merged

hnyls2002 merged 5 commits into
sgl-project:mainfrom
hope1262946533:fix-http-metrics-multi-tokenizer

Conversation

@hope1262946533

@hope1262946533 hope1262946533 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

With --enable-metrics and --tokenizer-worker-num > 1, the HTTP request
metrics are silently missing from /metrics:

  • sglang:http_requests_total
  • sglang:http_responses_total
  • sglang:http_requests_active
  • sglang:routing_keys_active

Per-endpoint request rates, in-flight counts and 5xx ratios therefore cannot be
monitored on a multi-tokenizer deployment, even though /metrics itself is
served fine.

Modifications

The track-response middleware was registered from _setup_and_run_http_server,
which runs in the parent process. With --tokenizer-worker-num > 1 the server
is started with uvicorn.run("...:app", workers=N): uvicorn spawns the HTTP
workers and each one re-imports http_server, so the app they actually serve is
a fresh object that never received the middleware.

  • Register the middleware while the module is imported (next to the existing
    CORSMiddleware / RequestDecompressionMiddleware registrations) so that
    every tokenizer worker picks it up, and drop the parent-process registration.
  • Create the metrics themselves in a separate step, init_http_request_metrics,
    called from the lifespan once the process has published its config.
    prometheus_client fixes its value backend when it is first imported, and
    SGLang only sets PROMETHEUS_MULTIPROC_DIR in _set_envs_and_config, which
    runs after http_server has been imported. Creating the metrics at import
    time would therefore leave them in the in-memory registry, where the
    MultiProcessCollector behind /metrics never sees them -- including in
    single-tokenizer mode.
  • The middleware now runs inside each worker, so also expose that worker's
    in-flight request count as sglang:http_worker_active_requests{worker_id}.
    This is the number to watch when a shared listening socket concentrates
    connections on a few workers.

A unit test in test/registered/unit/observability/test_http_request_metrics.py
covers all three: that the middleware is on the module-level app, that importing
http_server does not pull prometheus_client in early, and that a worker
process records the counters and labels the per-worker gauge.

Accuracy Tests

N/A - metrics only.

Speed Tests and Profiling

N/A - no change to the inference path. With metrics enabled the per-request work
is unchanged; with metrics disabled the middleware returns after one config
lookup.

Checklist


CI States

Latest PR Test (Base): ⏳ Run #39325198175
Latest PR Test (Extra): ❌ Run #39325198198
Latest PR Test (AMD ROCm 10): ⏳ Run #39325198377

The track-response middleware was registered from
`_setup_and_run_http_server`, which runs in the parent process. With
`--tokenizer-worker-num > 1` uvicorn spawns the HTTP workers and each of them
re-imports `http_server`, so the app they actually serve never had the
middleware. `sglang:http_requests_total`, `sglang:http_responses_total`,
`sglang:http_requests_active` and `sglang:routing_keys_active` were therefore
missing from `/metrics` whenever more than one tokenizer worker was used.

Register the middleware while the module is imported instead, so that every
worker picks it up. The metrics themselves are created in a separate step,
`init_http_request_metrics`, called from the lifespan once the process has
published its config: prometheus_client fixes its value backend when it is
first imported, and SGLang only sets `PROMETHEUS_MULTIPROC_DIR` in
`_set_envs_and_config`, which runs after `http_server` has been imported.
Creating them at import time would leave them in the in-memory registry that
the `/metrics` endpoint never reads, breaking single-tokenizer mode.

Because the middleware now runs inside each worker, also expose that worker's
in-flight request count as `sglang:http_worker_active_requests{worker_id}`.
@hope1262946533
hope1262946533 force-pushed the fix-http-metrics-multi-tokenizer branch from 7a39e75 to 7d42e6b Compare October 16, 2026 05:02
…i-tokenizer

# Conflicts:
#	python/sglang/srt/entrypoints/http_server.py
…sion server

- Remove sglang:http_worker_active_requests: out of scope, and its label was the worker PID.
- Remove the unused extra_labels parameter.
- The diffusion HTTP server registers the same middleware, so it now also calls
  init_http_request_metrics(); without it the server recorded no HTTP metrics.
- Run the test file's TestCase from __main__ so CI executes it.
…t_metrics.py

Pure move of RefCountedGauge, the metrics holder, init_http_request_metrics,
add_prometheus_track_response_middleware and _get_fastapi_request_path out of
utils/common.py; only imports change.
- Rename add_prometheus_track_response_middleware to
  add_http_request_metrics_middleware, the pair of init_http_request_metrics.
- Name the metric attributes after their metrics (requests_total, responses_total).
- Make init_http_request_metrics idempotent.
- Merge the three tests into one: a request on the module-level app is counted
  only after init. Drop the subprocess import check, which any unrelated
  top-level prometheus_client import would break.
@hnyls2002

Copy link
Copy Markdown
Collaborator

/rerun-test test_http_request_metrics.py registered/observability/test_metrics.py registered/tokenizer/test_multi_tokenizer.py test_diffusion_metrics.py registered/openai_server/basic/test_http2_server.py registered/e2e/entrypoints/test_engine_multi_tokenizer.py

@hnyls2002

Copy link
Copy Markdown
Collaborator

/tag-and-rerun-ci

@github-actions github-actions Bot added the run-ci CI: run the baseline test suite on this PR label Oct 19, 2026
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

🚀 ubuntu-latest (1 test): ✅ View workflow run

cd test/ && python3 registered/unit/observability/test_http_request_metrics.py

🚀 1-gpu-5090 (2 tests): ✅ View workflow run

cd test/ && python3 registered/observability/test_metrics.py
cd test/ && python3 registered/openai_server/basic/test_http2_server.py

🚀 1-gpu-h100 (2 tests): ✅ View workflow run

cd test/ && python3 registered/tokenizer/test_multi_tokenizer.py
cd test/ && python3 registered/e2e/entrypoints/test_engine_multi_tokenizer.py

🚀 1-gpu-h100 (1 test): ✅ View workflow run

python3 -m pytest python/sglang/multimodal_gen/test/unit/test_diffusion_metrics.py -x

@hnyls2002
hnyls2002 merged commit 81ac153 into sgl-project:main Oct 19, 2026
200 of 289 checks passed
detain pushed a commit to detain/sglang that referenced this pull request Oct 29, 2026
…project#44663)

Cherry-pick of upstream 81ac153.
Middleware registration moves to module-import time so tokenizer workers
that re-import http_server record http_requests/responses/active and
routing_keys; init stays in lifespan (after PROMETHEUS_MULTIPROC_DIR).

PORT NOTE: inert-but-correct at our config -- we do not pass
--tokenizer-worker-num (default), so the worker-side registration path
does not run today; the fix matters when the entry-201 (sgl-project#39716)
worker-owned preprocessing story scales workers out.

Rides with our entry-201 monitoring story.
(cherry picked from commit 81ac153)
(cherry picked from commit 5469a4b26c268daab1d3d7e1e9a349860d140753)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diffusion SGLang Diffusion run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants