From 27c4fdee6e04d6d61bef320673bed22b2872b51c Mon Sep 17 00:00:00 2001 From: alvarius Date: Thu, 25 Apr 2024 15:59:45 +0100 Subject: [PATCH] feat(store-indexer): add prometheus metrics (#2739) --- .changeset/afraid-eagles-learn.md | 5 ++ .../store-indexer/bin/postgres-frontend.ts | 7 +++ .../store-indexer/bin/postgres-indexer.ts | 7 +++ packages/store-indexer/bin/sqlite-indexer.ts | 7 +++ packages/store-indexer/package.json | 1 + .../src/koa-middleware/metrics.ts | 43 +++++++++++++++++ pnpm-lock.yaml | 46 +++++++++++++++---- 7 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 .changeset/afraid-eagles-learn.md create mode 100644 packages/store-indexer/src/koa-middleware/metrics.ts diff --git a/.changeset/afraid-eagles-learn.md b/.changeset/afraid-eagles-learn.md new file mode 100644 index 0000000000..53625d7a3f --- /dev/null +++ b/.changeset/afraid-eagles-learn.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/store-indexer": patch +--- + +Add Prometheus metrics at `/metrics` to the Postgres indexer backend and frontend, as well as the SQLite indexer. diff --git a/packages/store-indexer/bin/postgres-frontend.ts b/packages/store-indexer/bin/postgres-frontend.ts index f95d80919c..f54651c6df 100644 --- a/packages/store-indexer/bin/postgres-frontend.ts +++ b/packages/store-indexer/bin/postgres-frontend.ts @@ -13,6 +13,7 @@ import { apiRoutes } from "../src/postgres/apiRoutes"; import { sentry } from "../src/koa-middleware/sentry"; import { healthcheck } from "../src/koa-middleware/healthcheck"; import { helloWorld } from "../src/koa-middleware/helloWorld"; +import { metrics } from "../src/koa-middleware/metrics"; const env = parseEnv( z.intersection( @@ -34,6 +35,12 @@ if (env.SENTRY_DSN) { server.use(cors()); server.use(healthcheck()); +server.use( + metrics({ + isHealthy: () => true, + isReady: () => true, + }), +); server.use(helloWorld()); server.use(apiRoutes(database)); diff --git a/packages/store-indexer/bin/postgres-indexer.ts b/packages/store-indexer/bin/postgres-indexer.ts index 28b0ee2482..b92c43e23c 100644 --- a/packages/store-indexer/bin/postgres-indexer.ts +++ b/packages/store-indexer/bin/postgres-indexer.ts @@ -96,6 +96,7 @@ if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) { const { default: Koa } = await import("koa"); const { default: cors } = await import("@koa/cors"); const { healthcheck } = await import("../src/koa-middleware/healthcheck"); + const { metrics } = await import("../src/koa-middleware/metrics"); const { helloWorld } = await import("../src/koa-middleware/helloWorld"); const server = new Koa(); @@ -106,6 +107,12 @@ if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) { isReady: () => isCaughtUp, }), ); + server.use( + metrics({ + isHealthy: () => true, + isReady: () => isCaughtUp, + }), + ); server.use(helloWorld()); server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT }); diff --git a/packages/store-indexer/bin/sqlite-indexer.ts b/packages/store-indexer/bin/sqlite-indexer.ts index a544fea7d6..690fcca5f1 100644 --- a/packages/store-indexer/bin/sqlite-indexer.ts +++ b/packages/store-indexer/bin/sqlite-indexer.ts @@ -19,6 +19,7 @@ import { healthcheck } from "../src/koa-middleware/healthcheck"; import { helloWorld } from "../src/koa-middleware/helloWorld"; import { apiRoutes } from "../src/sqlite/apiRoutes"; import { sentry } from "../src/koa-middleware/sentry"; +import { metrics } from "../src/koa-middleware/metrics"; const env = parseEnv( z.intersection( @@ -107,6 +108,12 @@ server.use( isReady: () => isCaughtUp, }), ); +server.use( + metrics({ + isHealthy: () => true, + isReady: () => isCaughtUp, + }), +); server.use(helloWorld()); server.use(apiRoutes(database)); diff --git a/packages/store-indexer/package.json b/packages/store-indexer/package.json index 16fae34ea6..cde241dac2 100644 --- a/packages/store-indexer/package.json +++ b/packages/store-indexer/package.json @@ -59,6 +59,7 @@ "koa": "^2.14.2", "koa-compose": "^4.1.0", "postgres": "3.3.5", + "prom-client": "^15.1.2", "rxjs": "7.5.5", "superjson": "^1.12.4", "trpc-koa-adapter": "^1.1.3", diff --git a/packages/store-indexer/src/koa-middleware/metrics.ts b/packages/store-indexer/src/koa-middleware/metrics.ts new file mode 100644 index 0000000000..b8d1d46ac2 --- /dev/null +++ b/packages/store-indexer/src/koa-middleware/metrics.ts @@ -0,0 +1,43 @@ +import { Middleware } from "koa"; +import promClient from "prom-client"; + +type MetricsOptions = { + isHealthy?: () => boolean; + isReady?: () => boolean; +}; + +/** + * Middleware to add Prometheus metrics endpoints + */ +export function metrics({ isHealthy, isReady }: MetricsOptions = {}): Middleware { + promClient.collectDefaultMetrics(); + if (isHealthy != null) { + new promClient.Gauge({ + name: "health_status", + help: "Health status (0 = unhealthy, 1 = healthy)", + collect(): void { + this.set(Number(isHealthy())); + }, + }); + } + + if (isReady != null) { + new promClient.Gauge({ + name: "readiness_status", + help: "Readiness status (whether the service is ready to receive requests, 0 = not ready, 1 = ready)", + collect(): void { + this.set(Number(isReady())); + }, + }); + } + + return async function metricsMiddleware(ctx, next): Promise { + if (ctx.path === "/metrics") { + ctx.status = 200; + ctx.body = await promClient.register.metrics(); + return; + } + + await next(); + }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa28ea8adf..8b7bd87c9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -877,6 +877,9 @@ importers: postgres: specifier: 3.3.5 version: 3.3.5 + prom-client: + specifier: ^15.1.2 + version: 15.1.2 rxjs: specifier: 7.5.5 version: 7.5.5 @@ -1258,7 +1261,7 @@ packages: '@arktype/util': 0.0.23 '@typescript/analyze-trace': 0.10.1 '@typescript/vfs': 1.5.0 - arktype: 2.0.0-dev.7 + arktype: 2.0.0-dev.8 typescript: 5.4.2 transitivePeerDependencies: - supports-color @@ -1268,10 +1271,10 @@ packages: resolution: {integrity: sha512-f3p8apUMOTl6KlFNTrOX6twQJDeSdSmtYPVca7SaQjUUTAxfUlaROY1/i1C3y2llYDrrQBEbsggk8k2Oxxgrvw==} dev: true - /@arktype/schema@0.0.7: - resolution: {integrity: sha512-awo14Oi98bn6pEgN7soiXvD+mQzidLgzABVO7Tpbw6xtU7+XRWp6JucSEmWLASC+fcoK0QSJxdoC+rm3iIKarQ==} + /@arktype/schema@0.0.8: + resolution: {integrity: sha512-X4WCMpiS4EOdTjb6NFI2cYw0QAzXXitj5oMPryv0topEeC8smuMOe3rlesbE/nG2eNUE0OpzDCY+LY/o1S5ktA==} dependencies: - '@arktype/util': 0.0.33 + '@arktype/util': 0.0.34 dev: true /@arktype/util@0.0.23: @@ -1282,8 +1285,8 @@ packages: resolution: {integrity: sha512-fDTBSVzxLj9k1ZjinkawmaQdcXFKMBVK8c+vqMPxwoa94mPMZxBo84yQcqyFVcIcWIkg6qQQmH1ozyT4nqFT/g==} dev: false - /@arktype/util@0.0.33: - resolution: {integrity: sha512-MYbrLHf0tVYjxI84m0mMRISmKKVoPzv25B1/X05nePUcyPqROoDBn+hYhHpB0GqnJZQOr8UG1CyMuxjFeVbTNg==} + /@arktype/util@0.0.34: + resolution: {integrity: sha512-P3opb4q9eDZYbKo1pzLa34Dytg+vMuq8fkY0xQ98BN3RrjHtAVRPFmK3CeAz4fSa39yhrYlLUBfuUEvi52Hb8w==} dev: true /@aws-crypto/ie11-detection@3.0.0: @@ -3728,6 +3731,11 @@ packages: rimraf: 3.0.2 dev: false + /@opentelemetry/api@1.8.0: + resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==} + engines: {node: '>=8.0.0'} + dev: false + /@oven/bun-darwin-aarch64@1.0.11: resolution: {integrity: sha512-6wCO37lyGNcqefEDQ7IJp4LW7ElKMH50/hlvW5agIBN/XbTwwtv3788dJ9NczEV7RQSlkOI6J3dUoQJ6Pgav6w==} cpu: [arm64] @@ -5222,11 +5230,11 @@ packages: resolution: {integrity: sha512-glMLgVhIQRSkR3tymiS+POAcWVJH09sfrgic0jHnyFL8BlhHAJZX2BzdImU9zYr1y9NBqy+U93ZNrRTHXsKRDw==} dev: false - /arktype@2.0.0-dev.7: - resolution: {integrity: sha512-TeehK+ExNsvqNoEccNOMs73LcNwR9+gX9pQsoCIvZfuxrQ24nB5MUQGweAAuNSwVX7GUUU9Ad0BWGnsvD8ST+g==} + /arktype@2.0.0-dev.8: + resolution: {integrity: sha512-WaQ5fBos/VIhBNAKtJmBKK1GXMJuD/OCKUfY1tWpxzLG8Fz4RMCz7NEs7xRGjUw+J5ODExbvSguTdsrcE0R37w==} dependencies: - '@arktype/schema': 0.0.7 - '@arktype/util': 0.0.33 + '@arktype/schema': 0.0.8 + '@arktype/util': 0.0.34 dev: true /array-includes@3.1.6: @@ -5477,6 +5485,10 @@ packages: file-uri-to-path: 1.0.0 dev: false + /bintrees@1.0.2: + resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + dev: false + /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -10302,6 +10314,14 @@ packages: engines: {node: '>=0.4.0'} dev: true + /prom-client@15.1.2: + resolution: {integrity: sha512-on3h1iXb04QFLLThrmVYg1SChBQ9N1c+nKAjebBjokBqipddH3uxmOUcEkTnzmJ8Jh/5TSUnUqS40i2QB2dJHQ==} + engines: {node: ^16 || ^18 || >=20} + dependencies: + '@opentelemetry/api': 1.8.0 + tdigest: 0.1.2 + dev: false + /promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: @@ -11485,6 +11505,12 @@ packages: yallist: 4.0.0 dev: false + /tdigest@0.1.2: + resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + dependencies: + bintrees: 1.0.2 + dev: false + /term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'}