Add navigation & usage metrics for end-user experience visibility

Instrument DB query timing (11 operations across 3 repositories),
streaming lifecycle (TTFB, duration, feature count), cache operation
latency, listing detail step breakdown, and frontend page load /
time-to-first-listing / stream download / detail load metrics.

Adds 16 new OTel instruments, extends the perf ingestion endpoint
with 4 new frontend metrics, and adds ~20 Grafana dashboard panels
across 4 new rows (DB Query Performance, Streaming Performance,
Listing Detail Breakdown, Cache Performance, Frontend Navigation).
This commit is contained in:
Viktor Barzin 2026-02-23 20:28:42 +00:00
parent 1ae00b7cbf
commit 35f1987ac1
No known key found for this signature in database
GPG key ID: 0EB088298288D958
11 changed files with 1236 additions and 26 deletions

View file

@ -6,6 +6,7 @@ import type { ParameterValues } from '@/components/FilterPanel';
import { ApiError } from '@/types';
import { API_ENDPOINTS } from '@/constants';
import { fireUnauthorized } from './apiClient';
import { record as recordPerf } from './perfCollector';
/**
* Build query string from parameters object
@ -99,6 +100,8 @@ export async function* streamListingGeoJSON(
const decoder = new TextDecoder();
let buffer = '';
let totalCount = 0;
let streamStart = performance.now();
let firstBatchRecorded = false;
while (true) {
if (options?.signal?.aborted) {
@ -123,6 +126,10 @@ export async function* streamListingGeoJSON(
} else if (message.type === 'batch' && message.features) {
totalCount += message.features.length;
onProgress?.({ count: totalCount });
if (!firstBatchRecorded) {
recordPerf('time_to_first_listing', 'stream', (performance.now() - streamStart) / 1000);
firstBatchRecorded = true;
}
yield message.features;
} else if (message.type === 'complete') {
onProgress?.({ count: message.total ?? totalCount, total: message.total });
@ -144,4 +151,7 @@ export async function* streamListingGeoJSON(
console.error('Failed to parse final streaming message:', e);
}
}
// Record total stream download duration
recordPerf('stream_download', 'stream', (performance.now() - streamStart) / 1000);
}