Skip to content

Commit 5345b7a

Browse files
committed
feat: added integration tests
Signed-off-by: Niladri Adhikary <niladrix719@gmail.com>
1 parent 8920b88 commit 5345b7a

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

tests/integration/src/querier/03_metrics.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,3 +966,167 @@ def test_metrics_fill_zero_formula_with_group_by(
966966
expected_by_ts=expectations[group],
967967
context=f"metrics/fillZero/F1/{group}",
968968
)
969+
970+
971+
972+
def test_metrics_heatmap(
973+
signoz: types.SigNoz,
974+
create_user_admin: None, # pylint: disable=unused-argument
975+
get_token: Callable[[str, str], str],
976+
insert_metrics: Callable[[List[Metrics]], None],
977+
) -> None:
978+
"""
979+
Test heatmap query with metrics.
980+
"""
981+
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
982+
metric_name = "test_heatmap_multi_bucket"
983+
984+
metrics: List[Metrics] = []
985+
986+
# t-3: First histogram
987+
# Distribution: 10 in 0-10, 10 in 10-50, 10 in 50-100, 20 in 100+
988+
for le, value in [("10", 10.0), ("50", 20.0), ("100", 30.0), ("+Inf", 50.0)]:
989+
metrics.append(
990+
Metrics(
991+
metric_name=metric_name,
992+
labels={"service": "test", "le": le},
993+
timestamp=now - timedelta(minutes=3),
994+
value=value,
995+
temporality="Cumulative",
996+
type_="Histogram",
997+
)
998+
)
999+
1000+
# t-2: Second histogram
1001+
# Total: 30 in 0-10, 40 in 10-50, 50 in 50-100, 90 in 100+
1002+
for le, value in [("10", 30.0), ("50", 60.0), ("100", 90.0), ("+Inf", 150.0)]:
1003+
metrics.append(
1004+
Metrics(
1005+
metric_name=metric_name,
1006+
labels={"service": "test", "le": le},
1007+
timestamp=now - timedelta(minutes=2),
1008+
value=value,
1009+
temporality="Cumulative",
1010+
type_="Histogram",
1011+
)
1012+
)
1013+
1014+
# t-1: Third histogram
1015+
# Total: 40 in 0-10, 70 in 10-50, 100 in 50-100, 170 in 100+
1016+
for le, value in [("10", 40.0), ("50", 80.0), ("100", 120.0), ("+Inf", 200.0)]:
1017+
metrics.append(
1018+
Metrics(
1019+
metric_name=metric_name,
1020+
labels={"service": "test", "le": le},
1021+
timestamp=now - timedelta(minutes=1),
1022+
value=value,
1023+
temporality="Cumulative",
1024+
type_="Histogram",
1025+
)
1026+
)
1027+
1028+
insert_metrics(metrics)
1029+
1030+
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
1031+
1032+
start_ms = int((now - timedelta(minutes=4)).timestamp() * 1000)
1033+
end_ms = int(now.timestamp() * 1000)
1034+
1035+
response = requests.post(
1036+
signoz.self.host_configs["8080"].get("/api/v5/query_range"),
1037+
timeout=5,
1038+
headers={"authorization": f"Bearer {token}"},
1039+
json={
1040+
"schemaVersion": "v1",
1041+
"start": start_ms,
1042+
"end": end_ms,
1043+
"requestType": "heatmap",
1044+
"compositeQuery": {
1045+
"queries": [
1046+
{
1047+
"type": "builder_query",
1048+
"spec": {
1049+
"name": "A",
1050+
"signal": "metrics",
1051+
"aggregations": [
1052+
{
1053+
"metricName": metric_name,
1054+
"temporality": "cumulative",
1055+
"timeAggregation": "increase",
1056+
"spaceAggregation": "sum",
1057+
}
1058+
],
1059+
"stepInterval": 60,
1060+
"disabled": False,
1061+
},
1062+
}
1063+
]
1064+
},
1065+
"formatOptions": {"formatTableResultForUI": False},
1066+
},
1067+
)
1068+
1069+
assert response.status_code == HTTPStatus.OK, f"Expected 200, got {response.status_code}: {response.text}"
1070+
1071+
response_data = response.json()
1072+
assert response_data["status"] == "success", f"Query failed: {response_data}"
1073+
1074+
results = response_data["data"]["data"]["results"]
1075+
assert len(results) == 1, f"Expected 1 result, got {len(results)}"
1076+
1077+
aggregations = results[0]["aggregations"]
1078+
assert len(aggregations) == 1, f"Expected 1 aggregation, got {len(aggregations)}"
1079+
1080+
series = aggregations[0]["series"]
1081+
# Heatmap returns one series with time points
1082+
assert len(series) == 1, f"Expected 1 series for heatmap, got {len(series)}: {series}"
1083+
1084+
# Verify the series has proper structure
1085+
s = series[0]
1086+
assert "values" in s, f"Series missing 'values': {s}"
1087+
1088+
values = s["values"]
1089+
assert isinstance(values, list), f"Values should be a list, got {type(values)}"
1090+
1091+
# Should have exactly 3 time points (t-3, t-2, t-1)
1092+
assert len(values) == 3
1093+
1094+
# Verify structure and basic invariants
1095+
for val in values:
1096+
assert isinstance(val, dict)
1097+
assert "timestamp" in val
1098+
assert "bucket" in val
1099+
assert "values" in val
1100+
1101+
bucket = val["bucket"]
1102+
assert "bounds" in bucket
1103+
bounds = bucket["bounds"]
1104+
assert isinstance(bounds, list)
1105+
assert len(bounds) == 4 # 10, 50, 100, +Inf
1106+
1107+
counts = val["values"]
1108+
assert isinstance(counts, list)
1109+
assert len(counts) == len(bounds) - 1
1110+
1111+
for count in counts:
1112+
assert isinstance(count, (int, float))
1113+
assert count >= 0
1114+
1115+
# Verify bucket bounds
1116+
assert values[0]["bucket"]["bounds"] == [0, 10, 50, 100]
1117+
1118+
# Verify expected counts per timestamp
1119+
ts_min_3 = int((now - timedelta(minutes=3)).timestamp() * 1000)
1120+
ts_min_2 = int((now - timedelta(minutes=2)).timestamp() * 1000)
1121+
ts_min_1 = int((now - timedelta(minutes=1)).timestamp() * 1000)
1122+
1123+
expected_by_ts = {
1124+
ts_min_3: [10.0, 10.0, 10.0],
1125+
ts_min_2: [20.0, 20.0, 20.0],
1126+
ts_min_1: [10.0, 10.0, 10.0],
1127+
}
1128+
1129+
for val in values:
1130+
if val["timestamp"] in expected_by_ts:
1131+
assert val["values"] == expected_by_ts[val["timestamp"]]
1132+

0 commit comments

Comments
 (0)