Skip to content

Commit b055006

Browse files
authored
test(BA-5824): add unit tests for FixedQueryBuilder (#11273)
1 parent bbb8e02 commit b055006

2 files changed

Lines changed: 150 additions & 0 deletions

File tree

changes/11273.test.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add unit tests for FixedQueryBuilder
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"""
2+
Tests for FixedQueryBuilder: query building, metric type classification,
3+
and live stat query construction.
4+
"""
5+
6+
import re
7+
from uuid import UUID
8+
9+
import pytest
10+
11+
from ai.backend.common.clients.prometheus import (
12+
FixedQueryBuilder,
13+
)
14+
from ai.backend.common.clients.prometheus.fixed_query_builder import _regex_union
15+
from ai.backend.common.clients.prometheus.metric_types import (
16+
ContainerMetricOptionalLabel,
17+
MetricType,
18+
)
19+
from ai.backend.common.clients.prometheus.preset import LabelMatcher, LabelOperator, MetricPreset
20+
from ai.backend.common.clients.prometheus.types import ValueType
21+
from ai.backend.common.types import KernelId
22+
23+
24+
class TestGetContainerMetricType:
25+
@pytest.fixture
26+
def builder(self) -> FixedQueryBuilder:
27+
return FixedQueryBuilder("1m")
28+
29+
@pytest.mark.parametrize(
30+
("metric_name", "value_type", "expected"),
31+
[
32+
("mem", ValueType.CURRENT, MetricType.GAUGE),
33+
("cpu_util", ValueType.CAPACITY, MetricType.GAUGE),
34+
("net_rx", ValueType.CURRENT, MetricType.RATE),
35+
("net_tx", ValueType.CURRENT, MetricType.RATE),
36+
("net_rx", ValueType.CAPACITY, MetricType.RATE),
37+
("cpu_util", ValueType.CURRENT, MetricType.DIFF),
38+
],
39+
ids=[
40+
"gauge-unknown-metric",
41+
"gauge-capacity-overrides-diff",
42+
"rate-net_rx",
43+
"rate-net_tx",
44+
"rate-precedence-over-value_type",
45+
"diff-cpu_util-current",
46+
],
47+
)
48+
def test_metric_type_classification(
49+
self,
50+
builder: FixedQueryBuilder,
51+
metric_name: str,
52+
value_type: ValueType,
53+
expected: MetricType,
54+
) -> None:
55+
label = ContainerMetricOptionalLabel(value_type=value_type)
56+
assert builder.get_container_metric_type(metric_name, label) == expected
57+
58+
59+
class TestGetContainerMetricQuery:
60+
@pytest.fixture
61+
def builder(self) -> FixedQueryBuilder:
62+
return FixedQueryBuilder("5m")
63+
64+
def test_gauge_query_preset(self, builder: FixedQueryBuilder) -> None:
65+
label = ContainerMetricOptionalLabel(value_type=ValueType.CURRENT)
66+
67+
result = builder.get_container_metric_query("mem", label)
68+
69+
assert isinstance(result, MetricPreset)
70+
assert result.window == "5m"
71+
assert result.labels["container_metric_name"] == LabelMatcher.exact("mem")
72+
assert result.labels["value_type"] == LabelMatcher.exact("current")
73+
assert "value_type" in result.group_by
74+
75+
@pytest.mark.parametrize("metric_name", ["net_rx", "cpu_util"])
76+
def test_rate_based_query_uses_rate_function(
77+
self, builder: FixedQueryBuilder, metric_name: str
78+
) -> None:
79+
label = ContainerMetricOptionalLabel(value_type=ValueType.CURRENT)
80+
81+
rendered = builder.get_container_metric_query(metric_name, label).render()
82+
83+
assert "rate(" in rendered
84+
assert "[5m]" in rendered
85+
86+
def test_query_with_optional_labels(self, builder: FixedQueryBuilder) -> None:
87+
kid = UUID("12345678-1234-5678-1234-567812345678")
88+
label = ContainerMetricOptionalLabel(
89+
value_type=ValueType.CURRENT,
90+
kernel_id=kid,
91+
)
92+
93+
result = builder.get_container_metric_query("mem", label)
94+
95+
assert result.labels["kernel_id"] == LabelMatcher.exact(str(kid))
96+
assert "kernel_id" in result.group_by
97+
98+
99+
class TestGetContainerLiveStatQueries:
100+
def test_kernel_id_regex_filter(self) -> None:
101+
builder = FixedQueryBuilder("1m")
102+
kid1 = KernelId(UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"))
103+
kid2 = KernelId(UUID("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"))
104+
105+
result = builder.get_container_live_stat_queries([kid1, kid2])
106+
107+
matcher = result.gauge.labels["kernel_id"]
108+
assert matcher.operator == LabelOperator.REGEX
109+
pattern = re.compile(matcher.value)
110+
assert pattern.fullmatch(str(kid1))
111+
assert pattern.fullmatch(str(kid2))
112+
assert not pattern.fullmatch("cccccccc-cccc-cccc-cccc-cccccccccccc")
113+
114+
@pytest.mark.parametrize(
115+
("preset_attr", "expected_metrics"),
116+
[
117+
("diff", ["cpu_util"]),
118+
("rate", ["net_rx", "net_tx"]),
119+
],
120+
)
121+
def test_preset_filters_by_metric_name_and_value_type(
122+
self, preset_attr: str, expected_metrics: list[str]
123+
) -> None:
124+
builder = FixedQueryBuilder("1m")
125+
kid = KernelId(UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"))
126+
127+
result = builder.get_container_live_stat_queries([kid])
128+
labels = getattr(result, preset_attr).labels
129+
130+
assert labels["container_metric_name"].operator == LabelOperator.REGEX
131+
for metric in expected_metrics:
132+
assert metric in labels["container_metric_name"].value
133+
assert labels["value_type"] == LabelMatcher.exact("current")
134+
135+
def test_gauge_has_no_metric_name_filter(self) -> None:
136+
builder = FixedQueryBuilder("1m")
137+
kid = KernelId(UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"))
138+
139+
result = builder.get_container_live_stat_queries([kid])
140+
141+
assert "container_metric_name" not in result.gauge.labels
142+
143+
144+
class TestRegexUnion:
145+
def test_escapes_special_chars(self) -> None:
146+
result = _regex_union(["a.b", "c+d"])
147+
assert r"a\.b" in result
148+
assert r"c\+d" in result
149+
assert "|" in result

0 commit comments

Comments
 (0)