Files
iptvnator/apps/electron-backend-e2e/src/performance/xtream-ipc-marker-protocol.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

334 lines
13 KiB
TypeScript

import type { XtreamIpcMarkerCaptureOptions } from './xtream-ipc-marker-events.model';
import type { XtreamIpcRequestIdentity } from './xtream-ipc-marker-events.model';
export interface SafeXtreamPreloadMarker {
action: string | null;
boundary: 'end' | 'start';
categoryType: string | null;
contentType: string | null;
ipcCallId: number;
itemCount: number | null;
method: string;
operationId: string | null;
outcome: 'error' | 'success' | null;
playlistId: string | null;
schemaVersion: number;
sessionId: string | null;
sourceEpochMs: number;
}
export interface SafeXtreamMainPhase {
boundary: 'end' | 'start';
durationMs: number | null;
epochMs: number;
phase: string;
requestId: string;
}
export interface XtreamIpcMarkerProtocol {
applyMarkerIdentity(
identity: XtreamIpcRequestIdentity,
marker: SafeXtreamPreloadMarker
): void;
correlationKey(marker: SafeXtreamPreloadMarker): string | null;
isEpoch(value: unknown): value is number;
parseMainPhase(input: unknown): SafeXtreamMainPhase | null;
parseMarker(input: unknown): SafeXtreamPreloadMarker | null;
readCancelKey(input: unknown): string | null | undefined;
readRequestKey(input: unknown): string | null | undefined;
sameMarkerIdentity(
start: SafeXtreamPreloadMarker,
end: SafeXtreamPreloadMarker
): boolean;
unavailableIdentity(reason: string): XtreamIpcRequestIdentity;
}
export function createXtreamIpcMarkerProtocol(
options: XtreamIpcMarkerCaptureOptions
): XtreamIpcMarkerProtocol {
type JsonRecord = Record<string, unknown>;
const markerKeys = new Set([
'schemaVersion',
'sourceEpochMs',
'ipcCallId',
'method',
'boundary',
'outcome',
'playlistId',
'operationId',
'sessionId',
'action',
'categoryType',
'contentType',
'itemCount',
]);
const actions = new Set(options.actions);
const mainPhases = new Set(options.mainPhases);
const methods = new Set(options.methods);
const categoryTypes = new Set(['live', 'movies', 'series']);
const contentTypes = new Set(['live', 'movie', 'series']);
const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const rendererRequestId = /^xtream-[0-9a-z]+-[1-9][0-9]*$/;
const internalRequestId =
/^xtream-main-(?:fallback-[1-9][0-9]*|[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/;
const operationToMethod: Readonly<Record<string, string>> = {
DB_DELETE_PLAYLIST: 'dbDeletePlaylist',
DB_DELETE_XTREAM_CONTENT: 'dbDeleteXtreamContent',
DB_GET_CATEGORIES: 'dbGetCategories',
DB_GET_CONTENT: 'dbGetContent',
DB_GLOBAL_SEARCH: 'dbGlobalSearch',
DB_RESTORE_XTREAM_USER_DATA: 'dbRestoreXtreamUserData',
DB_SAVE_CATEGORIES: 'dbSaveCategories',
DB_SAVE_CONTENT: 'dbSaveContent',
DB_SEARCH_CONTENT: 'dbSearchContent',
};
const helpers = {
correlationKey(marker: SafeXtreamPreloadMarker): string | null {
const values: Array<number | string | null> = [marker.method];
switch (marker.method) {
case 'xtreamRequest':
if (marker.action === null) return null;
values.push(marker.ipcCallId);
break;
case 'xtreamCancelSession':
values.push(marker.sessionId);
break;
case 'dbGetCategories':
case 'dbSaveCategories':
values.push(marker.playlistId, marker.categoryType);
break;
case 'dbGetContent':
values.push(marker.playlistId, marker.contentType);
break;
case 'dbSaveContent':
values.push(
marker.playlistId,
marker.contentType,
marker.operationId
);
break;
case 'dbDeletePlaylist':
values.push(
marker.playlistId,
marker.operationId ?? '<no-operation-id>'
);
break;
case 'dbDeleteXtreamContent':
case 'dbRestoreXtreamUserData':
values.push(marker.playlistId, marker.operationId);
break;
case 'dbSearchContent':
values.push(marker.playlistId);
break;
case 'dbGlobalSearch':
break;
case 'dbCancelOperation':
values.push(marker.operationId);
break;
default:
return null;
}
return values.some((value) => value === null)
? null
: JSON.stringify(values);
},
isEpoch(value: unknown): value is number {
return (
typeof value === 'number' && Number.isFinite(value) && value > 0
);
},
isIdentifier(value: unknown): value is string {
return typeof value === 'string' && identifierPattern.test(value);
},
isNullableIdentifier(value: unknown): value is string | null {
return value === null || helpers.isIdentifier(value);
},
isNullableMember(
value: unknown,
allowed: ReadonlySet<string>
): value is string | null {
return (
value === null ||
(typeof value === 'string' && allowed.has(value))
);
},
isRecord(value: unknown): value is JsonRecord {
return (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value)
);
},
};
return {
applyMarkerIdentity(identity, marker): void {
identity.ipcCallId = marker.ipcCallId;
identity.operationId = marker.operationId;
identity.operationIdUnavailableReason = null;
identity.sourceEpochMs = marker.sourceEpochMs;
},
correlationKey(marker): string | null {
return helpers.correlationKey(marker);
},
isEpoch(value): value is number {
return helpers.isEpoch(value);
},
parseMainPhase(input): SafeXtreamMainPhase | null {
try {
if (!helpers.isRecord(input)) {
return null;
}
const boundary = input['boundary'];
const durationMs = input['durationMs'];
const epochMs = input['epochMs'];
const phase = input['phase'];
const requestId = input['requestId'];
if (
(boundary !== 'start' && boundary !== 'end') ||
!helpers.isEpoch(epochMs) ||
typeof phase !== 'string' ||
!mainPhases.has(phase) ||
typeof requestId !== 'string' ||
requestId.length > 64 ||
(!rendererRequestId.test(requestId) &&
!internalRequestId.test(requestId)) ||
(boundary === 'start'
? durationMs !== null
: typeof durationMs !== 'number' ||
!Number.isFinite(durationMs) ||
durationMs < 0)
) {
return null;
}
return {
boundary,
durationMs: durationMs as number | null,
epochMs,
phase,
requestId,
};
} catch {
return null;
}
},
parseMarker(input): SafeXtreamPreloadMarker | null {
try {
if (
!helpers.isRecord(input) ||
Reflect.ownKeys(input).length !== markerKeys.size ||
Reflect.ownKeys(input).some(
(key) => typeof key !== 'string' || !markerKeys.has(key)
)
) {
return null;
}
const boundary = input['boundary'];
const outcome = input['outcome'];
const itemCount = input['itemCount'];
if (
input['schemaVersion'] !== options.schemaVersion ||
!helpers.isEpoch(input['sourceEpochMs']) ||
!Number.isSafeInteger(input['ipcCallId']) ||
Number(input['ipcCallId']) < 1 ||
typeof input['method'] !== 'string' ||
!methods.has(input['method']) ||
(boundary !== 'start' && boundary !== 'end') ||
(boundary === 'start'
? outcome !== null
: outcome !== 'success' && outcome !== 'error') ||
!helpers.isNullableIdentifier(input['playlistId']) ||
!helpers.isNullableIdentifier(input['operationId']) ||
!helpers.isNullableIdentifier(input['sessionId']) ||
!helpers.isNullableMember(input['action'], actions) ||
!helpers.isNullableMember(
input['categoryType'],
categoryTypes
) ||
!helpers.isNullableMember(
input['contentType'],
contentTypes
) ||
(itemCount !== null &&
(!Number.isSafeInteger(itemCount) ||
Number(itemCount) < 0 ||
Number(itemCount) > 1_000_000))
) {
return null;
}
return input as unknown as SafeXtreamPreloadMarker;
} catch {
return null;
}
},
readCancelKey(input): string | null | undefined {
if (!helpers.isRecord(input) || input['type'] !== 'cancel') {
return undefined;
}
return helpers.correlationKey({
method: 'dbCancelOperation',
operationId:
typeof input['operationId'] === 'string'
? input['operationId']
: null,
} as SafeXtreamPreloadMarker);
},
readRequestKey(input): string | null | undefined {
if (
!helpers.isRecord(input) ||
typeof input['operation'] !== 'string'
) {
return undefined;
}
const method = operationToMethod[input['operation']];
if (!method) {
return undefined;
}
const payload = helpers.isRecord(input['payload'])
? input['payload']
: {};
return helpers.correlationKey({
method,
playlistId:
typeof payload['playlistId'] === 'string'
? payload['playlistId']
: null,
operationId:
typeof payload['operationId'] === 'string'
? payload['operationId']
: null,
categoryType:
typeof payload['type'] === 'string' &&
categoryTypes.has(payload['type'])
? payload['type']
: null,
contentType:
typeof payload['type'] === 'string' &&
contentTypes.has(payload['type'])
? payload['type']
: null,
} as SafeXtreamPreloadMarker);
},
sameMarkerIdentity(start, end): boolean {
return (
start.ipcCallId === end.ipcCallId &&
start.method === end.method &&
start.playlistId === end.playlistId &&
start.operationId === end.operationId &&
start.sessionId === end.sessionId &&
start.action === end.action &&
start.categoryType === end.categoryType &&
start.contentType === end.contentType &&
start.itemCount === end.itemCount
);
},
unavailableIdentity(reason): XtreamIpcRequestIdentity {
return {
ipcCallId: null,
operationId: null,
operationIdUnavailableReason: reason,
sourceEpochMs: null,
};
},
};
}