mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
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
This commit is contained in:
1 parent
5e725a06f3
commit
a2fafcfc08
256 files changed
+43760
-883
No files matched your search
@@ -0,0 +1,259 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
|
||||
import type { XtreamIterationDefinition } from './xtream-benchmark-contract';
|
||||
import {
|
||||
XTREAM_ITERATION_KIND,
|
||||
XTREAM_SCENARIO_ID,
|
||||
} from './xtream-benchmark-contract';
|
||||
import type { XtreamCatalogVerification } from './xtream-catalog-database-verification';
|
||||
import {
|
||||
assertXtreamDiagnosticArtifactEvidence,
|
||||
type XtreamDiagnosticArtifactEvidence,
|
||||
} from './xtream-diagnostic-artifacts';
|
||||
import { freezeDeep } from './xtream-exact-schema';
|
||||
import {
|
||||
assertXtreamMainEvidenceGates,
|
||||
createXtreamDatabaseWorkerMetrics,
|
||||
createXtreamMainMetrics,
|
||||
createXtreamRendererMetrics,
|
||||
} from './xtream-iteration-assembly-metrics';
|
||||
import { deriveXtreamPhaseCapture } from './xtream-iteration-assembly-phases';
|
||||
import { assembleXtreamScenarioEvidence } from './xtream-iteration-assembly-scenarios';
|
||||
import { assertXtreamIterationEvidenceBinding } from './xtream-iteration-evidence-binding';
|
||||
import { assertXtreamIterationResult } from './xtream-iteration-validity';
|
||||
import type { XtreamMainCaptureResult } from './m3u-refresh-main-capture';
|
||||
import { attributeXtreamPhases } from './xtream-phase-attribution';
|
||||
import type { XtreamRendererCaptureMetrics } from './xtream-renderer-capture';
|
||||
import { validXtreamRendererConsoleErrorEvidence } from './xtream-renderer-console-errors';
|
||||
import { assertCompleteXtreamRendererProbe } from './xtream-renderer-probe';
|
||||
import {
|
||||
XTREAM_SCENARIO_DATABASE_REQUEST_COUNT,
|
||||
type XtreamScenarioTerminalEvidence,
|
||||
type XtreamScenarioTriggerEvidence,
|
||||
} from './xtream-scenario-driver';
|
||||
import {
|
||||
XTREAM_NOT_APPLICABLE,
|
||||
type XtreamCatalogVerification as XtreamSummaryCatalogVerification,
|
||||
type XtreamIterationProcessIdentity,
|
||||
type XtreamRawIterationResult,
|
||||
} from './xtream-summary-contract';
|
||||
|
||||
export interface XtreamIterationProcessEvidence {
|
||||
readonly electronPid: number;
|
||||
readonly freshProcessVerified: true;
|
||||
readonly launchId: string;
|
||||
readonly profileDirectorySha256: string;
|
||||
}
|
||||
|
||||
export interface XtreamIterationAssemblyInput {
|
||||
readonly catalogVerification: XtreamCatalogVerification;
|
||||
readonly definition: XtreamIterationDefinition;
|
||||
readonly diagnosticArtifacts: XtreamDiagnosticArtifactEvidence | null;
|
||||
readonly mainCapture: XtreamMainCaptureResult;
|
||||
readonly process: XtreamIterationProcessEvidence;
|
||||
readonly rendererCapture: XtreamRendererCaptureMetrics;
|
||||
readonly terminal: XtreamScenarioTerminalEvidence;
|
||||
readonly trigger: XtreamScenarioTriggerEvidence;
|
||||
}
|
||||
|
||||
export function assembleXtreamRawIteration(
|
||||
input: XtreamIterationAssemblyInput
|
||||
): XtreamRawIterationResult {
|
||||
try {
|
||||
return assembleStrict(input);
|
||||
} catch {
|
||||
throw new Error('xtream-iteration-assembly-invalid');
|
||||
}
|
||||
}
|
||||
|
||||
function assembleStrict(
|
||||
input: XtreamIterationAssemblyInput
|
||||
): XtreamRawIterationResult {
|
||||
assertInputBoundary(input);
|
||||
const diagnostic = validateDiagnosticEvidence(input);
|
||||
assertXtreamMainEvidenceGates(input.mainCapture);
|
||||
assertCompleteXtreamRendererProbe(input.rendererCapture.probe);
|
||||
const phaseCapture = deriveXtreamPhaseCapture(
|
||||
input.definition.scenarioId,
|
||||
input.mainCapture,
|
||||
input.rendererCapture
|
||||
);
|
||||
const phases = attributeXtreamPhases(phaseCapture);
|
||||
const scenario = assembleXtreamScenarioEvidence({
|
||||
main: input.mainCapture,
|
||||
phaseCapture,
|
||||
renderer: input.rendererCapture,
|
||||
scenarioId: input.definition.scenarioId,
|
||||
terminal: input.terminal,
|
||||
trigger: input.trigger,
|
||||
});
|
||||
const context = {
|
||||
diagnostic,
|
||||
kind: input.definition.kind,
|
||||
};
|
||||
const raw: XtreamRawIterationResult = {
|
||||
...input.definition,
|
||||
cancellation: scenario.cancellation,
|
||||
cancellationEvidence: scenario.cancellationEvidence,
|
||||
catalogVerification: copyCatalogVerification(input.catalogVerification),
|
||||
databaseWorker: createXtreamDatabaseWorkerMetrics(
|
||||
input.mainCapture,
|
||||
context
|
||||
),
|
||||
main: createXtreamMainMetrics(input.mainCapture, context),
|
||||
phaseCapture,
|
||||
phases,
|
||||
playlistRefreshWorker: {
|
||||
instantiated: false,
|
||||
reason: XTREAM_NOT_APPLICABLE.PLAYLIST_REFRESH_WORKER,
|
||||
},
|
||||
processIdentity: createProcessIdentity(input),
|
||||
renderer: createXtreamRendererMetrics(
|
||||
input.mainCapture,
|
||||
input.rendererCapture,
|
||||
context,
|
||||
scenario.backgroundMetrics
|
||||
),
|
||||
validity: {
|
||||
actionContractValid: true,
|
||||
catalogContractValid: true,
|
||||
databaseWorkerCount: input.mainCapture.databaseWorkerCount,
|
||||
dbPhaseContractValid: true,
|
||||
diagnosticArtifactsValid:
|
||||
input.definition.kind === XTREAM_ITERATION_KIND.DIAGNOSTIC
|
||||
? true
|
||||
: null,
|
||||
lateDatabaseRequestCount:
|
||||
input.mainCapture.lateDatabaseRequestCount,
|
||||
playlistWorkerCount: input.mainCapture.playlistRefreshWorkerCount,
|
||||
rendererTargetCount: 1,
|
||||
},
|
||||
};
|
||||
assertXtreamIterationEvidenceBinding(raw);
|
||||
assertXtreamIterationResult(raw);
|
||||
return freezeDeep(raw);
|
||||
}
|
||||
|
||||
function assertInputBoundary(input: XtreamIterationAssemblyInput): void {
|
||||
const scenarioId = input.definition.scenarioId;
|
||||
if (
|
||||
!Object.values(XTREAM_SCENARIO_ID).includes(scenarioId) ||
|
||||
input.mainCapture.requests.length !==
|
||||
XTREAM_SCENARIO_DATABASE_REQUEST_COUNT[scenarioId] ||
|
||||
!validXtreamRendererConsoleErrorEvidence(
|
||||
input.rendererCapture.consoleErrorCount,
|
||||
input.rendererCapture.consoleErrorBreakdown,
|
||||
scenarioId
|
||||
) ||
|
||||
input.rendererCapture.pageErrorCount !== 0 ||
|
||||
input.rendererCapture.storeToPaintMs !==
|
||||
requireEpoch(input.rendererCapture.probe.uiPaintedEpochMs) -
|
||||
requireEpoch(input.rendererCapture.probe.storeTerminalEpochMs)
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
const catalog = input.catalogVerification;
|
||||
if (
|
||||
catalog.databaseStateValid !== true ||
|
||||
catalog.fixtureIdentityValid !== true ||
|
||||
catalog.verifiedAfterCapture !== true ||
|
||||
catalog.providerCategoryCount !== 100 ||
|
||||
catalog.providerItemCount !== 100_000
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
}
|
||||
|
||||
function copyCatalogVerification(
|
||||
catalog: XtreamCatalogVerification
|
||||
): XtreamSummaryCatalogVerification {
|
||||
return {
|
||||
databaseStateValid: catalog.databaseStateValid,
|
||||
fixtureIdentityValid: catalog.fixtureIdentityValid,
|
||||
providerCategoryCount: catalog.providerCategoryCount as 100,
|
||||
providerItemCount: catalog.providerItemCount as 100_000,
|
||||
verifiedAfterCapture: catalog.verifiedAfterCapture,
|
||||
};
|
||||
}
|
||||
|
||||
function validateDiagnosticEvidence(
|
||||
input: XtreamIterationAssemblyInput
|
||||
): XtreamDiagnosticArtifactEvidence | null {
|
||||
if (input.definition.kind !== XTREAM_ITERATION_KIND.DIAGNOSTIC) {
|
||||
if (
|
||||
input.diagnosticArtifacts !== null ||
|
||||
input.mainCapture.capture.cpuProfilePath !== null ||
|
||||
input.mainCapture.capture.heapSnapshotPath !== null ||
|
||||
input.rendererCapture.cpuProfilePath !== null ||
|
||||
input.rendererCapture.heapSnapshotPath !== null ||
|
||||
input.rendererCapture.tracePath !== null ||
|
||||
input.mainCapture.workers.some(
|
||||
({ profilePath, snapshotPath }) =>
|
||||
profilePath !== null || snapshotPath !== null
|
||||
)
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
assertXtreamDiagnosticArtifactEvidence(input.diagnosticArtifacts);
|
||||
if (
|
||||
input.process.profileDirectorySha256 !==
|
||||
input.diagnosticArtifacts.directorySha256
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
return input.diagnosticArtifacts;
|
||||
}
|
||||
|
||||
function createProcessIdentity(
|
||||
input: XtreamIterationAssemblyInput
|
||||
): XtreamIterationProcessIdentity {
|
||||
const process = input.process;
|
||||
if (
|
||||
process.freshProcessVerified !== true ||
|
||||
!Number.isSafeInteger(process.electronPid) ||
|
||||
process.electronPid <= 0 ||
|
||||
!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(process.launchId) ||
|
||||
!/^[a-f0-9]{64}$/.test(process.profileDirectorySha256)
|
||||
) {
|
||||
invalid();
|
||||
}
|
||||
const captureGeneration = input.mainCapture.captureGeneration;
|
||||
if (!Number.isSafeInteger(captureGeneration) || captureGeneration <= 0) {
|
||||
invalid();
|
||||
}
|
||||
const identity = {
|
||||
captureGeneration,
|
||||
electronPid: process.electronPid,
|
||||
launchId: process.launchId,
|
||||
profileDirectorySha256: process.profileDirectorySha256,
|
||||
rendererTargetId: input.rendererCapture.targetId,
|
||||
browserWindowId:
|
||||
input.mainCapture.capture.rendererWindow.windowIdentity
|
||||
.browserWindowId,
|
||||
webContentsId:
|
||||
input.mainCapture.capture.rendererWindow.windowIdentity
|
||||
.webContentsId,
|
||||
};
|
||||
return {
|
||||
captureGeneration,
|
||||
electronPid: process.electronPid,
|
||||
freshProcess: true,
|
||||
generationIdentitySha256: createHash('sha256')
|
||||
.update(JSON.stringify(identity))
|
||||
.digest('hex'),
|
||||
launchId: process.launchId,
|
||||
profileDirectorySha256: process.profileDirectorySha256,
|
||||
};
|
||||
}
|
||||
|
||||
function requireEpoch(value: unknown): number {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
||||
invalid();
|
||||
return value;
|
||||
}
|
||||
|
||||
function invalid(): never {
|
||||
throw new Error('invalid Xtream iteration assembly input');
|
||||
}
|
||||
Reference in new issue
Block a user