import { NextResponse } from "next/server"; import { eventBus } from "@capakraken/api/sse"; export const dynamic = "force-dynamic"; export const runtime = "nodejs"; /** * GET /api/perf — Runtime performance metrics. * * Protected by CRON_SECRET header or query param. * Returns Node.js memory usage, process uptime, and SSE connection count. */ export function GET(request: Request) { const cronSecret = process.env["CRON_SECRET"]; if (cronSecret) { const url = new URL(request.url); const headerToken = request.headers.get("authorization")?.replace("Bearer ", ""); const queryToken = url.searchParams.get("token"); if (headerToken !== cronSecret && queryToken !== cronSecret) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } const mem = process.memoryUsage(); return NextResponse.json({ timestamp: new Date().toISOString(), uptime: { seconds: Math.round(process.uptime()), formatted: formatUptime(process.uptime()), }, memory: { heapUsedMB: round(mem.heapUsed / 1024 / 1024), heapTotalMB: round(mem.heapTotal / 1024 / 1024), rssMB: round(mem.rss / 1024 / 1024), externalMB: round(mem.external / 1024 / 1024), arrayBuffersMB: round(mem.arrayBuffers / 1024 / 1024), }, sse: { activeConnections: eventBus.subscriberCount, }, node: { version: process.version, platform: process.platform, arch: process.arch, }, }); } function round(n: number): number { return Math.round(n * 100) / 100; } function formatUptime(seconds: number): string { const d = Math.floor(seconds / 86400); const h = Math.floor((seconds % 86400) / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); const parts: string[] = []; if (d > 0) parts.push(`${d}d`); if (h > 0) parts.push(`${h}h`); if (m > 0) parts.push(`${m}m`); parts.push(`${s}s`); return parts.join(" "); }