ci: publish complete gate telemetry

This commit is contained in:
Brad Stein 2026-05-16 19:09:46 -03:00
parent 1da08ba589
commit e684bf9670
7 changed files with 123 additions and 10 deletions

View File

@ -8,6 +8,7 @@ SUMMARY_JSON="${REPORT_DIR}/summary.json"
SUMMARY_TXT="${REPORT_DIR}/summary.txt"
METRICS_FILE="${REPORT_DIR}/metrics.prom"
PUSHGATEWAY_URL=${QUALITY_GATE_PUSHGATEWAY_URL:-}
PUSHGATEWAY_JOB=${LESAVKA_GATE_GLUE_PUSHGATEWAY_JOB:-platform-quality-ci}
mkdir -p "${REPORT_DIR}"
cd "${ROOT_DIR}"
@ -21,7 +22,8 @@ if [[ -z "${commit}" ]]; then
commit=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
fi
python3 - "${SUMMARY_JSON}" "${SUMMARY_TXT}" "${METRICS_FILE}" "${branch}" "${commit}" "${ROOT_DIR}" <<'PY'
status=0
python3 - "${SUMMARY_JSON}" "${SUMMARY_TXT}" "${METRICS_FILE}" "${branch}" "${commit}" "${ROOT_DIR}" <<'PY' || status=$?
import json
import pathlib
import sys
@ -99,12 +101,11 @@ print(text_path.read_text(encoding='utf-8'))
if status != 'ok':
raise SystemExit(1)
PY
status=$?
if [[ -n "${PUSHGATEWAY_URL}" ]]; then
curl --fail --silent --show-error \
--data-binary @"${METRICS_FILE}" \
"${PUSHGATEWAY_URL%/}/metrics/job/lesavka-gate-glue-gate/suite/lesavka" || status=$?
"${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka/gate/gate-glue" || status=$?
fi
exit "${status}"

View File

@ -8,6 +8,8 @@ SUMMARY_TXT="${REPORT_DIR}/summary.txt"
BASELINE_JSON="${ROOT_DIR}/scripts/ci/hygiene_gate_baseline.json"
METADATA_JSON="${REPORT_DIR}/cargo-metadata.json"
METRICS_FILE="${REPORT_DIR}/metrics.prom"
PUSHGATEWAY_URL=${QUALITY_GATE_PUSHGATEWAY_URL:-}
PUSHGATEWAY_JOB=${LESAVKA_HYGIENE_GATE_PUSHGATEWAY_JOB:-platform-quality-ci}
mkdir -p "${REPORT_DIR}"
@ -25,7 +27,8 @@ if [[ -z "${commit}" ]]; then
commit=$(git -C "${ROOT_DIR}" rev-parse --short HEAD 2>/dev/null || echo unknown)
fi
python3 - "${CLIPPY_JSON}" "${BASELINE_JSON}" "${SUMMARY_TXT}" "${ROOT_DIR}" "${METRICS_FILE}" "${branch}" "${commit}" <<'PY'
status=0
python3 - "${CLIPPY_JSON}" "${BASELINE_JSON}" "${SUMMARY_TXT}" "${ROOT_DIR}" "${METRICS_FILE}" "${branch}" "${commit}" <<'PY' || status=$?
import json
import os
import pathlib
@ -530,3 +533,18 @@ if failed:
print(line, file=sys.stderr)
raise SystemExit(1)
PY
publish_status=0
if [[ -n "${PUSHGATEWAY_URL}" ]]; then
curl --fail --silent --show-error \
--data-binary @"${METRICS_FILE}" \
"${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka/gate/hygiene" || publish_status=$?
else
echo "Skipping hygiene metrics publish: QUALITY_GATE_PUSHGATEWAY_URL is not set"
fi
if [[ "${status}" -eq 0 && "${publish_status}" -ne 0 ]]; then
status="${publish_status}"
fi
exit "${status}"

View File

@ -10,7 +10,7 @@ SUMMARY_TXT="${REPORT_DIR}/summary.txt"
METRICS_FILE="${REPORT_DIR}/metrics.prom"
STATUS_FILE="${REPORT_DIR}/gate-status.txt"
PUSHGATEWAY_URL=${QUALITY_GATE_PUSHGATEWAY_URL:-}
PUSHGATEWAY_JOB=${LESAVKA_MEDIA_GATE_PUSHGATEWAY_JOB:-lesavka-media-reliability-gate}
PUSHGATEWAY_JOB=${LESAVKA_MEDIA_GATE_PUSHGATEWAY_JOB:-platform-quality-ci}
SYNC_PROBE_REPORT_JSON=${LESAVKA_SYNC_PROBE_REPORT_JSON:-}
SYNC_PROBE_REPORT_DIR=${LESAVKA_SYNC_PROBE_REPORT_DIR:-}
REQUIRE_SYNC_PROBE=${LESAVKA_REQUIRE_SYNC_PROBE:-0}
@ -279,7 +279,7 @@ status=$(cat "${STATUS_FILE}")
if [[ -n "${PUSHGATEWAY_URL}" ]]; then
curl --fail --silent --show-error \
--data-binary @"${METRICS_FILE}" \
"${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka" || status=$?
"${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka/gate/media-reliability" || status=$?
fi
exit "${status}"

