monitoring(testing): use lane timelines for test health

This commit is contained in:
jenkins 2026-05-15 21:05:13 -03:00
parent 792ac2b946
commit a2d5c9c83e
7 changed files with 1541 additions and 1005 deletions

View File

@ -1169,7 +1169,7 @@ def testing_case_variable():
"name": "test", "name": "test",
"label": "Test Case", "label": "Test Case",
"type": "query", "type": "query",
"query": f'label_values(platform_quality_gate_test_case_result{{suite=~"${{suite:regex}}",branch=~"${{branch:regex}}",test!="__no_test_cases__",{PLATFORM_TEST_EXPORT_FILTER}}}, test)', "query": f'label_values(platform_quality_gate_test_case_result{{suite=~"${{suite:regex}}",branch!="",branch=~"${{branch:regex}}",test!="",test!="__no_test_cases__",{PLATFORM_TEST_EXPORT_FILTER}}}, test)',
"current": {"text": "All", "value": "$__all", "selected": True}, "current": {"text": "All", "value": "$__all", "selected": True},
"options": [], "options": [],
"hide": 0, "hide": 0,
@ -1187,7 +1187,7 @@ def testing_branch_variable():
"name": "branch", "name": "branch",
"label": "Branch", "label": "Branch",
"type": "query", "type": "query",
"query": f'label_values(platform_quality_gate_build_info{{suite=~"${{suite:regex}}",{PLATFORM_TEST_EXPORT_FILTER}}}, branch)', "query": f'label_values(platform_quality_gate_build_info{{suite=~"${{suite:regex}}",branch!="",{PLATFORM_TEST_EXPORT_FILTER}}}, branch)',
"current": {"text": "All", "value": "$__all", "selected": True}, "current": {"text": "All", "value": "$__all", "selected": True},
"options": [], "options": [],
"hide": 0, "hide": 0,
@ -3385,9 +3385,7 @@ def build_jobs_dashboard():
coverage_metric_selector = f'__name__=~".*_quality_gate_coverage_percent",suite=~"{suite_var}",{exported}' coverage_metric_selector = f'__name__=~".*_quality_gate_coverage_percent",suite=~"{suite_var}",{exported}'
workspace_coverage_selector = f'suite=~"{suite_var}",{exported}' workspace_coverage_selector = f'suite=~"{suite_var}",{exported}'
smell_selector = f'suite=~"{suite_var}",{exported}' smell_selector = f'suite=~"{suite_var}",{exported}'
test_case_selector = f'suite=~"{suite_var}",branch=~"{branch_var}",test=~"{test_var}",test!="__no_test_cases__",{exported}' build_info_selector = f'suite=~"{suite_var}",branch!="",branch=~"{branch_var}",{exported}'
all_test_case_selector = f'suite=~"{suite_var}",branch=~"{branch_var}",test!="__no_test_cases__",{exported}'
build_info_selector = f'suite=~"{suite_var}",branch=~"{branch_var}",{exported}'
selected_suite_universe = ( selected_suite_universe = (
f'(sum by (suite) (increase(platform_quality_gate_runs_total{{{runs_selector}}}[30d])) >= bool 0)' f'(sum by (suite) (increase(platform_quality_gate_runs_total{{{runs_selector}}}[30d])) >= bool 0)'
) )
@ -3488,59 +3486,51 @@ def build_jobs_dashboard():
return f"(100 * ({state_checks}) / clamp_min(({total_checks}), 1)) and on(suite) (({total_checks}) > 0)" return f"(100 * ({state_checks}) / clamp_min(({total_checks}), 1)) and on(suite) (({total_checks}) > 0)"
rollup_failed_tests = ( rollup_failed_tests = (
f'sum by (suite, test) (platform_quality:test_case_status:count_1h{{suite=~"{suite_var}",branch=~"{branch_var}",test!="__no_test_cases__",status="failed"}})' f'sum by (suite, test) (platform_quality:test_case_status:count_1h{{suite=~"{suite_var}",branch!="",branch=~"{branch_var}",test!="",test!="__no_test_cases__",status="failed"}})'
) )
raw_failed_tests = ( raw_failed_tests = (
f'sum by (suite, test) (max_over_time(platform_quality_gate_test_case_result{{{all_test_case_selector},status="failed"}}[$__interval]))' f'sum by (suite, test) (max_over_time(platform_quality_gate_test_case_result{{suite=~"{suite_var}",branch!="",branch=~"{branch_var}",test!="",test!="__no_test_cases__",{exported},status="failed"}}[$__interval]))'
) )
problematic_tests_history_core = f"topk(12, (({rollup_failed_tests}) or on(suite, test) ({raw_failed_tests})))" problematic_tests_history_core = f"topk(12, (({rollup_failed_tests}) or on(suite, test) ({raw_failed_tests})))"
problematic_tests_history = f"({problematic_tests_history_core}) or on() vector(0)" problematic_tests_history = problematic_tests_history_core
rollup_failed_tests_30d = ( rollup_failed_tests_30d = (
f'sum by (suite, test) (sum_over_time(platform_quality:test_case_status:count_1h{{suite=~"{suite_var}",branch=~"{branch_var}",test!="__no_test_cases__",status="failed"}}[30d:1h]))' f'sum by (suite, test) (sum_over_time(platform_quality:test_case_status:count_1h{{suite=~"{suite_var}",branch!="",branch=~"{branch_var}",test!="",test!="__no_test_cases__",status="failed"}}[30d:1h]))'
) )
raw_failed_tests_30d = ( raw_failed_tests_30d = (
f'sum by (suite, test) (increase(platform_quality_gate_test_case_result{{{all_test_case_selector},status="failed"}}[30d]))' f'sum by (suite, test) (increase(platform_quality_gate_test_case_result{{suite=~"{suite_var}",branch!="",branch=~"{branch_var}",test!="",test!="__no_test_cases__",{exported},status="failed"}}[30d]))'
) )
worst_test_per_suite_core = ( worst_test_per_suite_core = (
f"topk by (suite) (1, (({rollup_failed_tests_30d}) or on(suite, test) ({raw_failed_tests_30d})))" f"topk by (suite) (1, (({rollup_failed_tests_30d}) or on(suite, test) ({raw_failed_tests_30d})))"
) )
worst_test_per_suite = f"({worst_test_per_suite_core}) or on() vector(0)" worst_test_per_suite = worst_test_per_suite_core
def _selected_status_history(status: str) -> str: def _selected_status_volume(status: str) -> str:
rollup = ( return (
f'sum by (suite) (platform_quality:test_case_status:count_1h{{suite=~"{suite_var}",branch=~"{branch_var}",test=~"{test_var}",test!="__no_test_cases__",status="{status}"}})' f'(sum(platform_quality:test_case_status:count_1h{{suite=~"{suite_var}",branch!="",'
f'branch=~"{branch_var}",test!="",test=~"{test_var}",test!="__no_test_cases__",'
f'status="{status}"}}) or on() vector(0))'
) )
raw = (
f'sum by (suite) (max_over_time(platform_quality_gate_test_case_result{{{test_case_selector},status="{status}"}}[$__interval]))'
)
return f"(({rollup}) or on(suite) ({raw}) or on(suite) ({selected_suite_zero}))"
selected_passed_history = _selected_status_history("passed")
selected_failed_history = _selected_status_history("failed")
selected_skipped_history = _selected_status_history("skipped")
selected_total_history = (
f'(sum by (suite) (platform_quality:test_case_status:count_1h{{suite=~"{suite_var}",branch=~"{branch_var}",test=~"{test_var}",test!="__no_test_cases__",status=~"passed|failed|error|skipped"}}) '
f'or on(suite) sum by (suite) (max_over_time(platform_quality_gate_test_case_result{{{test_case_selector},status=~"passed|failed|error|skipped"}}[$__interval])))'
)
selected_test_pass_fail = [ selected_test_pass_fail = [
{ {
"refId": "A", "refId": "A",
"expr": selected_passed_history, "expr": _selected_status_volume("passed"),
"legendFormat": "{{suite}} passed", "legendFormat": "Passed",
}, },
{ {
"refId": "B", "refId": "B",
"expr": selected_failed_history, "expr": _selected_status_volume("failed"),
"legendFormat": "{{suite}} failed", "legendFormat": "Failed",
}, },
{ {
"refId": "C", "refId": "C",
"expr": selected_skipped_history, "expr": _selected_status_volume("skipped"),
"legendFormat": "{{suite}} skipped", "legendFormat": "Skipped",
}, },
] ]
selected_test_pass_rate = ( selected_test_pass_rate = (
f"((100 * ({selected_passed_history}) / clamp_min(({selected_total_history}), 1)) or on(suite) ({selected_suite_zero}))" f'avg by (suite) (platform_quality:test_case_pass_rate:percent_1h{{suite=~"{suite_var}",'
f'branch!="",branch=~"{branch_var}",test!="",test=~"{test_var}",test!="__no_test_cases__"}})'
) )
recent_branch_evidence = ( recent_branch_evidence = (
f'sort_desc(count by (suite, branch) (max_over_time(platform_quality_gate_build_info{{{build_info_selector}}}[30d])))' f'sort_desc(count by (suite, branch) (max_over_time(platform_quality_gate_build_info{{{build_info_selector}}}[30d])))'
@ -3628,7 +3618,25 @@ def build_jobs_dashboard():
*, *,
description: str, description: str,
thresholds: dict, thresholds: dict,
unit: str = "percent",
min_value: int | float | None = 0,
max_value: int | float | None = 100,
legend: str = "{{suite}}",
) -> dict: ) -> dict:
defaults = {
"color": {"mode": "thresholds"},
"unit": unit,
"thresholds": thresholds,
"custom": {
"fillOpacity": 70,
"lineWidth": 0,
"spanNulls": True,
},
}
if min_value is not None:
defaults["min"] = min_value
if max_value is not None:
defaults["max"] = max_value
panel = { panel = {
"id": panel_id, "id": panel_id,
"type": "state-timeline", "type": "state-timeline",
@ -3636,20 +3644,9 @@ def build_jobs_dashboard():
"description": description, "description": description,
"datasource": PROM_DS, "datasource": PROM_DS,
"gridPos": grid, "gridPos": grid,
"targets": [{"expr": expr, "refId": "A", "legendFormat": "{{suite}}"}], "targets": [{"expr": expr, "refId": "A", "legendFormat": legend}],
"fieldConfig": { "fieldConfig": {
"defaults": { "defaults": defaults,
"color": {"mode": "thresholds"},
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": thresholds,
"custom": {
"fillOpacity": 70,
"lineWidth": 0,
"spanNulls": True,
},
},
"overrides": [], "overrides": [],
}, },
"options": { "options": {
@ -3860,63 +3857,47 @@ def build_jobs_dashboard():
"Higher means more of the selected suites/checks are healthy right now; gaps mean there was no check evidence." "Higher means more of the selected suites/checks are healthy right now; gaps mean there was no check evidence."
) )
for index, (label, regex) in enumerate(check_dimensions[:4]): for index, (label, regex) in enumerate(check_dimensions[:4]):
panel = timeseries_panel( panel = _state_timeline_panel(
start_id + index, start_id + index,
f"{label} {title_prefix}", f"{label} {title_prefix}",
_check_state_percent_series(regex, failed), _check_state_percent_series(regex, failed),
{"h": 7, "w": 6, "x": index * 6, "y": y}, {"h": 7, "w": 6, "x": index * 6, "y": y},
unit="percent", thresholds=trend_thresholds,
legend="{{suite}}", description=trend_description,
legend_display="list",
legend_placement="bottom",
legend_calcs=[],
) )
panel["description"] = trend_description
panel["fieldConfig"]["defaults"]["thresholds"] = trend_thresholds
panel["fieldConfig"]["defaults"]["min"] = 0
panel["fieldConfig"]["defaults"]["max"] = 100
panel["fieldConfig"]["defaults"].setdefault("custom", {})["spanNulls"] = True
panel["fieldConfig"]["defaults"]["custom"]["showPoints"] = "never"
panel["fieldConfig"]["defaults"]["custom"]["lineWidth"] = 2
panels.append(panel) panels.append(panel)
for index, (label, regex) in enumerate(check_dimensions[4:]): for index, (label, regex) in enumerate(check_dimensions[4:]):
panel = timeseries_panel( panel = _state_timeline_panel(
start_id + 4 + index, start_id + 4 + index,
f"{label} {title_prefix}", f"{label} {title_prefix}",
_check_state_percent_series(regex, failed), _check_state_percent_series(regex, failed),
{"h": 7, "w": 8, "x": index * 8, "y": y + 7}, {"h": 7, "w": 8, "x": index * 8, "y": y + 7},
unit="percent", thresholds=trend_thresholds,
legend="{{suite}}", description=trend_description,
legend_display="list",
legend_placement="bottom",
legend_calcs=[],
) )
panel["description"] = trend_description
panel["fieldConfig"]["defaults"]["thresholds"] = trend_thresholds
panel["fieldConfig"]["defaults"]["min"] = 0
panel["fieldConfig"]["defaults"]["max"] = 100
panel["fieldConfig"]["defaults"].setdefault("custom", {})["spanNulls"] = True
panel["fieldConfig"]["defaults"]["custom"]["showPoints"] = "never"
panel["fieldConfig"]["defaults"]["custom"]["lineWidth"] = 2
panels.append(panel) panels.append(panel)
_append_check_trends(130, "Failure Rate", True, 29) _append_check_trends(130, "Failure Rate", True, 29)
_append_check_trends(138, "Healthy Rate", False, 43) _append_check_trends(138, "Healthy Rate", False, 43)
panels.append( panels.append(
timeseries_panel( _state_timeline_panel(
145, 145,
"Problematic Tests Over Time (Top failures)", "Problematic Tests Over Time (Top failures)",
problematic_tests_history, problematic_tests_history,
{"h": 8, "w": 12, "x": 0, "y": 57}, {"h": 8, "w": 12, "x": 0, "y": 57},
thresholds=failures_thresholds,
unit="none", unit="none",
min_value=0,
max_value=None,
legend="{{suite}} - {{test}}", legend="{{suite}} - {{test}}",
legend_display="list", description=(
legend_placement="right", "Top failing test cases over time, using memoized hourly rollups. "
legend_calcs=[], "Blank branch/test labels and placeholder no-test-case rows are excluded."
links=jenkins_suite_links(), ),
data_links=jenkins_latest_artifact_data_links(),
) )
) )
panels[-1]["links"] = jenkins_suite_links()
panels[-1]["fieldConfig"]["defaults"]["links"] = jenkins_latest_artifact_data_links()
panels.append( panels.append(
bargauge_panel( bargauge_panel(
147, 147,
@ -3948,22 +3929,32 @@ def build_jobs_dashboard():
data_links=jenkins_artifact_data_links(), data_links=jenkins_artifact_data_links(),
) )
) )
selected_pass_rate_panel = timeseries_panel( panels[-1]["description"] = (
"Stacked hourly outcome volume for the selected suite/branch/test scope. "
"This uses vmalert rollups only, avoiding expensive raw 30-day per-test scans."
)
panels[-1]["fieldConfig"]["defaults"]["min"] = 0
panels[-1]["fieldConfig"]["defaults"]["custom"] = {
"drawStyle": "bars",
"barAlignment": 0,
"lineWidth": 0,
"fillOpacity": 70,
"stacking": {"mode": "normal", "group": "A"},
}
selected_pass_rate_panel = _state_timeline_panel(
152, 152,
"Selected Test Pass Rate History", "Selected Test Pass Rate History",
selected_test_pass_rate, selected_test_pass_rate,
{"h": 8, "w": 12, "x": 12, "y": 65}, {"h": 8, "w": 12, "x": 12, "y": 65},
unit="percent", thresholds=success_thresholds,
legend="{{suite}}", legend="{{suite}}",
legend_display="list", description=(
legend_placement="bottom", "Average pass rate per suite for the selected test filter, using memoized hourly "
legend_calcs=[], "test-case pass-rate rollups instead of raw historical scans."
links=jenkins_suite_links(), ),
data_links=jenkins_artifact_data_links(),
) )
selected_pass_rate_panel["fieldConfig"]["defaults"]["min"] = 0 selected_pass_rate_panel["links"] = jenkins_suite_links()
selected_pass_rate_panel["fieldConfig"]["defaults"]["max"] = 100 selected_pass_rate_panel["fieldConfig"]["defaults"]["links"] = jenkins_artifact_data_links()
selected_pass_rate_panel["fieldConfig"]["defaults"]["thresholds"] = success_thresholds
panels.append(selected_pass_rate_panel) panels.append(selected_pass_rate_panel)
coverage_panel = bargauge_panel( coverage_panel = bargauge_panel(
@ -4078,7 +4069,7 @@ def build_jobs_dashboard():
stat_panel( stat_panel(
32, 32,
"Sonar Projects (Selected)", "Sonar Projects (Selected)",
f'(count(sonarqube_project_quality_gate_pass{{project_key=~"{suite_var}"}}) or on() vector(0))', f'(count(max by (project_key) (sonarqube_project_quality_gate_pass{{project_key=~"{suite_var}"}})) or on() vector(0))',
{"h": 6, "w": 4, "x": 4, "y": 88}, {"h": 6, "w": 4, "x": 4, "y": 88},
unit="none", unit="none",
instant=True, instant=True,
@ -4099,23 +4090,32 @@ def build_jobs_dashboard():
sonar_status_mix_panel = pie_panel( sonar_status_mix_panel = pie_panel(
34, 34,
"Sonar Gate Status Mix (Selected)", "Sonar Gate Status Mix (Selected)",
f'count by (status) (sonarqube_project_quality_gate_pass{{project_key=~"{suite_var}"}})', f'count by (status) (max by (project_key, status) (sonarqube_project_quality_gate_pass{{project_key=~"{suite_var}"}}))',
{"h": 6, "w": 6, "x": 12, "y": 88}, {"h": 6, "w": 4, "x": 12, "y": 88},
) )
sonar_status_mix_panel["targets"][0]["legendFormat"] = "{{status}}" sonar_status_mix_panel["targets"][0]["legendFormat"] = "{{status}}"
panels.append(sonar_status_mix_panel) panels.append(sonar_status_mix_panel)
panels.append( panels.append(
bargauge_panel( _state_timeline_panel(
35, 35,
"Projects Failing Sonar Gate", "Projects Failing Sonar Gate",
f'(sort_desc(count by (project_key) (sonarqube_project_quality_gate_pass{{project_key=~"{suite_var}",status!~"OK|ok"}})) ' f'max by (project_key) ((max by (project_key, status) (sonarqube_project_quality_gate_pass{{project_key=~"{suite_var}",status!~"OK|ok"}})) * 0 + 1)',
f'or on() label_replace(vector(0), "project_key", "none", "__name__", ".*"))', {"h": 6, "w": 8, "x": 16, "y": 88},
{"h": 6, "w": 6, "x": 18, "y": 88}, thresholds={
"mode": "absolute",
"steps": [
{"color": dark_green, "value": None},
{"color": dark_red, "value": 1},
],
},
unit="none", unit="none",
instant=True, min_value=0,
max_value=1,
legend="{{project_key}}", legend="{{project_key}}",
sort_order="desc", description=(
thresholds=failures_thresholds, "Projects observed with a non-OK SonarQube gate status over time. "
"The query deduplicates pod/service endpoint scrapes before rendering."
),
) )
) )
panels.append( panels.append(
@ -4222,8 +4222,8 @@ def build_jobs_dashboard():
31: {"h": 6, "w": 4, "x": 0, "y": 111}, 31: {"h": 6, "w": 4, "x": 0, "y": 111},
32: {"h": 6, "w": 4, "x": 4, "y": 111}, 32: {"h": 6, "w": 4, "x": 4, "y": 111},
33: {"h": 6, "w": 4, "x": 8, "y": 111}, 33: {"h": 6, "w": 4, "x": 8, "y": 111},
34: {"h": 6, "w": 6, "x": 12, "y": 111}, 34: {"h": 6, "w": 4, "x": 12, "y": 111},
35: {"h": 6, "w": 6, "x": 18, "y": 111}, 35: {"h": 6, "w": 8, "x": 16, "y": 111},
} }
for panel_id, grid in row_layout.items(): for panel_id, grid in row_layout.items():
panel_by_id[panel_id]["gridPos"] = grid panel_by_id[panel_id]["gridPos"] = grid

View File

@ -193,10 +193,30 @@ def test_jobs_dashboard_collapses_heavy_drilldowns_for_light_first_paint():
assert "SonarQube API Up" in nested_panels_by_title assert "SonarQube API Up" in nested_panels_by_title
failure_rate_panel = nested_panels_by_title["Coverage Failure Rate"] failure_rate_panel = nested_panels_by_title["Coverage Failure Rate"]
assert failure_rate_panel["type"] == "state-timeline"
assert failure_rate_panel["fieldConfig"]["defaults"]["unit"] == "percent" assert failure_rate_panel["fieldConfig"]["defaults"]["unit"] == "percent"
assert failure_rate_panel["fieldConfig"]["defaults"]["max"] == 100 assert failure_rate_panel["fieldConfig"]["defaults"]["max"] == 100
assert "increase(" not in failure_rate_panel["targets"][0]["expr"] assert "increase(" not in failure_rate_panel["targets"][0]["expr"]
pass_rate_panel = nested_panels_by_title["Selected Test Pass Rate History"] pass_rate_panel = nested_panels_by_title["Selected Test Pass Rate History"]
assert "platform_quality_gate_test_case_result" in pass_rate_panel["targets"][0]["expr"] assert pass_rate_panel["type"] == "state-timeline"
assert "platform_quality:test_case_pass_rate:percent_1h" not in pass_rate_panel["targets"][0]["expr"] assert "platform_quality:test_case_pass_rate:percent_1h" in pass_rate_panel["targets"][0]["expr"]
assert "platform_quality_gate_test_case_result" not in pass_rate_panel["targets"][0]["expr"]
pass_fail_panel = nested_panels_by_title["Selected Test Pass/Fail History"]
assert pass_fail_panel["fieldConfig"]["defaults"]["custom"]["drawStyle"] == "bars"
assert all(
"platform_quality:test_case_status:count_1h" in target["expr"]
for target in pass_fail_panel["targets"]
)
problematic_panel = nested_panels_by_title["Problematic Tests Over Time (Top failures)"]
assert problematic_panel["type"] == "state-timeline"
assert 'test!=""' in problematic_panel["targets"][0]["expr"]
assert "vector(0)" not in problematic_panel["targets"][0]["expr"]
sonar_mix_panel = nested_panels_by_title["Sonar Gate Status Mix (Selected)"]
sonar_failing_panel = nested_panels_by_title["Projects Failing Sonar Gate"]
assert sonar_mix_panel["gridPos"]["w"] == 4
assert sonar_failing_panel["gridPos"]["w"] == 8
assert sonar_failing_panel["type"] == "state-timeline"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -147,7 +147,7 @@ data:
- record: platform_quality:test_case_status:count_1h - record: platform_quality:test_case_status:count_1h
expr: | expr: |
sum by (suite, branch, test, status) ( sum by (suite, branch, test, status) (
max_over_time(platform_quality_gate_test_case_result{exported_job="platform-quality-ci",test!="__no_test_cases__"}[1h]) max_over_time(platform_quality_gate_test_case_result{exported_job="platform-quality-ci",branch!="",test!="",test!="__no_test_cases__"}[1h])
) )
labels: labels:
rollup: hourly rollup: hourly
@ -155,13 +155,13 @@ data:
expr: | expr: |
100 * ( 100 * (
sum by (suite, branch, test) ( sum by (suite, branch, test) (
max_over_time(platform_quality_gate_test_case_result{exported_job="platform-quality-ci",test!="__no_test_cases__",status="passed"}[1h]) max_over_time(platform_quality_gate_test_case_result{exported_job="platform-quality-ci",branch!="",test!="",test!="__no_test_cases__",status="passed"}[1h])
) )
) )
/ /
clamp_min( clamp_min(
sum by (suite, branch, test) ( sum by (suite, branch, test) (
max_over_time(platform_quality_gate_test_case_result{exported_job="platform-quality-ci",test!="__no_test_cases__",status=~"passed|failed|error|skipped"}[1h]) max_over_time(platform_quality_gate_test_case_result{exported_job="platform-quality-ci",branch!="",test!="",test!="__no_test_cases__",status=~"passed|failed|error|skipped"}[1h])
), ),
1 1
) )
@ -196,7 +196,7 @@ spec:
labels: labels:
app: vmalert-atlas-availability app: vmalert-atlas-availability
annotations: annotations:
bstein.dev/rules-revision: "2026-05-15-platform-quality-rollups-v2" bstein.dev/rules-revision: "2026-05-15-platform-quality-rollups-v3"
spec: spec:
serviceAccountName: vmalert-atlas-availability serviceAccountName: vmalert-atlas-availability
affinity: affinity: