Files
ln-bi/src/server/source-telemetry.ts
T

79 lines
2.3 KiB
TypeScript

export const DATA_SOURCE_IDS = [
'oneOsMileage',
'hydrogenDatabase',
'electricDatabase',
'etcDatabase',
] as const;
export type DataSourceId = typeof DATA_SOURCE_IDS[number];
interface Outcome {
success: boolean;
at: number;
}
const MAX_OUTCOMES = 100;
const outcomes = new Map<DataSourceId, Outcome[]>();
const startedAt = new Date().toISOString();
function lastMatching(entries: Outcome[], success: boolean): Outcome | undefined {
for (let index = entries.length - 1; index >= 0; index -= 1) {
if (entries[index].success === success) return entries[index];
}
return undefined;
}
export function recordDataSourceOutcome(
source: DataSourceId,
success: boolean,
at = Date.now(),
): void {
const entries = outcomes.get(source) || [];
entries.push({ success, at });
if (entries.length > MAX_OUTCOMES) entries.splice(0, entries.length - MAX_OUTCOMES);
outcomes.set(source, entries);
}
export async function observeDataSource<T>(
source: DataSourceId,
operation: () => Promise<T>,
): Promise<T> {
try {
const result = await operation();
recordDataSourceOutcome(source, true);
return result;
} catch (error) {
recordDataSourceOutcome(source, false);
throw error;
}
}
export function getDataSourceTelemetry(now = new Date()) {
return {
semantics: 'observed-requests' as const,
windowSize: MAX_OUTCOMES,
processStartedAt: startedAt,
checkedAt: now.toISOString(),
sources: DATA_SOURCE_IDS.map(source => {
const entries = outcomes.get(source) || [];
const failures = entries.filter(entry => !entry.success).length;
const last = entries.at(-1);
const lastSuccess = lastMatching(entries, true);
const lastFailure = lastMatching(entries, false);
return {
source,
state: !last ? 'unobserved' as const : last.success ? 'available' as const : 'failing' as const,
attempts: entries.length,
failures,
failureRate: entries.length > 0 ? Math.round(failures / entries.length * 10_000) / 10_000 : null,
lastSuccessAt: lastSuccess ? new Date(lastSuccess.at).toISOString() : null,
lastFailureAt: lastFailure ? new Date(lastFailure.at).toISOString() : null,
};
}),
};
}
export function clearDataSourceTelemetry(): void {
outcomes.clear();
}