SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
4 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
2.3 KB · 49 lines typescript
Raw Blame History
1/** /api/health, /api/ready, /api/metrics — no cache, no envelope. */2import type { FastifyInstance } from "fastify";3import { performance, type EventLoopUtilization } from "node:perf_hooks";4import { pg } from "../lib/sql.js";5import { metrics } from "../metrics.js";6import { cacheStats } from "../cache.js";7import { redisPing } from "../redis.js";89export const API_VERSION = "0.1.0";1011export async function systemRoutes(app: FastifyInstance): Promise<void> {12  app.get("/api/health", { schema: { summary: "Liveness (fast, no I/O)", tags: ["system"] } }, async (_req, reply) => {13    reply.header("cache-control", "no-store");14    return { ok: true, service: "dci-api", version: API_VERSION, uptimeSec: Math.round(process.uptime()), now: new Date().toISOString() };15  });1617  app.get("/api/ready", { schema: { summary: "Readiness: Postgres ping (Redis reported, not required)", tags: ["system"] } }, async (_req, reply) => {18    reply.header("cache-control", "no-store");19    const started = Date.now();20    let db = false;21    let dbError: string | null = null;22    try {23      const sql = pg();24      await Promise.race([sql`select 1`, new Promise((_, rej) => setTimeout(() => rej(new Error("db timeout")), 3_000))]);25      db = true;26    } catch (e) {27      dbError = (e as Error).message;28    }29    const redis = await redisPing();30    const body = { ok: db, db, dbError, redis, dbLatencyMs: Date.now() - started, now: new Date().toISOString() };31    return reply.code(db ? 200 : 503).send(body);32  });3334  app.get("/api/metrics", { schema: { summary: "Prometheus metrics", tags: ["system"] } }, async (_req, reply) => {35    const cs = cacheStats();36    const poolMax = Number(process.env.PG_POOL_MAX ?? 16);37    reply.header("cache-control", "no-store");38    reply.type("text/plain; version=0.0.4; charset=utf-8");39    return metrics.render({ dci_api_cache_memory_entries: cs.entries, dci_api_cache_memory_max: cs.max, dci_api_db_pool_max: poolMax, dci_api_process_rss_bytes: process.memoryUsage().rss, dci_api_event_loop_utilization: Math.round(performanceUtilization() * 1000) / 1000 });40  });41}4243let lastElu: EventLoopUtilization | null = null;44function performanceUtilization(): number {45  const cur = lastElu ? performance.eventLoopUtilization(lastElu) : performance.eventLoopUtilization();46  lastElu = performance.eventLoopUtilization();47  return cur.utilization;48}49