mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -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,330 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
import {
|
||||
XTREAM_SCENARIO_ID,
|
||||
type XtreamScenarioId,
|
||||
} from './xtream-benchmark-contract';
|
||||
import { createXtreamAssemblerFixture } from './xtream-iteration-assembler.fixture';
|
||||
import { assembleXtreamRawIteration } from './xtream-iteration-assembler';
|
||||
import { assertXtreamIterationResult } from './xtream-iteration-validity';
|
||||
|
||||
describe('Xtream raw iteration assembler', () => {
|
||||
for (const scenarioId of Object.values(XTREAM_SCENARIO_ID)) {
|
||||
it(`assembles measured ${scenarioId} evidence without inventing fields`, () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
scenarioId as XtreamScenarioId
|
||||
);
|
||||
|
||||
const result = assembleXtreamRawIteration(input);
|
||||
|
||||
assert.doesNotThrow(() => assertXtreamIterationResult(result));
|
||||
assert.equal(
|
||||
result.processIdentity.captureGeneration,
|
||||
input.mainCapture.captureGeneration
|
||||
);
|
||||
assert.equal(
|
||||
result.databaseWorker.requests.evidence.length,
|
||||
input.mainCapture.requests.length
|
||||
);
|
||||
assert.equal(result.validity.databaseWorkerCount, 1);
|
||||
assert.equal(result.validity.playlistWorkerCount, 0);
|
||||
assert.equal(result.main.cpuMetricScope, 'electron-main-thread');
|
||||
assert.equal(
|
||||
result.databaseWorker.cpuMetricScope,
|
||||
'sum-valid-request-thread-cpu'
|
||||
);
|
||||
assert.equal(
|
||||
result.databaseWorker.cpuUserMicros,
|
||||
result.databaseWorker.requests.evidence.reduce(
|
||||
(total, request) =>
|
||||
total +
|
||||
(request.invalidReason === null
|
||||
? Number(request.threadCpuUserMicros)
|
||||
: 0),
|
||||
0
|
||||
)
|
||||
);
|
||||
assert.equal(
|
||||
result.cancellationEvidence === null,
|
||||
scenarioId !== XTREAM_SCENARIO_ID.CANCEL_IMPORT
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
it('fails closed on capture invalidation instead of manufacturing a valid result', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
mainCapture: {
|
||||
...input.mainCapture,
|
||||
invalidReasons: {
|
||||
...input.mainCapture.invalidReasons,
|
||||
capture: ['database-worker-activity-after-cutoff'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
/xtream-iteration-assembly-invalid/
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts a coarse trigger clock from the same millisecond as renderer start', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE
|
||||
);
|
||||
const operationStartEpochMs =
|
||||
input.rendererCapture.probe.operationStartEpochMs + 0.05;
|
||||
|
||||
assert.doesNotThrow(() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
rendererCapture: {
|
||||
...input.rendererCapture,
|
||||
measurementStartEpochMs: operationStartEpochMs,
|
||||
probe: {
|
||||
...input.rendererCapture.probe,
|
||||
operationStartEpochMs,
|
||||
},
|
||||
},
|
||||
trigger: {
|
||||
...input.trigger,
|
||||
triggerEpochMs: Math.floor(operationStartEpochMs),
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects a coarse trigger clock from the millisecond before renderer start', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE
|
||||
);
|
||||
const operationStartEpochMs =
|
||||
input.rendererCapture.probe.operationStartEpochMs + 0.05;
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
rendererCapture: {
|
||||
...input.rendererCapture,
|
||||
probe: {
|
||||
...input.rendererCapture.probe,
|
||||
operationStartEpochMs,
|
||||
},
|
||||
},
|
||||
trigger: {
|
||||
...input.trigger,
|
||||
triggerEpochMs: Math.floor(operationStartEpochMs) - 1,
|
||||
},
|
||||
}),
|
||||
/xtream-iteration-assembly-invalid/
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts a late delete driver observation after renderer and main capture cutoffs', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.DELETE_LARGE
|
||||
);
|
||||
|
||||
assert.doesNotThrow(() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
terminal: {
|
||||
...input.terminal,
|
||||
terminalEpochMs:
|
||||
input.mainCapture.captureCutoffEpochMs + 100,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts renderer cancellation delivery after the authoritative database response', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.CANCEL_IMPORT
|
||||
);
|
||||
const authoritativeTerminalEpochMs = input.mainCapture.requests.find(
|
||||
({ operation, success }) =>
|
||||
operation === 'DB_SAVE_CONTENT' && !success
|
||||
)?.responseEpochMs;
|
||||
assert.equal(typeof authoritativeTerminalEpochMs, 'number');
|
||||
const dbCancellationTerminalEpochMs =
|
||||
Number(authoritativeTerminalEpochMs) + 0.25;
|
||||
assert.ok(
|
||||
dbCancellationTerminalEpochMs <
|
||||
Number(input.rendererCapture.probe.uiPaintedEpochMs)
|
||||
);
|
||||
|
||||
assert.doesNotThrow(() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
rendererCapture: {
|
||||
...input.rendererCapture,
|
||||
probe: {
|
||||
...input.rendererCapture.probe,
|
||||
dbCancellationTerminalEpochMs,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts main observing a network cancel response after the next renderer IPC starts', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.CANCEL_IMPORT
|
||||
);
|
||||
const databaseCancelStart = input.mainCapture.ipcTimeline.find(
|
||||
({ boundary, method }) =>
|
||||
boundary === 'start' && method === 'dbCancelOperation'
|
||||
);
|
||||
assert.ok(databaseCancelStart);
|
||||
assert.equal(typeof databaseCancelStart.sourceEpochMs, 'number');
|
||||
const rendererHandoffEpochMs = Number(
|
||||
databaseCancelStart.sourceEpochMs
|
||||
);
|
||||
const delayedMainObservationEpochMs = rendererHandoffEpochMs + 0.1;
|
||||
assert.ok(delayedMainObservationEpochMs < databaseCancelStart.epochMs);
|
||||
|
||||
assert.doesNotThrow(() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
mainCapture: {
|
||||
...input.mainCapture,
|
||||
ipcTimeline: input.mainCapture.ipcTimeline.map((record) =>
|
||||
record.boundary === 'end' &&
|
||||
record.method === 'xtreamCancelSession'
|
||||
? {
|
||||
...record,
|
||||
epochMs: delayedMainObservationEpochMs,
|
||||
sourceEpochMs: rendererHandoffEpochMs,
|
||||
}
|
||||
: record
|
||||
),
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts the non-authoritative cancel IPC returning after the database acknowledgement', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.CANCEL_IMPORT
|
||||
);
|
||||
const authoritativeTerminalEpochMs = input.mainCapture.requests.find(
|
||||
({ operation, success }) =>
|
||||
operation === 'DB_SAVE_CONTENT' && !success
|
||||
)?.responseEpochMs;
|
||||
assert.equal(typeof authoritativeTerminalEpochMs, 'number');
|
||||
const responseSourceEpochMs =
|
||||
Number(authoritativeTerminalEpochMs) + 0.1;
|
||||
const responseObservedEpochMs = responseSourceEpochMs + 0.1;
|
||||
assert.ok(
|
||||
responseObservedEpochMs <
|
||||
Number(input.rendererCapture.probe.uiPaintedEpochMs)
|
||||
);
|
||||
|
||||
assert.doesNotThrow(() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
mainCapture: {
|
||||
...input.mainCapture,
|
||||
ipcTimeline: input.mainCapture.ipcTimeline
|
||||
.map((record) =>
|
||||
record.boundary === 'end' &&
|
||||
record.method === 'dbCancelOperation'
|
||||
? {
|
||||
...record,
|
||||
epochMs: responseObservedEpochMs,
|
||||
sourceEpochMs: responseSourceEpochMs,
|
||||
}
|
||||
: record
|
||||
)
|
||||
.sort((left, right) => left.epochMs - right.epochMs),
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('binds store-to-paint duration to the renderer terminal and paint epochs', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.CANCEL_IMPORT
|
||||
);
|
||||
|
||||
assert.doesNotThrow(() => assembleXtreamRawIteration(input));
|
||||
assert.throws(
|
||||
() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
rendererCapture: {
|
||||
...input.rendererCapture,
|
||||
storeToPaintMs:
|
||||
input.rendererCapture.storeToPaintMs + 0.1,
|
||||
},
|
||||
}),
|
||||
/xtream-iteration-assembly-invalid/
|
||||
);
|
||||
});
|
||||
|
||||
it('includes trailing delete database and IPC evidence after the visible paint', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.DELETE_LARGE
|
||||
);
|
||||
const rendererTerminalEpochMs =
|
||||
input.rendererCapture.probe.uiPaintedEpochMs;
|
||||
|
||||
const result = assembleXtreamRawIteration({
|
||||
...input,
|
||||
rendererCapture: {
|
||||
...input.rendererCapture,
|
||||
probe: {
|
||||
...input.rendererCapture.probe,
|
||||
terminalEpochMs: rendererTerminalEpochMs,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.ok(
|
||||
result.phaseCapture.terminalEpochMs > rendererTerminalEpochMs
|
||||
);
|
||||
assert.equal(
|
||||
result.phases.totalMs,
|
||||
result.phaseCapture.terminalEpochMs -
|
||||
result.phaseCapture.operationStartEpochMs
|
||||
);
|
||||
});
|
||||
|
||||
it('binds diagnostic durations and worker ordinal only from validated artifact evidence', () => {
|
||||
const input = createXtreamAssemblerFixture(
|
||||
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
assembleXtreamRawIteration({
|
||||
...input,
|
||||
definition: {
|
||||
...input.definition,
|
||||
eligibleForComparison: false,
|
||||
kind: 'diagnostic',
|
||||
runId: 'diagnostic',
|
||||
},
|
||||
diagnosticArtifacts: {
|
||||
artifacts: [],
|
||||
directorySha256: '0'.repeat(64),
|
||||
profiles: {
|
||||
databaseWorker: {
|
||||
durationMicros: 1,
|
||||
ordinal: 1,
|
||||
},
|
||||
main: { durationMicros: 1 },
|
||||
renderer: { durationMicros: 1 },
|
||||
},
|
||||
} as never,
|
||||
}),
|
||||
/xtream-iteration-assembly-invalid/
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user