[observability] add ServerArgs.stat_loggers for pluggable metrics backend (#24610)

Signed-off-by: Dongjun Na <kmu5544616@gmail.com>
This commit is contained in:
Dongjun Na
2026-05-24 22:41:28 +08:00
committed by GitHub
parent b6f71d5850
commit 9d50cd9742
9 changed files with 411 additions and 22 deletions
@@ -1,3 +1,4 @@
import os
import unittest
from typing import Dict, List
@@ -8,6 +9,8 @@ from prometheus_client.samples import Sample
from sglang.srt.environ import envs
from sglang.srt.observability.metrics_collector import (
ROUTING_KEY_REQ_COUNT_BUCKET_BOUNDS,
STAT_LOGGER_ROLE_SCHEDULER,
SchedulerMetricsCollector,
compute_routing_key_stats,
)
from sglang.srt.utils import kill_process_tree
@@ -274,6 +277,66 @@ def _check_metrics_positive(test_case, metrics, metrics_to_check):
test_case.assertGreater(value, 0, f"{metric_name} {labels}")
_DI_MARKER_PATH = "/tmp/sglang_di_test_marker"
class _MarkingSchedulerCollector(SchedulerMetricsCollector):
"""Records its own instantiation to a file so the test can verify the
custom subclass was used in the scheduler subprocess.
Defined at module level so it is picklable into the scheduler process.
Cross-process signalling uses a filesystem marker because the scheduler
runs in its own subprocess and cannot share in-memory state with the
test runner.
"""
def __init__(self, *args, **kwargs):
with open(_DI_MARKER_PATH, "w") as f:
f.write("scheduler_collector_initialized\n")
super().__init__(*args, **kwargs)
class TestStatLoggersDI(CustomTestCase):
"""Verify that a custom MetricsCollector subclass passed through
``ServerArgs.stat_loggers`` is the one instantiated inside the
scheduler subprocess."""
def setUp(self) -> None:
try:
os.unlink(_DI_MARKER_PATH)
except FileNotFoundError:
pass
def tearDown(self) -> None:
try:
os.unlink(_DI_MARKER_PATH)
except FileNotFoundError:
pass
def test_engine_custom_scheduler_collector(self):
import sglang as sgl
engine = sgl.Engine(
model_path=_MODEL_NAME,
enable_metrics=True,
stat_loggers={
STAT_LOGGER_ROLE_SCHEDULER: _MarkingSchedulerCollector,
},
)
try:
# One small generation triggers scheduler init, which is where
# resolve_collector_class() picks the injected subclass.
engine.generate("Hello", {"max_new_tokens": 4})
finally:
engine.shutdown()
self.assertTrue(
os.path.exists(_DI_MARKER_PATH),
"Custom SchedulerMetricsCollector was not instantiated; "
"stat_loggers DI did not take effect.",
)
class TestComputeRoutingKeyStats(unittest.TestCase):
def test_empty(self):
num_unique, req_counts = compute_routing_key_stats([])