Files
iptvnator/apps/electron-backend-e2e/src/performance/xtream-worker-request-evidence.fixtures.ts
T
4gray a2fafcfc08 test(performance): add end-to-end Xtream benchmark harness (#1300)
* docs(performance): plan Xtream benchmark

* feat(xtream-mock-server): add deterministic 100k fixture

* style(xtream-mock-server): apply repository formatting

* fix(xtream-mock-server): harden performance fixture data

* feat(xtream-mock-server): add performance control plane

* docs(performance): correct Xtream capture plan

* fix(xtream-mock-server): harden performance controls

* fix(xtream-mock-server): harden control lifecycle

* feat(performance): add Xtream preload markers

* feat(performance): trace Xtream main phases

* feat(performance): mark Xtream store publications

* feat(performance): trace Xtream database phases

* feat(performance): trace Xtream delete cancellation

* feat(performance): capture Xtream phase attribution

* feat(performance): mark Sources Xtream refresh

* test(performance): define Xtream benchmark evidence contracts

* test(performance): add Xtream benchmark runner

* test(performance): surface failure evidence writes

* test(performance): align database read clock

* test(performance): preserve capture failure contracts
2026-07-28 08:08:07 +02:00

246 lines
8.0 KiB
TypeScript

import type {
DatabaseWorkerPerformancePhase,
PerformancePhaseEvent,
} from '@iptvnator/shared/interfaces';
import {
expectedXtreamWorkerRequestInventory,
XTREAM_SCENARIO_ID,
type XtreamScenarioId,
} from './xtream-benchmark-contract';
import type { WorkerRequestPerformanceMetrics } from './m3u-refresh-cancellation-contract';
import {
XTREAM_WORKER_REQUEST_IDENTITY_KIND,
xtreamWorkerRequestIdentityKind,
} from './xtream-worker-request-identity';
import { normalizeWorkerRequestPerformanceOutcome } from './worker-request-performance';
export function workerRequestEvidence(
scenarioId: XtreamScenarioId
): WorkerRequestPerformanceMetrics[] {
let requestOrdinal = 0;
const operationOrdinals = new Map<string, number>();
return expectedXtreamWorkerRequestInventory(scenarioId).flatMap((entry) =>
Array.from({ length: entry.count }, () => {
requestOrdinal += 1;
const operationOrdinal =
(operationOrdinals.get(entry.operation) ?? 0) + 1;
operationOrdinals.set(entry.operation, operationOrdinal);
return request(
scenarioId,
entry.operation,
entry.phases,
entry.success,
operationOrdinal,
requestOrdinal
);
})
);
}
export function invalidateWorkerRequestMetrics(
requests: readonly WorkerRequestPerformanceMetrics[],
count: number
): WorkerRequestPerformanceMetrics[] {
return requests.map((request, index) =>
index >= count
? request
: {
...request,
eventLoopDelay: null,
eventLoopDelayUnavailableReason:
'overlapping-database-worker-requests',
eventLoopUtilization: null,
eventLoopUtilizationUnavailableReason:
'overlapping-database-worker-requests',
histogramFlushedEpochMs: null,
invalidReason: 'overlapping-database-worker-requests',
threadCpuSystemMicros: null,
threadCpuUnavailableReason:
'overlapping-database-worker-requests',
threadCpuUserMicros: null,
}
);
}
function request(
scenarioId: XtreamScenarioId,
operation: string,
phases: readonly string[],
success: boolean,
operationOrdinal: number,
requestOrdinal: number
): WorkerRequestPerformanceMetrics {
const requestId = `request-${requestOrdinal}`;
const start = 1_000 + requestOrdinal * 20;
const phaseEvents = createPhaseEvents(
phases,
phaseItemCounts(scenarioId, operation, operationOrdinal),
requestId,
start + 2,
success
);
const identity = requestIdentity(
scenarioId,
operation,
operationOrdinal,
requestOrdinal,
start
);
return normalizeWorkerRequestPerformanceOutcome({
...identity,
operation,
performanceCapture: {
eventLoopDelay: { maxMs: 9, p95Ms: 3, p99Ms: 5 },
eventLoopDelayUnavailableReason: null,
eventLoopUtilization: 0.5,
eventLoopUtilizationUnavailableReason: null,
histogramFlushedEpochMs: start + 13,
invalidReason: null,
phaseEvents,
requestReceivedEpochMs: start,
responsePostedEpochMs: start + 14,
threadCpuSystemMicros: 10,
threadCpuUnavailableReason: null,
threadCpuUserMicros: 20,
workEndedEpochMs: start + 12,
workStartedEpochMs: start + 1,
},
playlistId: 'synthetic-playlist',
requestId,
responseEpochMs: start + 15,
success,
});
}
function requestIdentity(
scenarioId: XtreamScenarioId,
operation: string,
operationOrdinal: number,
requestOrdinal: number,
start: number
) {
const kind = xtreamWorkerRequestIdentityKind(operation);
if (kind === XTREAM_WORKER_REQUEST_IDENTITY_KIND.NOT_APPLICABLE) {
return {
ipcCallId: null,
operationId: null,
operationIdUnavailableReason:
'preload-performance-marker-not-applicable',
sourceEpochMs: null,
} as const;
}
const correlatedLegacy =
kind === XTREAM_WORKER_REQUEST_IDENTITY_KIND.LEGACY_PRELOAD &&
(scenarioId === XTREAM_SCENARIO_ID.REFRESH_LARGE ||
scenarioId === XTREAM_SCENARIO_ID.BACKGROUND_UI) &&
(operation === 'DB_UPSERT_APP_PLAYLIST' || operationOrdinal === 1);
const emptyDeleteFollowUp =
operation === 'DB_DELETE_PLAYLIST' && operationOrdinal === 2;
const operationId =
(kind === XTREAM_WORKER_REQUEST_IDENTITY_KIND.PRODUCER_OPERATION ||
correlatedLegacy) &&
!emptyDeleteFollowUp
? `operation-${requestOrdinal}`
: null;
return {
ipcCallId: requestOrdinal,
operationId,
operationIdUnavailableReason:
kind === XTREAM_WORKER_REQUEST_IDENTITY_KIND.LEGACY_PRELOAD &&
!correlatedLegacy
? 'preload-performance-marker-uncorrelated'
: null,
sourceEpochMs: start - 1,
} as const;
}
function createPhaseEvents(
phases: readonly string[],
itemCounts: readonly (number | null)[],
requestId: string,
firstEpochMs: number,
success: boolean
): PerformancePhaseEvent<DatabaseWorkerPerformancePhase>[] {
return phases.flatMap((phase, index) => {
const startEpochMs = firstEpochMs + index * 2;
const itemCount = itemCounts[index] ?? null;
const omitCancelledWriteCount = !success && index === phases.length - 1;
return [
{
boundary: 'start' as const,
durationMs: null,
epochMs: startEpochMs,
phase: phase as DatabaseWorkerPerformancePhase,
requestId,
},
{
boundary: 'end' as const,
durationMs: 1,
epochMs: startEpochMs + 1,
...(!omitCancelledWriteCount && itemCount !== null
? { metadata: { itemCount } }
: {}),
phase: phase as DatabaseWorkerPerformancePhase,
requestId,
},
];
});
}
function phaseItemCounts(
scenarioId: XtreamScenarioId,
operation: string,
ordinal: number
): readonly (number | null)[] {
if (operation === 'DB_UPSERT_APP_PLAYLIST') return [null, 1];
if (operation === 'DB_GET_APP_PLAYLIST') return [1, 0];
if (operation === 'DB_DELETE_XTREAM_CONTENT') return [100_100, 100_100];
if (operation === 'DB_DELETE_PLAYLIST') {
return ordinal === 1 ? [100_100, 100_101] : [0, 1];
}
if (operation === 'DB_GET_CATEGORIES') {
return (
[[0], [60], [0], [20], [0], [20], [60], [20], [20]][ordinal - 1] ??
[]
);
}
if (operation === 'DB_SAVE_CATEGORIES') {
const count = [60, 20, 20][ordinal - 1] ?? 0;
return [count, count];
}
if (operation === 'DB_GET_CONTENT') {
if (scenarioId === XTREAM_SCENARIO_ID.CANCEL_IMPORT) return [0];
return (
[
[0],
[60_000],
[0],
[20_000],
[0],
[20_000],
[60_000],
[20_000],
[20_000],
][ordinal - 1] ?? []
);
}
if (operation === 'DB_SAVE_CONTENT') {
if (scenarioId === XTREAM_SCENARIO_ID.CANCEL_IMPORT) {
return [60, 60_000, null];
}
const index = ordinal - 1;
return (
[
[60, 60_000, 60_000],
[20, 20_000, 20_000],
[20, 20_000, 20_000],
][index] ?? []
);
}
if (operation === 'DB_CLEAR_XTREAM_IMPORT_CACHE') {
return [ordinal === 1 ? 6_060 : 20];
}
return [];
}