@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 8d52f2b3a7
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[ray][metrics] Replace ':' with '_' for OpenTelemetry compatibility in Ray (#25439)

Signed-off-by: Seiji Eicher <seiji@anyscale.com> Signed-off-by: Seiji Eicher <58963096+eicherseiji@users.noreply.github.com> Co-authored-by: Rui Qiao <161574667+ruisearch42@users.noreply.github.com>

Seiji Eicher committed Sep 26, 2025 at 09:43 UTC 8d52f2b3a7b75c9efe9eba906ab37780f6e4e5f3
2 files changed +57 -1
tests/v1/metrics/test_ray_metrics.py
+38 -1
@@ -8,7 +8,8 @@ import ray
8 from vllm.config import ModelDType
9 from vllm.sampling_params import SamplingParams
10 from vllm.v1.engine.async_llm import AsyncEngineArgs, AsyncLLM
11 -from vllm.v1.metrics.ray_wrappers import RayPrometheusStatLogger
11 +from vllm.v1.metrics.ray_wrappers import (RayPrometheusMetric,
12 + RayPrometheusStatLogger)
13
14
15 @pytest.fixture(scope="function", autouse=True)
@@ -65,3 +66,39 @@ def test_engine_log_metrics_ray(
66 # Create the actor and call the async method
67 actor = EngineTestActor.remote() # type: ignore[attr-defined]
68 ray.get(actor.run.remote())
69 +
70 +
71 +def test_sanitized_opentelemetry_name():
72 + """Test the metric name sanitization logic for Ray."""
73 +
74 + # Only a-z, A-Z, 0-9, _, test valid characters are preserved
75 + valid_name = "valid_metric_123_abcDEF"
76 + assert RayPrometheusMetric._get_sanitized_opentelemetry_name(
77 + valid_name) == valid_name
78 +
79 + # Test dash, dot, are replaced
80 + name_with_dash_dot = "metric-name.test"
81 + expected = "metric_name_test"
82 + assert RayPrometheusMetric._get_sanitized_opentelemetry_name(
83 + name_with_dash_dot) == expected
84 +
85 + # Test colon is replaced with underscore
86 + name_with_colon = "metric:name"
87 + expected = "metric_name"
88 + assert RayPrometheusMetric._get_sanitized_opentelemetry_name(
89 + name_with_colon) == expected
90 +
91 + # Test multiple invalid characters are replaced
92 + name_with_invalid = "metric:name@with#special%chars"
93 + expected = "metric_name_with_special_chars"
94 + assert RayPrometheusMetric._get_sanitized_opentelemetry_name(
95 + name_with_invalid) == expected
96 +
97 + # Test mixed valid and invalid characters
98 + complex_name = "vllm:engine_stats/time.latency_ms-99p"
99 + expected = "vllm_engine_stats_time_latency_ms_99p"
100 + assert RayPrometheusMetric._get_sanitized_opentelemetry_name(
101 + complex_name) == expected
102 +
103 + # Test empty string
104 + assert RayPrometheusMetric._get_sanitized_opentelemetry_name("") == ""
vllm/v1/metrics/ray_wrappers.py
+19
@@ -11,6 +11,7 @@ try:
11 from ray.util.metrics import Metric
12 except ImportError:
13 ray_metrics = None
14 +import regex as re
15
16
17 class RayPrometheusMetric:
@@ -42,6 +43,21 @@ class RayPrometheusMetric:
43
44 return self
45
46 + @staticmethod
47 + def _get_sanitized_opentelemetry_name(name: str) -> str:
48 + """
49 + For compatibility with Ray + OpenTelemetry, the metric name must be
50 + sanitized. In particular, this replaces disallowed character (e.g., ':')
51 + with '_' in the metric name.
52 + Allowed characters: a-z, A-Z, 0-9, _
53 +
54 + # ruff: noqa: E501
55 + Ref: https://github.com/open-telemetry/opentelemetry-cpp/blob/main/sdk/src/metrics/instrument_metadata_validator.cc#L22-L23
56 + Ref: https://github.com/ray-project/ray/blob/master/src/ray/stats/metric.cc#L107
57 + """
58 +
59 + return re.sub(r"[^a-zA-Z0-9_]", "_", name)
60 +
61
62 class RayGaugeWrapper(RayPrometheusMetric):
63 """Wraps around ray.util.metrics.Gauge to provide same API as
@@ -58,6 +74,7 @@ class RayGaugeWrapper(RayPrometheusMetric):
74 # implemented at the observability layer (Prometheus/Grafana).
75 del multiprocess_mode
76 labelnames_tuple = tuple(labelnames) if labelnames else None
77 + name = self._get_sanitized_opentelemetry_name(name)
78 self.metric = ray_metrics.Gauge(name=name,
79 description=documentation,
80 tag_keys=labelnames_tuple)
@@ -79,6 +96,7 @@ class RayCounterWrapper(RayPrometheusMetric):
96 documentation: Optional[str] = "",
97 labelnames: Optional[list[str]] = None):
98 labelnames_tuple = tuple(labelnames) if labelnames else None
99 + name = self._get_sanitized_opentelemetry_name(name)
100 self.metric = ray_metrics.Counter(name=name,
101 description=documentation,
102 tag_keys=labelnames_tuple)
@@ -99,6 +117,7 @@ class RayHistogramWrapper(RayPrometheusMetric):
117 labelnames: Optional[list[str]] = None,
118 buckets: Optional[list[float]] = None):
119 labelnames_tuple = tuple(labelnames) if labelnames else None
120 + name = self._get_sanitized_opentelemetry_name(name)
121 boundaries = buckets if buckets else []
122 self.metric = ray_metrics.Histogram(name=name,
123 description=documentation,