Files
EpicNext-Cms/src/lib/performance-diagnostics.ts
T
Simo baeb54aeb5
CI / check (push) Successful in 2m23s
CI / deploy (push) Successful in 1m13s
CI / publish-container (push) Successful in 44s
feat(devops): separate public page server timing diagnostics
2026-09-11 10:49:08 +02:00

98 lines
2.7 KiB
TypeScript

import { AsyncLocalStorage } from "node:async_hooks";
type Dependency = "database" | "external";
type Measurement = { count: number; durationMs: number; errors: number };
export type PerformanceMetrics = Record<Dependency, Measurement> & {
durationMs: number;
};
const context = new AsyncLocalStorage<PerformanceMetrics>();
export async function collectPerformance<T>(
work: () => Promise<T>,
onComplete?: (metrics: PerformanceMetrics) => void,
) {
const metrics: PerformanceMetrics = {
durationMs: 0,
database: { count: 0, durationMs: 0, errors: 0 },
external: { count: 0, durationMs: 0, errors: 0 },
};
const start = performance.now();
try {
const value = await context.run(metrics, work);
metrics.durationMs = performance.now() - start;
return { value, metrics: structuredClone(metrics) };
} finally {
metrics.durationMs = performance.now() - start;
try {
onComplete?.(structuredClone(metrics));
} catch {
/* Diagnostics must not change application outcomes. */
}
}
}
export async function measureDependency<T>(
kind: Dependency,
work: () => Promise<T>,
): Promise<T> {
const metrics = context.getStore();
if (!metrics) return work();
const start = performance.now();
const measurement = metrics[kind];
try {
return await work();
} catch (error) {
measurement.errors++;
throw error;
} finally {
measurement.count++;
measurement.durationMs += performance.now() - start;
}
}
/** Promise driver only. Preserve receiver, overloads and connection release behavior. */
export function instrumentDatabase<T extends object>(driver: T): T {
return new Proxy(driver, {
get(target, property) {
const value = Reflect.get(target, property, target);
if (typeof value !== "function") return value;
if (property === "query" || property === "execute") {
return (...args: unknown[]) =>
measureDependency("database", () =>
Reflect.apply(value, target, args),
);
}
if (property === "getConnection") {
return async (...args: unknown[]) =>
instrumentDatabase(await Reflect.apply(value, target, args));
}
return value.bind(target);
},
});
}
export function routeTemplate(
pathname: string,
params: Record<string, string | string[]> = {},
) {
const dynamic = new Map<string, string>();
for (const [key, value] of Object.entries(params)) {
for (const part of Array.isArray(value) ? value : [value])
dynamic.set(part, `[${key}]`);
}
return pathname
.split("?")[0]
.split("/")
.map((part) => {
let decoded = part;
try {
decoded = decodeURIComponent(part);
} catch {
return "[invalid]";
}
return dynamic.get(decoded) ?? (/^\d+$/.test(part) ? "[id]" : part);
})
.join("/")
.slice(0, 300);
}