/** * Opt-in performance harness. Nothing in src/ imports this file, so the app * runs without it. Load it with `yarn perf` (or `node --import ./tools/perf-hooks.mjs`). */ import mongoose from "mongoose"; import { monitorEventLoopDelay, performance } from "node:perf_hooks"; import express from "express"; type Metric = { count: number; totalMs: number; maxMs: number; over100Ms: number; }; type RequestMetric = { count: number; totalMs: number; maxMs: number; over500Ms: number; }; const dbMetrics = new Map(); const requestMetrics = new Map(); const eventLoop = monitorEventLoopDelay({ resolution: 20 }); eventLoop.enable(); // --- Database timings ------------------------------------------------------- // Mongoose emits these for every query, so no route code has to be touched. export function register() { mongoose.connection.on('connected', () => { const client = mongoose.connection.getClient(); client.on('commandStarted', (event: any) => { (event as any).__startedAt = performance.now(); }); client.on('commandSucceeded', (event: any) => { recordDb(`${event.commandName}.${Object.keys(event.command ?? {})[0] ?? 'unknown'}`, event); }); client.on('commandFailed', (event: any) => { recordDb(`${event.commandName}.failed`, event); }); }); } function recordDb(label: string, event: any) { const startedAt = event.__startedAt; if (typeof startedAt !== 'number') return; const durationMs = performance.now() - startedAt; let metric = dbMetrics.get(label); if (!metric) { metric = { count: 0, totalMs: 0, maxMs: 0, over100Ms: 0 }; dbMetrics.set(label, metric); } metric.count++; metric.totalMs += durationMs; metric.maxMs = Math.max(metric.maxMs, durationMs); if (durationMs > 100) metric.over100Ms++; } // --- Request timings -------------------------------------------------------- // Installed by patching express.json() from the loader, so index.ts stays clean. export function requestTimer() { const originalJson = express.json.bind(express); express.json = ((...args: any[]) => { const parser = (originalJson as any)(...args); return (req: any, res: any, next: any) => { const started = performance.now(); res.on("finish", () => { const durationMs = performance.now() - started; const key = `${req.method} ${req.route?.path ?? req.path} ${res.statusCode}`; let metric = requestMetrics.get(key); if (!metric) { metric = { count: 0, totalMs: 0, maxMs: 0, over500Ms: 0 }; requestMetrics.set(key, metric); } const current = metric; current.count++; current.totalMs += durationMs; current.maxMs = Math.max(current.maxMs, durationMs); if (durationMs > 500) current.over500Ms++; }); parser(req, res, next); }; }) as typeof express.json; } // --- Reporting -------------------------------------------------------------- function summarize(entries: [string, T][]) { return Object.fromEntries( entries.map(([key, value]) => [ key, { count: value.count, avgMs: Number((value.totalMs / value.count).toFixed(2)), maxMs: Number(value.maxMs.toFixed(2)), }, ]), ); } setInterval(() => { if (requestMetrics.size === 0 && dbMetrics.size === 0) return; console.log(JSON.stringify({ type: "performance_summary", timestamp: new Date().toISOString(), requests: summarize([...requestMetrics.entries()]), database: summarize([...dbMetrics.entries()]), eventLoopDelayMs: { mean: Number((eventLoop.mean / 1e6 || 0).toFixed(2)), p95: Number((eventLoop.percentile(95) / 1e6).toFixed(2)), max: Number((eventLoop.max / 1e6).toFixed(2)), }, })); requestMetrics.clear(); dbMetrics.clear(); eventLoop.reset(); }, 10_000).unref();