Files
iptvnator/apps/electron-backend-e2e/src/performance/worker-request-performance.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

389 lines
13 KiB
TypeScript

import type {
EventLoopDelayMetrics,
MainCaptureMetrics,
WorkerCaptureMetrics,
WorkerPostGcHeapCapture,
WorkerRequestPerformanceMetrics,
} from './m3u-refresh-cancellation-contract';
import { WORKER_POST_GC_HEAP_UNAVAILABLE_REASON } from './m3u-refresh-cancellation-contract';
import { parseWorkerPerformancePhaseEvents } from './worker-performance-phase-events';
export interface WorkerRequestPerformanceOutcomeTransport {
readonly ipcCallId: number | null;
readonly operation: string | null;
readonly operationId: string | null;
readonly operationIdUnavailableReason: string | null;
readonly performanceCapture: unknown;
readonly playlistId: string | null;
readonly requestId: string | null;
readonly responseEpochMs: number;
readonly sourceEpochMs: number | null;
readonly success: boolean;
}
export interface MainCaptureGenerationTransport {
readonly captureGeneration: number;
readonly metrics: Omit<MainCaptureMetrics, 'workers'>;
readonly workers: readonly {
readonly captureGeneration: number | null;
readonly metrics: Omit<
WorkerCaptureMetrics,
'eventLoopDelay' | 'eventLoopDelayUnavailableReason' | 'requests'
>;
readonly requests: readonly WorkerRequestPerformanceOutcomeTransport[];
}[];
}
type ParsedWorkerPerformanceCapture = Pick<
WorkerRequestPerformanceMetrics,
| 'eventLoopDelay'
| 'eventLoopDelayUnavailableReason'
| 'eventLoopUtilization'
| 'eventLoopUtilizationUnavailableReason'
| 'histogramFlushedEpochMs'
| 'invalidReason'
| 'phaseEvents'
| 'requestReceivedEpochMs'
| 'responsePostedEpochMs'
| 'threadCpuSystemMicros'
| 'threadCpuUnavailableReason'
| 'threadCpuUserMicros'
| 'workEndedEpochMs'
| 'workStartedEpochMs'
>;
const INVALID_REASONS = new Set(['overlapping-database-worker-requests']);
const EVENT_LOOP_DELAY_REASONS = new Set([
...INVALID_REASONS,
'event-loop-delay-arm-timeout',
'event-loop-delay-capture-unavailable',
'event-loop-delay-flush-timeout',
'event-loop-delay-invalid',
]);
const EVENT_LOOP_UTILIZATION_REASONS = new Set([
...INVALID_REASONS,
'event-loop-utilization-unavailable',
]);
const THREAD_CPU_REASONS = new Set([
...INVALID_REASONS,
'thread-cpu-usage-invalid',
'thread-cpu-usage-unavailable',
]);
const WORKER_POST_GC_HEAP_UNAVAILABLE_REASONS = new Set<string>(
Object.values(WORKER_POST_GC_HEAP_UNAVAILABLE_REASON)
);
function isRecord(input: unknown): input is Record<string, unknown> {
return typeof input === 'object' && input !== null && !Array.isArray(input);
}
function isFiniteNonNegativeNumber(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
}
function isNullableFiniteNonNegativeNumber(
value: unknown
): value is number | null {
return value === null || isFiniteNonNegativeNumber(value);
}
function isNullableReason(
value: unknown,
allowed: ReadonlySet<string>
): value is string | null {
return value === null || (typeof value === 'string' && allowed.has(value));
}
function normalizeWorkerPostGcHeapCapture(
input: Record<string, unknown>
): WorkerPostGcHeapCapture {
const postGcHeapUnavailableReason = input['postGcHeapUnavailableReason'];
const postGcHeapUsedBytes = input['postGcHeapUsedBytes'];
if (
Number.isSafeInteger(postGcHeapUsedBytes) &&
Number(postGcHeapUsedBytes) >= 0 &&
postGcHeapUnavailableReason === null
) {
return {
postGcHeapUnavailableReason: null,
postGcHeapUsedBytes: Number(postGcHeapUsedBytes),
};
}
if (
postGcHeapUsedBytes === null &&
typeof postGcHeapUnavailableReason === 'string' &&
WORKER_POST_GC_HEAP_UNAVAILABLE_REASONS.has(postGcHeapUnavailableReason)
) {
return {
postGcHeapUnavailableReason,
postGcHeapUsedBytes: null,
} as WorkerPostGcHeapCapture;
}
return {
postGcHeapUnavailableReason:
WORKER_POST_GC_HEAP_UNAVAILABLE_REASON.INVALID_CAPTURE,
postGcHeapUsedBytes: null,
};
}
function parseEventLoopDelay(
input: unknown
): EventLoopDelayMetrics | null | undefined {
if (input === null) {
return null;
}
if (!isRecord(input)) {
return undefined;
}
const maxMs = input['maxMs'];
const p95Ms = input['p95Ms'];
const p99Ms = input['p99Ms'];
if (
!isFiniteNonNegativeNumber(maxMs) ||
!isFiniteNonNegativeNumber(p95Ms) ||
!isFiniteNonNegativeNumber(p99Ms) ||
p95Ms > p99Ms ||
p99Ms > maxMs
) {
return undefined;
}
return { maxMs, p95Ms, p99Ms };
}
function parseWorkerPerformanceCapture(
input: unknown,
operation: string | null,
requestSucceeded: boolean
): ParsedWorkerPerformanceCapture | null {
if (!isRecord(input)) {
return null;
}
const eventLoopDelay = parseEventLoopDelay(input['eventLoopDelay']);
const eventLoopDelayUnavailableReason =
input['eventLoopDelayUnavailableReason'];
const eventLoopUtilization = input['eventLoopUtilization'];
const eventLoopUtilizationUnavailableReason =
input['eventLoopUtilizationUnavailableReason'];
const histogramFlushedEpochMs = input['histogramFlushedEpochMs'];
const invalidReason = input['invalidReason'];
const requestReceivedEpochMs = input['requestReceivedEpochMs'];
const responsePostedEpochMs = input['responsePostedEpochMs'];
const threadCpuSystemMicros = input['threadCpuSystemMicros'];
const threadCpuUnavailableReason = input['threadCpuUnavailableReason'];
const threadCpuUserMicros = input['threadCpuUserMicros'];
const workEndedEpochMs = input['workEndedEpochMs'];
const workStartedEpochMs = input['workStartedEpochMs'];
if (
eventLoopDelay === undefined ||
!isNullableReason(
eventLoopDelayUnavailableReason,
EVENT_LOOP_DELAY_REASONS
) ||
!isNullableFiniteNonNegativeNumber(eventLoopUtilization) ||
(eventLoopUtilization !== null && eventLoopUtilization > 1) ||
!isNullableReason(
eventLoopUtilizationUnavailableReason,
EVENT_LOOP_UTILIZATION_REASONS
) ||
!isNullableFiniteNonNegativeNumber(histogramFlushedEpochMs) ||
!isNullableReason(invalidReason, INVALID_REASONS) ||
!isFiniteNonNegativeNumber(requestReceivedEpochMs) ||
!isFiniteNonNegativeNumber(responsePostedEpochMs) ||
!isNullableFiniteNonNegativeNumber(threadCpuSystemMicros) ||
!isNullableReason(threadCpuUnavailableReason, THREAD_CPU_REASONS) ||
!isNullableFiniteNonNegativeNumber(threadCpuUserMicros) ||
!isFiniteNonNegativeNumber(workEndedEpochMs) ||
!isFiniteNonNegativeNumber(workStartedEpochMs)
) {
return null;
}
const phaseEvents = parseWorkerPerformancePhaseEvents(
operation,
input['phaseEvents'],
workStartedEpochMs,
workEndedEpochMs,
requestSucceeded
);
if (phaseEvents === null) {
return null;
}
const timestampsAreOrdered =
requestReceivedEpochMs <= workStartedEpochMs &&
workStartedEpochMs <= workEndedEpochMs &&
workEndedEpochMs <= responsePostedEpochMs &&
(histogramFlushedEpochMs === null ||
(workEndedEpochMs <= histogramFlushedEpochMs &&
histogramFlushedEpochMs <= responsePostedEpochMs));
const eventLoopDelayIsCoherent =
eventLoopDelay === null
? eventLoopDelayUnavailableReason !== null &&
(eventLoopDelayUnavailableReason === 'event-loop-delay-invalid'
? histogramFlushedEpochMs !== null
: histogramFlushedEpochMs === null)
: eventLoopDelayUnavailableReason === null &&
histogramFlushedEpochMs !== null;
const eventLoopUtilizationIsCoherent =
eventLoopUtilization === null
? eventLoopUtilizationUnavailableReason !== null
: eventLoopUtilizationUnavailableReason === null;
const threadCpuIsCoherent =
threadCpuSystemMicros === null && threadCpuUserMicros === null
? threadCpuUnavailableReason !== null
: threadCpuSystemMicros !== null &&
threadCpuUserMicros !== null &&
threadCpuUnavailableReason === null;
const invalidCaptureIsCoherent =
invalidReason === null ||
(eventLoopDelay === null &&
eventLoopDelayUnavailableReason === invalidReason &&
eventLoopUtilization === null &&
eventLoopUtilizationUnavailableReason === invalidReason &&
histogramFlushedEpochMs === null &&
threadCpuSystemMicros === null &&
threadCpuUnavailableReason === invalidReason &&
threadCpuUserMicros === null);
if (
!timestampsAreOrdered ||
!eventLoopDelayIsCoherent ||
!eventLoopUtilizationIsCoherent ||
!threadCpuIsCoherent ||
!invalidCaptureIsCoherent
) {
return null;
}
return {
eventLoopDelay,
eventLoopDelayUnavailableReason,
eventLoopUtilization,
eventLoopUtilizationUnavailableReason,
histogramFlushedEpochMs,
invalidReason,
phaseEvents,
requestReceivedEpochMs,
responsePostedEpochMs,
threadCpuSystemMicros,
threadCpuUnavailableReason,
threadCpuUserMicros,
workEndedEpochMs,
workStartedEpochMs,
};
}
export function normalizeWorkerRequestPerformanceOutcome(
input: WorkerRequestPerformanceOutcomeTransport
): WorkerRequestPerformanceMetrics {
const identity = {
ipcCallId: input.ipcCallId,
operation: input.operation,
operationId: input.operationId,
operationIdUnavailableReason: input.operationIdUnavailableReason,
playlistId: input.playlistId,
requestId: input.requestId,
responseEpochMs: input.responseEpochMs,
sourceEpochMs: input.sourceEpochMs,
success: input.success,
};
const unavailable = (
reason:
| 'worker-performance-capture-invalid'
| 'worker-performance-capture-missing'
): WorkerRequestPerformanceMetrics => ({
eventLoopDelay: null,
eventLoopDelayUnavailableReason: null,
eventLoopUtilization: null,
eventLoopUtilizationUnavailableReason: null,
histogramFlushedEpochMs: null,
invalidReason: null,
...identity,
performanceCaptureUnavailableReason: reason,
phaseEvents: [],
requestReceivedEpochMs: null,
responsePostedEpochMs: null,
threadCpuSystemMicros: null,
threadCpuUnavailableReason: null,
threadCpuUserMicros: null,
workEndedEpochMs: null,
workStartedEpochMs: null,
});
if (
input.performanceCapture === undefined ||
input.performanceCapture === null
) {
return unavailable('worker-performance-capture-missing');
}
const performanceCapture = parseWorkerPerformanceCapture(
input.performanceCapture,
input.operation,
input.success
);
if (!performanceCapture) {
return unavailable('worker-performance-capture-invalid');
}
if (
!isFiniteNonNegativeNumber(input.responseEpochMs) ||
performanceCapture.responsePostedEpochMs > input.responseEpochMs
) {
return unavailable('worker-performance-capture-invalid');
}
if (
performanceCapture.phaseEvents.length > 0 &&
(input.requestId === null ||
performanceCapture.phaseEvents.some(
(event) => event.requestId !== input.requestId
))
) {
return unavailable('worker-performance-capture-invalid');
}
return {
...performanceCapture,
...identity,
performanceCaptureUnavailableReason: null,
};
}
export function selectMainCaptureGeneration(
input: MainCaptureGenerationTransport
): MainCaptureMetrics {
const workers = input.workers
.filter(
(worker) => worker.captureGeneration === input.captureGeneration
)
.map((worker): WorkerCaptureMetrics => {
const requests = worker.requests.map(
normalizeWorkerRequestPerformanceOutcome
);
const onlyRequest =
requests.length === 1 ? (requests[0] ?? null) : null;
const postGcHeap = normalizeWorkerPostGcHeapCapture(
worker.metrics as unknown as Record<string, unknown>
);
return {
...worker.metrics,
...postGcHeap,
eventLoopDelay: onlyRequest?.eventLoopDelay ?? null,
eventLoopDelayUnavailableReason: onlyRequest
? (onlyRequest.eventLoopDelayUnavailableReason ??
onlyRequest.performanceCaptureUnavailableReason ??
'worker-self-profile-result-unavailable')
: requests.length > 1
? 'multiple-request-scoped-captures'
: worker.metrics.terminatedEpochMs !== null
? 'worker-terminated-before-profile-flush'
: 'worker-self-profile-result-unavailable',
requests,
};
});
return {
...input.metrics,
workers,
};
}