test_metrics_registry.py 8.69 KB
Newer Older
1
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# SPDX-License-Identifier: Apache-2.0

"""Tests for Python MetricsRegistry bindings.

This test suite verifies that Python can create, introspect, and use Prometheus
metrics through the Dynamo MetricsRegistry interface.
"""

import pytest


async def get_metrics_runtime(runtime, endpoint_name):
    """Helper to create a unique metrics runtime for each test."""
    namespace = runtime.namespace("test_metrics_ns")
    component = namespace.component("test_metrics_comp")
    endpoint = component.endpoint(endpoint_name)
    return endpoint.metrics


pytestmark = pytest.mark.pre_merge


@pytest.mark.asyncio
@pytest.mark.forked
async def test_counter_introspection(runtime):
    """Test Counter metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_counter_introspection")

    counter = metrics_runtime.create_counter(
        "test_counter", "A test counter", [("env", "test")]  # constant labels
    )

    # Test name() method
    name = counter.name()
    assert isinstance(name, str)
    assert "test_counter" in name
    assert name.endswith("test_counter")

    # Test const_labels() method
    labels = counter.const_labels()
    assert isinstance(labels, dict)
    assert "env" in labels
    assert labels["env"] == "test"
    assert "dynamo_namespace" in labels
    assert labels["dynamo_namespace"] == "test_metrics_ns"


@pytest.mark.asyncio
@pytest.mark.forked
async def test_intcounter_introspection(runtime):
    """Test IntCounter metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_intcounter_introspection")
    counter = metrics_runtime.create_intcounter(
        "test_int_counter", "A test int counter", [("type", "integer")]
    )

    name = counter.name()
    assert isinstance(name, str)
    assert "test_int_counter" in name

    labels = counter.const_labels()
    assert isinstance(labels, dict)
    assert labels["type"] == "integer"


@pytest.mark.asyncio
@pytest.mark.forked
async def test_gauge_introspection(runtime):
    """Test Gauge metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_gauge_introspection")
    gauge = metrics_runtime.create_gauge(
        "test_gauge", "A test gauge", [("unit", "bytes")]
    )

    name = gauge.name()
    assert isinstance(name, str)
    assert "test_gauge" in name

    labels = gauge.const_labels()
    assert isinstance(labels, dict)
    assert labels["unit"] == "bytes"


@pytest.mark.asyncio
@pytest.mark.forked
async def test_intgauge_introspection(runtime):
    """Test IntGauge metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_intgauge_introspection")
    gauge = metrics_runtime.create_intgauge(
        "test_int_gauge", "A test int gauge", []  # no constant labels
    )

    name = gauge.name()
    assert isinstance(name, str)
    assert "test_int_gauge" in name

    labels = gauge.const_labels()
    assert isinstance(labels, dict)
    # Should still have hierarchy labels
    assert "dynamo_namespace" in labels


@pytest.mark.asyncio
@pytest.mark.forked
async def test_histogram_introspection(runtime):
    """Test Histogram metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_histogram_introspection")
    histogram = metrics_runtime.create_histogram(
        "test_histogram", "A test histogram", [("method", "POST")]
    )

    name = histogram.name()
    assert isinstance(name, str)
    assert "test_histogram" in name

    labels = histogram.const_labels()
    assert isinstance(labels, dict)
    assert labels["method"] == "POST"


@pytest.mark.asyncio
@pytest.mark.forked
async def test_countervec_introspection(runtime):
    """Test CounterVec metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_countervec_introspection")
    counter_vec = metrics_runtime.create_countervec(
        "test_counter_vec",
        "A test counter vec",
        ["worker_id", "status"],  # variable labels
        [("cluster", "prod")],  # constant labels
    )

    # Test name()
    name = counter_vec.name()
    assert isinstance(name, str)
    assert "test_counter_vec" in name

    # Test const_labels()
    const_labels = counter_vec.const_labels()
    assert isinstance(const_labels, dict)
    assert const_labels["cluster"] == "prod"
    assert "dynamo_namespace" in const_labels

    # Test variable_labels()
    var_labels = counter_vec.variable_labels()
    assert isinstance(var_labels, list)
    assert len(var_labels) == 2
    assert "worker_id" in var_labels
    assert "status" in var_labels


