fix(monitoring): bound Titan test metric labels

This commit is contained in:
jenkins 2026-09-02 23:50:23 -03:00
parent b34888abb3
commit 87c0a8b201
8 changed files with 56 additions and 5 deletions

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import hashlib
import json import json
import os import os
from glob import glob from glob import glob
@ -27,6 +28,8 @@ _infer_workspace_coverage_percent = _quality_helpers._infer_workspace_coverage_p
_load_optional_json = _quality_helpers._load_optional_json _load_optional_json = _quality_helpers._load_optional_json
_normalize_result_status = _quality_helpers._normalize_result_status _normalize_result_status = _quality_helpers._normalize_result_status
TEST_CASE_LABEL_MAX_BYTES = 240
def _escape_label(value: str) -> str: def _escape_label(value: str) -> str:
"""Escape a Prometheus label value without changing its content.""" """Escape a Prometheus label value without changing its content."""
@ -39,6 +42,17 @@ def _label_str(labels: dict[str, str]) -> str:
return "{" + ",".join(parts) + "}" if parts else "" return "{" + ",".join(parts) + "}" if parts else ""
def _bounded_test_name(value: str) -> str:
"""Keep test labels readable, unique, and safely below scraper limits."""
encoded = value.encode("utf-8")
if len(encoded) <= TEST_CASE_LABEL_MAX_BYTES:
return value
digest = hashlib.sha256(encoded).hexdigest()[:16]
suffix = f"...[sha256:{digest}]"
prefix = encoded[: TEST_CASE_LABEL_MAX_BYTES - len(suffix)]
return prefix.decode("utf-8", errors="ignore") + suffix
def _read_text(url: str) -> str: def _read_text(url: str) -> str:
"""Fetch a plain-text response body from the given URL.""" """Fetch a plain-text response body from the given URL."""
with urllib.request.urlopen(url, timeout=10) as response: with urllib.request.urlopen(url, timeout=10) as response:
@ -121,7 +135,7 @@ def _collect_junit_cases(pattern: str) -> list[tuple[str, str]]:
status = "failed" status = "failed"
elif test_case.find("skipped") is not None: elif test_case.find("skipped") is not None:
status = "skipped" status = "skipped"
cases.append((full_name, status)) cases.append((_bounded_test_name(full_name), status))
return cases return cases
@ -246,7 +260,7 @@ def _build_payload(
for test_name, test_status in test_cases: for test_name, test_status in test_cases:
labels = { labels = {
**test_case_base_labels, **test_case_base_labels,
"test": test_name, "test": _bounded_test_name(test_name),
"status": test_status, "status": test_status,
} }
lines.append( lines.append(

View File

@ -1632,7 +1632,7 @@ def testing_case_variable():
"label": "Test Case", "label": "Test Case",
"type": "query", "type": "query",
"query": ( "query": (
"query_result(topk(75, count by (test) (max_over_time(" "query_result(bottomk(75, min by (test) (min_over_time("
f'platform_quality:test_case_health_rate:percent_1h{{suite=~"${{suite:regex}}",branch!="",' f'platform_quality:test_case_health_rate:percent_1h{{suite=~"${{suite:regex}}",branch!="",'
f'branch=~"${{branch:regex}}",test!="",test!="__no_test_cases__",' f'branch=~"${{branch:regex}}",test!="",test!="__no_test_cases__",'
f'category!~"{PLATFORM_TEST_SUPPORT_CATEGORY_REGEX}"}}' f'category!~"{PLATFORM_TEST_SUPPORT_CATEGORY_REGEX}"}}'

View File

@ -46,6 +46,8 @@ def test_jobs_dashboard_separates_current_gate_health_from_reliability():
test_variable = variables_by_name["test"] test_variable = variables_by_name["test"]
assert test_variable["query"].startswith("query_result(") assert test_variable["query"].startswith("query_result(")
assert "bottomk(75" in test_variable["query"]
assert "min_over_time" in test_variable["query"]
assert "platform_quality:test_case_health_rate:percent_1h" in test_variable["query"] assert "platform_quality:test_case_health_rate:percent_1h" in test_variable["query"]
assert "platform_quality_gate_test_case_result" not in test_variable["query"] assert "platform_quality_gate_test_case_result" not in test_variable["query"]
assert test_variable["regex"] == '/test="([^"]+)"/' assert test_variable["regex"] == '/test="([^"]+)"/'

View File

@ -5044,7 +5044,7 @@
"name": "test", "name": "test",
"label": "Test Case", "label": "Test Case",
"type": "query", "type": "query",
"query": "query_result(topk(75, count by (test) (max_over_time(platform_quality:test_case_health_rate:percent_1h{suite=~\"${suite:regex}\",branch!=\"\",branch=~\"${branch:regex}\",test!=\"\",test!=\"__no_test_cases__\",category!~\"fixtures|golden|helpers\"}[24h:1h]))))", "query": "query_result(bottomk(75, min by (test) (min_over_time(platform_quality:test_case_health_rate:percent_1h{suite=~\"${suite:regex}\",branch!=\"\",branch=~\"${branch:regex}\",test!=\"\",test!=\"__no_test_cases__\",category!~\"fixtures|golden|helpers\"}[24h:1h]))))",
"regex": "/test=\"([^\"]+)\"/", "regex": "/test=\"([^\"]+)\"/",
"current": { "current": {
"text": "All", "text": "All",

View File

@ -5053,7 +5053,7 @@ data:
"name": "test", "name": "test",
"label": "Test Case", "label": "Test Case",
"type": "query", "type": "query",
"query": "query_result(topk(75, count by (test) (max_over_time(platform_quality:test_case_health_rate:percent_1h{suite=~\"${suite:regex}\",branch!=\"\",branch=~\"${branch:regex}\",test!=\"\",test!=\"__no_test_cases__\",category!~\"fixtures|golden|helpers\"}[24h:1h]))))", "query": "query_result(bottomk(75, min by (test) (min_over_time(platform_quality:test_case_health_rate:percent_1h{suite=~\"${suite:regex}\",branch!=\"\",branch=~\"${branch:regex}\",test!=\"\",test!=\"__no_test_cases__\",category!~\"fixtures|golden|helpers\"}[24h:1h]))))",
"regex": "/test=\"([^\"]+)\"/", "regex": "/test=\"([^\"]+)\"/",
"current": { "current": {
"text": "All", "text": "All",

View File

@ -247,6 +247,16 @@ def test_exchange_returns_result_and_enforces_response_limit():
(b'{"ok":true,"status":"200","content_type":"a","body":""}', 0, "invalid evidence"), (b'{"ok":true,"status":"200","content_type":"a","body":""}', 0, "invalid evidence"),
(b'{"ok":true,"status":200,"content_type":"a","body":"!!"}', 0, "invalid evidence"), (b'{"ok":true,"status":200,"content_type":"a","body":"!!"}', 0, "invalid evidence"),
], ],
ids=[
"empty-error",
"oversized-output",
"invalid-json",
"false-result",
"array-result",
"missing-fields",
"empty-body",
"invalid-body",
],
) )
def test_exchange_rejects_invalid_helper_evidence(output, returncode, match): def test_exchange_rejects_invalid_helper_evidence(output, returncode, match):
module = _load("deadline_http") module = _load("deadline_http")

View File

@ -141,6 +141,7 @@ def test_created_at_requires_short_timezone_aware_iso(value):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"value", "value",
[b"x" * (1024 * 1024 + 1), b"{}", json.dumps([None]).encode()], [b"x" * (1024 * 1024 + 1), b"{}", json.dumps([None]).encode()],
ids=["oversized-input", "object-input", "null-list-input"],
) )
def test_evaluate_rejects_invalid_top_level_inputs(value): def test_evaluate_rejects_invalid_top_level_inputs(value):
module = _module("branch_evaluate_top") module = _module("branch_evaluate_top")

View File

@ -65,6 +65,30 @@ def test_build_payload_includes_explicit_test_case_series():
) )
def test_test_case_labels_are_bounded_and_collision_safe():
first = "module.test_case[" + ("x" * (4 * 1024 * 1024)) + "]"
second = first[:-2] + "y]"
first_label = publish_test_metrics._bounded_test_name(first)
second_label = publish_test_metrics._bounded_test_name(second)
payload = publish_test_metrics._build_payload(
suite="titan_iac",
status="ok",
tests={"tests": 2, "failures": 0, "errors": 0, "skipped": 0},
test_cases=[(first, "passed"), (second, "passed")],
ok_count=1,
failed_count=0,
branch="main",
build_number="1",
jenkins_job="titan-iac",
)
assert len(first_label.encode("utf-8")) <= publish_test_metrics.TEST_CASE_LABEL_MAX_BYTES
assert first_label != second_label
assert "[sha256:" in first_label
assert max(len(line.encode("utf-8")) for line in payload.splitlines()) < 1024
def test_main_uses_reported_coverage_and_loc_without_fallback(tmp_path: Path, monkeypatch, capsys): def test_main_uses_reported_coverage_and_loc_without_fallback(tmp_path: Path, monkeypatch, capsys):
build_dir = tmp_path / "build" build_dir = tmp_path / "build"
build_dir.mkdir() build_dir.mkdir()