View File

@ -7,7 +7,7 @@ REPORT_DIR="${ROOT_DIR}/target/performance-gate"
TEST_LOG="${REPORT_DIR}/cargo-test.log"
METRICS_FILE="${REPORT_DIR}/metrics.prom"
PUSHGATEWAY_URL=${QUALITY_GATE_PUSHGATEWAY_URL:-}
PUSHGATEWAY_JOB=${LESAVKA_PERFORMANCE_GATE_PUSHGATEWAY_JOB:-lesavka-performance-gate}
PUSHGATEWAY_JOB=${LESAVKA_PERFORMANCE_GATE_PUSHGATEWAY_JOB:-platform-quality-ci}
mkdir -p "${REPORT_DIR}"
cd "${ROOT_DIR}"
@ -68,7 +68,7 @@ PY
if [[ -n "${PUSHGATEWAY_URL}" ]]; then
curl --fail --silent --show-error \
--data-binary @"${METRICS_FILE}" \
"${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka" || status=$?
"${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka/gate/performance" || status=$?
fi
exit "${status}"

View File

@ -103,6 +103,90 @@ publish_metrics() {
"${PUSHGATEWAY_URL%/}/metrics/job/platform-quality-ci/suite/lesavka"
}
write_fallback_quality_metrics() {
python3 - "${METRICS_FILE}" "${ROOT_DIR}" "${branch}" "${commit}" "${build_number}" "${jenkins_job}" <<'PY'
import pathlib
import subprocess
import sys
metrics_path = pathlib.Path(sys.argv[1])
root = pathlib.Path(sys.argv[2])
branch = sys.argv[3]
commit = sys.argv[4]
build_number = sys.argv[5]
jenkins_job = sys.argv[6]
def run_git(*args: str) -> list[str]:
proc = subprocess.run(
['git', '-C', str(root), *args],
check=True,
text=True,
capture_output=True,
)
return [line for line in proc.stdout.splitlines() if line]
def repo_files() -> list[str]:
tracked = run_git('ls-files')
untracked = run_git('ls-files', '--others', '--exclude-standard')
return sorted(set(tracked + untracked))
def is_test_path(rel: str) -> bool:
return 'tests' in pathlib.Path(rel).parts
def esc(value: str) -> str:
return value.replace('\\', r'\\').replace('\n', r'\\n').replace('"', r'\"')
source_files: list[str] = []
source_loc_over_500: list[str] = []
for rel in repo_files():
if not rel.endswith('.rs') or '/src/' not in rel or is_test_path(rel):
continue
path = root / rel
if not path.exists() or path.is_dir():
continue
source_files.append(rel)
loc = sum(1 for _ in path.open('r', encoding='utf-8'))
if loc > 500:
source_loc_over_500.append(rel)
labels = f'suite="lesavka",branch="{esc(branch)}",commit="{esc(commit)}"'
build_labels = f'{labels},build_number="{esc(build_number)}",jenkins_job="{esc(jenkins_job)}"'
loc_ok_value = 0 if source_loc_over_500 else 1
loc_failed_value = 1 if source_loc_over_500 else 0
metrics = [
'# HELP platform_quality_gate_runs_total Number of quality gate runs by result.',
'# TYPE platform_quality_gate_runs_total counter',
f'platform_quality_gate_runs_total{{{labels},status="failed"}} 1',
'# HELP platform_quality_gate_build_info Build metadata for the latest lesavka gate run.',
'# TYPE platform_quality_gate_build_info gauge',
f'platform_quality_gate_build_info{{{build_labels}}} 1',
'# HELP platform_quality_gate_checks_total Check outcomes from the latest lesavka gate run.',
'# TYPE platform_quality_gate_checks_total gauge',
f'platform_quality_gate_checks_total{{{labels},check="coverage",status="ok"}} 0',
f'platform_quality_gate_checks_total{{{labels},check="coverage",status="failed"}} 1',
f'platform_quality_gate_checks_total{{{labels},check="loc",status="ok"}} {loc_ok_value}',
f'platform_quality_gate_checks_total{{{labels},check="loc",status="failed"}} {loc_failed_value}',
]
for check in ('tests', 'style', 'media_reliability', 'gate_glue', 'sonarqube', 'supply_chain'):
metrics.append(f'platform_quality_gate_checks_total{{{labels},check="{check}",status="not_applicable"}} 1')
metrics.extend([
'# HELP platform_quality_gate_workspace_line_coverage_percent Workspace line coverage percent.',
'# TYPE platform_quality_gate_workspace_line_coverage_percent gauge',
f'platform_quality_gate_workspace_line_coverage_percent{{{labels}}} 0',
'# HELP platform_quality_gate_source_files_total Count of tracked source files in the source LOC gate.',
'# TYPE platform_quality_gate_source_files_total gauge',
f'platform_quality_gate_source_files_total{{{labels}}} {len(source_files)}',
'# HELP platform_quality_gate_source_lines_over_500_total Count of tracked source files over 500 LOC.',
'# TYPE platform_quality_gate_source_lines_over_500_total gauge',
f'platform_quality_gate_source_lines_over_500_total{{{labels}}} {len(source_loc_over_500)}',
'# HELP platform_quality_gate_repo_source_lines_over_500_total Count of repo source files over 500 LOC, including untracked working-tree files.',
'# TYPE platform_quality_gate_repo_source_lines_over_500_total gauge',
f'platform_quality_gate_repo_source_lines_over_500_total{{{labels}}} {len(source_loc_over_500)}',
])
metrics_path.write_text('\n'.join(metrics) + '\n', encoding='utf-8')
PY
}
status=0
# Several integration contracts intentionally mutate process environment and
# probe singleton runtime state. Keep coverage collection serial so per-file
@ -191,6 +275,7 @@ for rel, counts in sorted(lcov_counts.items()):
})
files.sort(key=lambda item: item['path'])
source_files = []
source_loc_over_500 = []
for rel in repo_files():
if not rel.endswith('.rs') or '/src/' not in rel:
@ -200,6 +285,7 @@ for rel in repo_files():
path = root / rel
if not path.exists() or path.is_dir():
continue
source_files.append(rel)
loc = sum(1 for _ in path.open('r', encoding='utf-8'))
if loc > 500:
source_loc_over_500.append(f'{rel}: source file exceeds 500 LOC ({loc})')
@ -293,6 +379,9 @@ metrics.append(f'platform_quality_gate_workspace_line_coverage_percent{{{labels}
metrics.append('# HELP platform_quality_gate_files_total Count of tracked source files in the quality gate.')
metrics.append('# TYPE platform_quality_gate_files_total gauge')
metrics.append(f'platform_quality_gate_files_total{{{labels}}} {len(files)}')
metrics.append('# HELP platform_quality_gate_source_files_total Count of tracked source files in the source LOC gate.')
metrics.append('# TYPE platform_quality_gate_source_files_total gauge')
metrics.append(f'platform_quality_gate_source_files_total{{{labels}}} {len(source_files)}')
metrics.append('# HELP platform_quality_gate_files_at_or_above_95_total Count of files at or above the 95 percent line target.')
metrics.append('# TYPE platform_quality_gate_files_at_or_above_95_total gauge')
metrics.append(f'platform_quality_gate_files_at_or_above_95_total{{{labels}}} {files_at_95}')
@ -410,6 +499,9 @@ else
fi
publish_status=0
if ! grep -q '^platform_quality_gate_checks_total{' "${METRICS_FILE}"; then
write_fallback_quality_metrics
fi
refresh_counter_metrics
if publish_metrics; then
:

View File

@ -8,6 +8,7 @@ SUMMARY_JSON="${REPORT_DIR}/summary.json"
SUMMARY_TXT="${REPORT_DIR}/summary.txt"
METRICS_FILE="${REPORT_DIR}/metrics.prom"
PUSHGATEWAY_URL=${QUALITY_GATE_PUSHGATEWAY_URL:-}
PUSHGATEWAY_JOB=${LESAVKA_SONAR_GATE_PUSHGATEWAY_JOB:-platform-quality-ci}
ENFORCE=${LESAVKA_SONAR_ENFORCE:-0}
mkdir -p "${REPORT_DIR}"
@ -92,7 +93,7 @@ PY
if [[ -n "${PUSHGATEWAY_URL}" ]]; then
curl --fail --silent --show-error \
--data-binary @"${METRICS_FILE}" \
"${PUSHGATEWAY_URL%/}/metrics/job/lesavka-sonarqube-gate/suite/lesavka" || status=$?
"${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka/gate/sonarqube" || status=$?
fi
exit "${status}"

View File

@ -11,6 +11,7 @@ SBOM_JSON="${REPORT_DIR}/sbom.cargo-metadata.json"
TREE_TXT="${REPORT_DIR}/dependency-tree.txt"
SECRET_TXT="${REPORT_DIR}/secret-scan.txt"
PUSHGATEWAY_URL=${QUALITY_GATE_PUSHGATEWAY_URL:-}
PUSHGATEWAY_JOB=${LESAVKA_SUPPLY_CHAIN_PUSHGATEWAY_JOB:-platform-quality-ci}
ENFORCE_TOOLS=${LESAVKA_SUPPLY_CHAIN_ENFORCE_TOOLS:-0}
mkdir -p "${REPORT_DIR}"
@ -138,7 +139,7 @@ PY
if [[ -n "${PUSHGATEWAY_URL}" ]]; then
curl --fail --silent --show-error \
--data-binary @"${METRICS_FILE}" \
"${PUSHGATEWAY_URL%/}/metrics/job/lesavka-supply-chain-gate/suite/lesavka" || status=$?
"${PUSHGATEWAY_URL%/}/metrics/job/${PUSHGATEWAY_JOB}/suite/lesavka/gate/supply-chain" || status=$?
fi
exit "${status}"