Add frontend performance metrics pipeline to Prometheus

Collect browser-side worker round-trips, computation times, main-thread
operations, and feature counts, batch them client-side, and expose as
Prometheus histograms via a new POST /api/perf endpoint.
This commit is contained in:
Viktor Barzin 2026-02-22 17:30:29 +00:00
parent c24c3a545c
commit d90fa38776
No known key found for this signature in database
GPG key ID: 0EB088298288D958
10 changed files with 188 additions and 5 deletions

View file

@ -7,3 +7,4 @@ export { checkBackendHealth, type HealthStatus, type HealthCheckResult } from '.
export { fetchUserPOIs, createPOI, updatePOI, deletePOI, triggerPOICalculation, fetchPOIDistances, fetchBulkPOIDistances } from './poiService';
export { fetchDecisions, setDecision, clearDecision } from './decisionService';
export { fetchListingDetail } from './listingDetailService';
export { record as recordPerf, startCollector, stopCollector } from './perfCollector';

View file

@ -0,0 +1,46 @@
interface PerfSample {
metric: string;
operation: string;
value: number;
}
const FLUSH_INTERVAL_MS = 30_000;
let batch: PerfSample[] = [];
let flushTimer: ReturnType<typeof setInterval> | null = null;
export function record(metric: string, operation: string, value: number): void {
batch.push({ metric, operation, value });
}
function flush(): void {
if (batch.length === 0) return;
const blob = new Blob([JSON.stringify(batch)], { type: 'application/json' });
batch = [];
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/perf', blob);
} else {
fetch('/api/perf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: blob,
keepalive: true,
}).catch(() => {});
}
}
export function startCollector(): void {
if (flushTimer) return;
flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flush();
});
}
export function stopCollector(): void {
if (flushTimer) {
clearInterval(flushTimer);
flushTimer = null;
}
flush();
}