Files
Nexus/apps/web/src/app/api/perf/route.ts
T
Hartmut b41c1d2501
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)

Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
2026-05-21 16:28:40 +02:00

62 lines
1.8 KiB
TypeScript

import { NextResponse } from "next/server";
import { eventBus } from "@nexus/api/sse";
import { verifyCronSecret } from "~/lib/cron-auth.js";
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
/**
* GET /api/perf — Runtime performance metrics.
*
* Protected by CRON_SECRET via `Authorization: Bearer <secret>` header only.
* Query-string authentication is not supported (secrets must not appear in URLs).
* Fails closed (401) when CRON_SECRET is not configured in the environment.
* Returns Node.js memory usage, process uptime, and SSE connection count.
*/
export function GET(request: Request) {
const deny = verifyCronSecret(request);
if (deny) return deny;
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(" ");
}