@pytest.mark.asyncio
@pytest.mark.forked
async def test_intcountervec_introspection(runtime):
    """Test IntCounterVec metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(
        runtime, "ep_intcountervec_introspection"
    )
    counter_vec = metrics_runtime.create_intcountervec(
        "test_int_counter_vec",
        "A test int counter vec",
        ["region", "zone"],
        [],  # no constant labels
    )

    name = counter_vec.name()
    assert "test_int_counter_vec" in name

    const_labels = counter_vec.const_labels()
    assert isinstance(const_labels, dict)

    var_labels = counter_vec.variable_labels()
    assert len(var_labels) == 2
    assert "region" in var_labels
    assert "zone" in var_labels


@pytest.mark.asyncio
@pytest.mark.forked
async def test_gaugevec_introspection(runtime):
    """Test GaugeVec metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_gaugevec_introspection")
    gauge_vec = metrics_runtime.create_gaugevec(
        "test_gauge_vec", "A test gauge vec", ["instance", "job"], [("env", "staging")]
    )

    name = gauge_vec.name()
    assert "test_gauge_vec" in name

    const_labels = gauge_vec.const_labels()
    assert const_labels["env"] == "staging"

    var_labels = gauge_vec.variable_labels()
    assert len(var_labels) == 2
    assert "instance" in var_labels
    assert "job" in var_labels


@pytest.mark.asyncio
@pytest.mark.forked
async def test_intgaugevec_introspection(runtime):
    """Test IntGaugeVec metric introspection methods."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_intgaugevec_introspection")
    gauge_vec = metrics_runtime.create_intgaugevec(
        "test_int_gauge_vec",
        "A test int gauge vec",
        ["device", "partition"],
        [("datacenter", "us-west")],
    )

    name = gauge_vec.name()
    assert "test_int_gauge_vec" in name

    const_labels = gauge_vec.const_labels()
    assert const_labels["datacenter"] == "us-west"

    var_labels = gauge_vec.variable_labels()
    assert len(var_labels) == 2
    assert "device" in var_labels
    assert "partition" in var_labels


@pytest.mark.asyncio
@pytest.mark.forked
async def test_metric_operations(runtime):
    """Test that metrics can be used after introspection."""
    metrics_runtime = await get_metrics_runtime(runtime, "ep_metric_operations")
    # Counter operations
    counter = metrics_runtime.create_intcounter("ops_counter", "Operations counter", [])
    counter.inc()
    counter.inc_by(5)
    assert counter.get() == 6

    # Gauge operations
    gauge = metrics_runtime.create_intgauge(
        "connections_gauge", "Connections gauge", []
    )
    gauge.set(10)
    assert gauge.get() == 10
    gauge.inc()
    assert gauge.get() == 11
    gauge.dec()
    assert gauge.get() == 10

    # Vec operations
    gauge_vec = metrics_runtime.create_intgaugevec(
        "worker_gauge_vec", "Worker gauge vec", ["worker_id"], []
    )
    gauge_vec.set(5, {"worker_id": "w1"})
    assert gauge_vec.get({"worker_id": "w1"}) == 5
    gauge_vec.inc({"worker_id": "w1"})
    assert gauge_vec.get({"worker_id": "w1"}) == 6


@pytest.mark.asyncio
@pytest.mark.forked
async def test_multiple_metrics_same_runtime(runtime):
    """Test creating multiple metrics in the same runtime."""
    metrics_runtime = await get_metrics_runtime(
        runtime, "ep_multiple_metrics_same_runtime"
    )
    counter1 = metrics_runtime.create_intcounter("counter1", "Counter 1", [])
    counter2 = metrics_runtime.create_intcounter("counter2", "Counter 2", [])
    gauge1 = metrics_runtime.create_gauge("gauge1", "Gauge 1", [])

    # All should have unique names
    names = {counter1.name(), counter2.name(), gauge1.name()}
    assert len(names) == 3

    # All should share the same hierarchy labels
    for metric in [counter1, counter2, gauge1]:
        labels = metric.const_labels()
        assert labels["dynamo_namespace"] == "test_metrics_ns"
        assert "dynamo_component" in labels  # Component name is test-specific
        assert labels["dynamo_endpoint"] == "ep_multiple_metrics_same_runtime"