Merge master, dedupe the kept copy, and retire superseded switches

Master had moved ten commits ahead. Two conflicts, both resolved without
losing either side: the `XTREAM_PROBE_URL` handler this PR extracted into
`events/stream-probe.ts` stays extracted (master added performance capture
nearby but never touched that handler), and the mock-server scenario table
takes the union of master's `performance` row and this branch's `multisrc`
pair.

Two findings fixed alongside it.

Pinning the copy the route is already on makes discovery return that very
row, so prepending the current source listed one stream twice — a phantom
copy in the grouping and a chip that counted it.

And handing the "playing" badge back to the route row left an in-flight
switch valid, so a slow resolution could arrive afterwards and replace the
playback the user had just started with Play, Resume or Restart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5 committed 2026-07-28 08:44:49 +02:00
commit 9f0a8b4f19
385 files changed
+58306 -2264

No files matched your search

@@ -0,0 +1,8 @@
---
type: fix
area: electron-backend
---
Cancelling a large Xtream import now stops database work even while it is
running at full speed, instead of letting the import finish before processing
the cancel request.
+9
View File
@@ -0,0 +1,9 @@
---
type: fix
area: epg
---
A database error while looking up manual EPG channel mappings no longer breaks
the request that triggered it. Searching for a channel in the "Map EPG channel"
dialog, or saving and removing a mapping, now degrades gracefully instead of
failing outright.
+9
View File
@@ -0,0 +1,9 @@
---
type: fix
area: epg
---
A database error while reading or saving a manual EPG channel mapping no longer
surfaces as a failed request. Looking up, saving, deleting, and searching
mappings now fall back quietly, so a transient storage hiccup can no longer take
down the EPG panel or the "Map EPG channel" dialog.
+10
View File
@@ -0,0 +1,10 @@
---
type: fix
area: settings
issues: [1156, 102]
---
Launching IPTVnator a second time now brings the running window to the front
instead of starting a rival copy that could not save anything — a common reason
settings appeared to reset on restart. If saving or loading settings does fail,
the app now says so instead of silently showing defaults.
+2 -2
View File
@@ -44,7 +44,7 @@ jobs:
# commit checked out above (the modern default); JavaScript is
# interpreted, so no build step is needed before analysis.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -53,4 +53,4 @@ jobs:
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
+1 -1
View File
@@ -56,4 +56,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
+4 -2
View File
@@ -17,6 +17,7 @@ This file provides guidance to coding agents working in this repository.
- Every Nx project should keep `scope:*`, `domain:*`, and `type:*` tags in `project.json` so `@nx/enforce-module-boundaries` remains useful for humans and agents.
- See `docs/architecture/nx-workspace-boundaries.md` for the current Nx tag and alias policy.
- ESLint enforces `max-lines` on TypeScript files (target under 300, hard maximum 400). Files that predate the rule are baselined in `tools/eslint/max-lines-baseline.mjs`; after splitting a file, regenerate it with `node tools/eslint/generate-max-lines-baseline.mjs`. Never add new files to the baseline — the list must only shrink. A new file that genuinely cannot be split (for example a function serialized into another process) instead carries its own file-wide `/* eslint-disable max-lines -- <why> */`; the generator skips those files, so a justified exemption never lands in the baseline.
- Project `lint` targets that shell out to eslint must quote the glob, e.g. `eslint "apps/<project>/**/*.ts"`. An unquoted `**` is expanded by the POSIX shell on Linux and macOS (which has no `globstar`, so it matches only a shallow subset of files) while Windows passes the literal pattern to ESLint, which expands it recursively — the two hosts then lint different file sets. The target still reports success either way, so a broken glob hides missing coverage instead of failing. After changing such a target, compare the linted file count against `find <project> -name '*.ts' | wc -l`.
- Repository-specific skills are committed under `.codex/skills/`. Claude Code only discovers skills under `.claude/skills/`, so `release-notes` and `release-cut` are mirrored there and the two copies must be kept in sync; every other entry in `.claude/skills/` is personal and stays gitignored. If an external agent does not support skills, treat those files as concise ownership docs.
## Documentation After Changes
@@ -69,6 +70,7 @@ This file provides guidance to coding agents working in this repository.
- Do not auto-open DevTools during normal CDP automation. In development, DevTools is opt-in via `ELECTRON_OPEN_DEVTOOLS=1`.
- If DevTools is open, `agent-browser --cdp 9222 ...` may attach to the DevTools page instead of the IPTVnator window. Symptoms: `tab list` shows `about:blank`, snapshots are empty, and screenshots are black.
- If that happens, inspect targets with `curl http://127.0.0.1:9222/json/list` and connect directly to the IPTVnator page websocket from the `webSocketDebuggerUrl` field.
- The app holds a single-instance lock (`acquireSingleInstanceLock` in `apps/electron-backend/src/app/services/single-instance.ts`): a second launch against the same `userData` quits immediately and focuses the running window. To attach a second CDP-enabled instance to the same profile, set `IPTVNATOR_ALLOW_MULTIPLE_INSTANCES=1` — knowing that only one of the two processes will own the renderer's IndexedDB, so settings written by the other are lost.
### Trace / Debug Startup
@@ -85,8 +87,8 @@ IPTVNATOR_TRACE_STARTUP=1 nx serve electron-backend
- `IPTVNATOR_TRACE_WINDOW=1` traces BrowserWindow lifecycle and unresponsive events
- `IPTVNATOR_TRACE_PLAYER=1` traces external-player activity and bounded Embedded MPV runtime-probe stderr
- `IPTVNATOR_TRACE_RENDERER_CONSOLE=1` mirrors renderer console output into the Electron terminal
- `IPTVNATOR_PERF_CAPTURE=1` enables development/test-only, redacted preload IPC phase markers for refresh/DB benchmark correlation; benchmark tooling sets it explicitly, and production launches must leave it unset
- `IPTVNATOR_PERF_WORKER_PROFILING=1` enables development/test-only, request-scoped worker timestamps, thread CPU, event-loop utilization, and event-loop delay metrics in database and playlist-refresh responses, plus the database worker's idle-only one-shot post-GC heap probe; overlapping database requests are explicitly invalidated instead of misattributed, the performance benchmark sets the flag automatically, and production launches must leave it unset
- `IPTVNATOR_PERF_CAPTURE=1` enables development/test-only, redacted M3U and Xtream preload IPC request/completion markers plus count-only M3U acquire/parse/normalize, Xtream main network/JSON-transform/success-response-ready/cancel-dispatch, and renderer store phase capture; renderer wrappers emit only while the benchmark installs its Symbol hook, benchmark tooling sets the flag explicitly, and production launches must leave it unset
- `IPTVNATOR_PERF_WORKER_PROFILING=1` enables development/test-only, request-scoped worker receive/work/response-post timestamps, thread CPU, event-loop utilization/delay, count-only playlist serialization/SQLite write/read/deserialization plus Xtream category/content/cache-clear/delete/in-source-search phase events, profiling-only worker cancel-receipt acknowledgements, valid-sample-counted isolate peak memory, and the database worker's idle-only one-shot post-GC heap probe; overlapping database requests are explicitly invalidated instead of misattributed, the performance benchmark sets the flag automatically, and production launches must leave it unset
- Settings, portal request/response, and trace payloads must use
`@iptvnator/shared/logging` or the redacting portal logger before reaching
+13 -2
View File
@@ -126,6 +126,7 @@ nx run electron-backend:make
- When the task is Electron automation/debugging, use the `electron` skill
- Do not auto-open DevTools during normal CDP automation. In development, DevTools is opt-in via `ELECTRON_OPEN_DEVTOOLS=1`.
- If DevTools is open, `agent-browser --cdp 9222 ...` may attach to the DevTools page instead of the IPTVnator window (symptoms: `tab list` shows `about:blank`, empty snapshots, black screenshots). Inspect targets with `curl http://127.0.0.1:9222/json/list` and connect directly to the app page's `webSocketDebuggerUrl`.
- The app holds a single-instance lock (`acquireSingleInstanceLock` in `apps/electron-backend/src/app/services/single-instance.ts`): a second launch against the same `userData` quits immediately and focuses the running window. To attach a second CDP-enabled instance to the same profile, set `IPTVNATOR_ALLOW_MULTIPLE_INSTANCES=1` — knowing that only one of the two processes will own the renderer's IndexedDB, so settings written by the other are lost.
For startup tracing or white-screen debugging:
@@ -141,8 +142,8 @@ Useful narrower flags:
- `IPTVNATOR_TRACE_WINDOW=1` traces BrowserWindow navigation/load lifecycle
- `IPTVNATOR_TRACE_PLAYER=1` traces external-player activity and bounded Embedded MPV runtime-probe stderr
- `IPTVNATOR_TRACE_RENDERER_CONSOLE=1` mirrors renderer console logs into the Electron terminal
- `IPTVNATOR_PERF_CAPTURE=1` enables development/test-only, redacted preload IPC phase markers for refresh/DB benchmark correlation; benchmark tooling sets it explicitly, and production launches must leave it unset
- `IPTVNATOR_PERF_WORKER_PROFILING=1` enables development/test-only, request-scoped worker timestamps, thread CPU, event-loop utilization, and event-loop delay metrics in database and playlist-refresh responses, plus the database worker's idle-only one-shot post-GC heap probe; overlapping database requests are explicitly invalidated instead of misattributed, the performance benchmark sets the flag automatically, and production launches must leave it unset
- `IPTVNATOR_PERF_CAPTURE=1` enables development/test-only, redacted M3U and Xtream preload IPC request/completion markers plus count-only M3U acquire/parse/normalize, Xtream main network/JSON-transform/success-response-ready/cancel-dispatch, and renderer store phase capture; renderer wrappers emit only while the benchmark installs its Symbol hook, benchmark tooling sets the flag explicitly, and production launches must leave it unset
- `IPTVNATOR_PERF_WORKER_PROFILING=1` enables development/test-only, request-scoped worker receive/work/response-post timestamps, thread CPU, event-loop utilization/delay, count-only playlist serialization/SQLite write/read/deserialization plus Xtream category/content/cache-clear/delete/in-source-search phase events, profiling-only worker cancel-receipt acknowledgements, valid-sample-counted isolate peak memory, and the database worker's idle-only one-shot post-GC heap probe; overlapping database requests are explicitly invalidated instead of misattributed, the performance benchmark sets the flag automatically, and production launches must leave it unset
Settings, portal request/response, and trace payloads must use
`@iptvnator/shared/logging` or the redacting portal logger before reaching
@@ -227,6 +228,15 @@ process) instead carries its own file-wide
`/* eslint-disable max-lines -- <why> */`; the generator skips those files, so
a justified exemption never lands in the baseline.
Project `lint` targets that shell out to eslint must quote the glob, e.g.
`eslint "apps/<project>/**/*.ts"`. An unquoted `**` is expanded by the POSIX
shell on Linux and macOS (which has no `globstar`, so it matches only a
shallow subset of files) while Windows passes the literal pattern to ESLint,
which expands it recursively — the two hosts then lint different file sets.
The target still reports success either way, so a broken glob hides missing
coverage instead of failing. After changing such a target, compare the linted
file count against `find <project> -name '*.ts' | wc -l`.
## Architecture
### Monorepo Structure (Nx Workspace)
@@ -581,6 +591,7 @@ This project uses modern Angular signal-based APIs and patterns. **ALWAYS** use
- Bootstraps Electron app and initializes database
- Registers event handlers for IPC communication
- Holds a single-instance lock (`app/services/single-instance.ts`), requested after the `userData` override so E2E runs with their own data dir keep independent locks. A second launch quits and focuses the running window; concurrent instances would otherwise share a Chromium profile whose IndexedDB only one of them can lock, silently breaking renderer-side settings persistence. `IPTVNATOR_ALLOW_MULTIPLE_INSTANCES=1` opts out for local debugging.
**Database**:
@@ -0,0 +1,34 @@
import { randomBytes } from 'node:crypto';
import { workspaceRoot } from '@nx/devkit';
import { defineConfig } from '@playwright/test';
const controlToken =
process.env['IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN'] ??
randomBytes(32).toString('base64url');
process.env['IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN'] = controlToken;
export default defineConfig({
fullyParallel: false,
reporter: [['list']],
retries: 0,
testDir: './src',
testMatch: 'xtream.performance.ts',
timeout: 30 * 60 * 1_000,
use: {
testIdAttribute: 'data-test-id',
},
webServer: {
command: 'pnpm nx run xtream-mock-server:serve',
cwd: workspaceRoot,
env: {
HOST: '127.0.0.1',
IPTVNATOR_XTREAM_MOCK_CONTROL: '1',
IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN: controlToken,
PORT: '3221',
},
reuseExistingServer: false,
url: 'http://127.0.0.1:3221/health',
},
workers: 1,
});
+20
View File
@@ -29,6 +29,26 @@
"command": "pnpm exec playwright test --config=playwright.performance.config.ts src/m3u-refresh-cancellation.performance.ts"
}
},
"benchmark-m3u-import": {
"dependsOn": ["electron-backend:build-performance"],
"executor": "nx:run-commands",
"cache": false,
"parallelism": false,
"options": {
"cwd": "apps/electron-backend-e2e",
"command": "pnpm exec playwright test --config=playwright.performance.config.ts src/m3u-import.performance.ts"
}
},
"benchmark-xtream": {
"dependsOn": ["electron-backend:build-performance"],
"executor": "nx:run-commands",
"cache": false,
"parallelism": false,
"options": {
"cwd": "apps/electron-backend-e2e",
"command": "pnpm exec playwright test --config=playwright.xtream-performance.config.ts src/xtream.performance.ts"
}
},
"packaged-frame-copy-smoke": {
"dependsOn": ["test-packaged-frame-copy-fixtures"],
"executor": "nx:run-commands",
@@ -7,6 +7,7 @@ import {
Page,
test as base,
} from '@playwright/test';
import { spawn } from 'child_process';
import { createServer, Server } from 'http';
import {
accessSync,
@@ -76,7 +77,9 @@ type ElectronFixtures = {
export type LaunchElectronAppOptions = {
args?: readonly string[];
environmentInheritance?: 'all' | 'runtime-only';
env?: Record<string, string | undefined>;
omitEnvKeys?: readonly string[];
};
export type PortalDebugEvent = {
@@ -119,6 +122,38 @@ export type LaunchedElectronApp = {
mainWindow: Page;
};
export type CompetingElectronInstanceResult = {
exitCode: number | null;
signal: NodeJS.Signals | null;
stderr: string;
timedOut: boolean;
};
const competingInstanceStderrLimit = 4000;
/**
* Removes a run's data directory, tolerating handles the OS has not released
* yet.
*
* Electron's single-instance lock keeps `SingletonLock`/`SingletonSocket`
* (`lockfile` on Windows) open for the whole process lifetime, and Windows
* releases those handles asynchronously as the process dies — so a removal
* issued right after `closeElectronApp` can still hit EBUSY. This is throwaway
* temp state, so a stubborn directory is a warning, never a test failure.
*/
function removeDataDir(dataDir: string): void {
try {
rmSync(dataDir, {
force: true,
maxRetries: 20,
recursive: true,
retryDelay: 250,
});
} catch (error) {
console.warn(`Could not remove E2E data dir ${dataDir}:`, error);
}
}
export const test = base.extend<ElectronFixtures>({
dataDir: async ({ browserName }, use) => {
void browserName;
@@ -126,12 +161,29 @@ export const test = base.extend<ElectronFixtures>({
await use(dataDir);
rmSync(dataDir, { force: true, recursive: true });
removeDataDir(dataDir);
},
});
export { expect };
/**
* Builds the argv every locally launched Electron process must share.
*
* Headless Linux CI has no usable sandbox or GPU, and an Electron started
* without these flags there dies on a signal instead of running — so any
* helper that spawns the app itself has to use the same list.
*/
function buildElectronLaunchArgs(extraArgs: readonly string[] = []): string[] {
const args = [...extraArgs, electronMainPath];
if (process.platform === 'linux' && process.env['CI']) {
args.unshift('--no-sandbox', '--disable-gpu');
}
return args;
}
export async function launchElectronApp(
dataDir: string,
options: LaunchElectronAppOptions = {}
@@ -143,24 +195,11 @@ export async function launchElectronApp(
}
assertPackagedRendererBuildIsElectronSafe();
const args = [...(options.args ?? []), electronMainPath];
if (process.platform === 'linux' && process.env['CI']) {
args.unshift('--no-sandbox', '--disable-gpu');
}
const args = buildElectronLaunchArgs(options.args);
const electronApp = await electron.launch({
args,
env: {
...process.env,
// Electron E2E uses local mock HTTP servers for playlists, portals, and EPG sources.
IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS:
process.env['IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS'] ?? '1',
...options.env,
ELECTRON_IS_DEV: '0',
IPTVNATOR_E2E_DATA_DIR: dataDir,
NODE_ENV: 'test',
},
env: buildElectronLaunchEnvironment(dataDir, options),
});
attachElectronProcessDiagnostics(electronApp);
@@ -176,6 +215,86 @@ export async function launchElectronApp(
};
}
export function buildElectronLaunchEnvironment(
dataDir: string,
options: LaunchElectronAppOptions = {},
inheritedEnvironment: NodeJS.ProcessEnv = process.env
): Record<string, string> {
if (
options.environmentInheritance !== undefined &&
options.environmentInheritance !== 'all' &&
options.environmentInheritance !== 'runtime-only'
) {
throw new Error('Electron environment inheritance mode is invalid');
}
const runtimeOnly = options.environmentInheritance === 'runtime-only';
const environment: Record<string, string | undefined> = {
...(runtimeOnly
? selectElectronRuntimeEnvironment(inheritedEnvironment)
: inheritedEnvironment),
// Electron E2E uses local mock HTTP servers for playlists, portals, and EPG sources.
IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS: runtimeOnly
? '1'
: (inheritedEnvironment['IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS'] ??
'1'),
...options.env,
ELECTRON_IS_DEV: '0',
IPTVNATOR_E2E_DATA_DIR: dataDir,
NODE_ENV: 'test',
};
for (const key of options.omitEnvKeys ?? []) {
delete environment[key];
}
return Object.fromEntries(
Object.entries(environment).filter(
(entry): entry is [string, string] => typeof entry[1] === 'string'
)
);
}
export const ELECTRON_RUNTIME_ENVIRONMENT_KEYS = Object.freeze([
'APPDATA',
'COMSPEC',
'DBUS_SESSION_BUS_ADDRESS',
'DISPLAY',
'HOME',
'LANG',
'LANGUAGE',
'LC_ALL',
'LC_CTYPE',
'LOCALAPPDATA',
'LOGNAME',
'PATH',
'PATHEXT',
'SYSTEMROOT',
'TEMP',
'TMP',
'TMPDIR',
'USER',
'USERPROFILE',
'WAYLAND_DISPLAY',
'WINDIR',
'XAUTHORITY',
'XDG_RUNTIME_DIR',
'XDG_SESSION_TYPE',
'__CF_USER_TEXT_ENCODING',
] as const);
function selectElectronRuntimeEnvironment(
inheritedEnvironment: NodeJS.ProcessEnv
): Record<string, string> {
const selected: Record<string, string> = {};
const allowed = new Set(
ELECTRON_RUNTIME_ENVIRONMENT_KEYS.map((key) => key.toUpperCase())
);
for (const [key, value] of Object.entries(inheritedEnvironment)) {
if (typeof value === 'string' && allowed.has(key.toUpperCase())) {
selected[key] = value;
}
}
return selected;
}
/**
* Resolve the x64 unpacked Linux executable produced by electron-builder.
* An explicit path wins so CI can point at an AppImage/Flatpak extraction
@@ -333,6 +452,65 @@ function attachElectronProcessDiagnostics(
});
}
/**
* Starts a raw second Electron process against an already-running instance's
* data directory and reports how it terminated.
*
* Deliberately not `launchElectronApp`: the expected outcome is that no window
* is ever created, because the single-instance guard hands the launch over to
* the running app. Two live instances would share a Chromium profile whose
* IndexedDB only one of them can lock, which is how settings silently stopped
* persisting (issues #102, #1156).
*/
export async function launchCompetingElectronInstance(
dataDir: string,
timeoutMs = 30000
): Promise<CompetingElectronInstanceResult> {
// In a Node context the `electron` package resolves to its binary path.
const electronBinaryPath = require('electron') as unknown as string;
const child = spawn(electronBinaryPath, buildElectronLaunchArgs(), {
env: {
...process.env,
ELECTRON_IS_DEV: '0',
IPTVNATOR_E2E_DATA_DIR: dataDir,
NODE_ENV: 'test',
},
stdio: ['ignore', 'ignore', 'pipe'],
});
// Kept for the assertion message: a competing launch that dies for an
// unrelated reason (missing sandbox, missing GPU) looks exactly like a
// refused one from the outside.
let stderr = '';
child.stderr?.on('data', (chunk: Buffer) => {
stderr = `${stderr}${chunk.toString()}`.slice(
-competingInstanceStderrLimit
);
});
return new Promise((resolvePromise) => {
const timer = setTimeout(() => {
child.kill();
resolvePromise({
exitCode: null,
signal: null,
stderr,
timedOut: true,
});
}, timeoutMs);
child.once('exit', (code, signal) => {
clearTimeout(timer);
resolvePromise({
exitCode: code,
signal,
stderr,
timedOut: false,
});
});
});
}
export async function closeElectronApp(
app: LaunchedElectronApp
): Promise<void> {
@@ -1598,9 +1776,7 @@ export async function expectWorkspaceSearchStatus(
}
/** The degraded-search hint chip must be absent (e.g. complete local search). */
export async function expectNoWorkspaceSearchStatus(
page: Page
): Promise<void> {
export async function expectNoWorkspaceSearchStatus(page: Page): Promise<void> {
await expect(
page.locator('app-workspace-shell-header .search-chip--status')
).toHaveCount(0);
@@ -0,0 +1,8 @@
import { test } from '@playwright/test';
import { runM3uImportBenchmark } from './performance/m3u-import.benchmark';
// eslint-disable-next-line playwright/expect-expect -- Assertions run inside the shared benchmark lifecycle.
test('profiles initial M3U imports at 10k, 50k, and 100k', async () => {
await runM3uImportBenchmark();
});
@@ -52,11 +52,125 @@ test('correlates the real preload marker to a DB request whose payload omits ope
});
assert.deepEqual(identity, {
ipcCallId: 2,
operationId: 'refresh-operation-1',
operationIdUnavailableReason: null,
sourceEpochMs: 100,
});
});
test('retains the initial-import upsert marker when its operationId is intentionally uncorrelated', () => {
const { api, ipcMain } = createCapture();
ipcMain.emit(
CHANNEL,
{},
marker({
correlationState: 'uncorrelated',
ipcCallId: 7,
method: 'dbUpsertAppPlaylist',
operationId: null,
sourceEpochMs: 1_077.25,
})
);
const identity = api.matchDatabaseRequest({
operation: 'DB_UPSERT_APP_PLAYLIST',
payload: { _id: 'playlist-1' },
requestId: 'initial-import-upsert',
type: 'request',
});
assert.deepEqual(identity, {
ipcCallId: 7,
operationId: null,
operationIdUnavailableReason: 'preload-performance-marker-uncorrelated',
sourceEpochMs: 1_077.25,
});
});
test('captures exact successful preload completion markers for DB return-clone attribution', () => {
const { api, ipcMain } = createCapture();
ipcMain.emit(
CHANNEL,
{},
marker({
correlationState: 'uncorrelated',
ipcCallId: 7,
method: 'dbUpsertAppPlaylist',
operationId: null,
phase: 'success',
sourceEpochMs: 140.25,
})
);
ipcMain.emit(
CHANNEL,
{},
marker({
correlationState: 'uncorrelated',
ipcCallId: 8,
operationId: null,
phase: 'success',
sourceEpochMs: 175.5,
})
);
assert.equal(api.successMarkerCount(), 2);
assert.deepEqual(api.takeSuccessMarkers(), [
{
ipcCallId: 7,
operation: 'DB_UPSERT_APP_PLAYLIST',
operationId: null,
playlistId: 'playlist-1',
sourceEpochMs: 140.25,
},
{
ipcCallId: 8,
operation: 'DB_GET_APP_PLAYLIST',
operationId: null,
playlistId: 'playlist-1',
sourceEpochMs: 175.5,
},
]);
assert.equal(api.successMarkerCount(), 0);
assert.deepEqual(api.takeSuccessMarkers(), []);
});
test('fails closed for malformed or invalid successful preload markers and resets completions', () => {
const { api, ipcMain } = createCapture();
for (const invalid of [
{ invalidReason: 'out-of-order' },
{ correlationState: 'invalid' },
{ ipcCallId: 0 },
{ playlistId: '' },
{ sourceEpochMs: Number.NaN },
{ method: 'refreshPlaylist' },
{ correlationState: 'correlated', operationId: null },
]) {
ipcMain.emit(
CHANNEL,
{},
marker({
phase: 'success',
...invalid,
})
);
}
assert.equal(api.successMarkerCount(), 0);
assert.deepEqual(api.takeSuccessMarkers(), []);
ipcMain.emit(
CHANNEL,
{},
marker({
phase: 'success',
})
);
api.stop();
api.start();
assert.equal(api.successMarkerCount(), 0);
assert.deepEqual(api.takeSuccessMarkers(), []);
});
test('resolves a response-retained request identity when the marker arrives later and resets between captures', () => {
const { api, ipcMain } = createCapture();
const identity = api.matchDatabaseRequest({
@@ -73,8 +187,10 @@ test('resolves a response-retained request identity when the marker arrives late
ipcMain.emit(CHANNEL, {}, marker());
assert.deepEqual(responseOutcome.identity, {
ipcCallId: 2,
operationId: 'refresh-operation-1',
operationIdUnavailableReason: null,
sourceEpochMs: 100,
});
api.stop();
@@ -87,8 +203,10 @@ test('resolves a response-retained request identity when the marker arrives late
type: 'request',
});
assert.deepEqual(afterReset, {
ipcCallId: null,
operationId: null,
operationIdUnavailableReason: 'preload-performance-marker-missing',
sourceEpochMs: null,
});
});
@@ -101,8 +219,10 @@ test('fails closed for missing and ambiguous preload markers', () => {
type: 'request',
});
assert.deepEqual(missing, {
ipcCallId: null,
operationId: null,
operationIdUnavailableReason: 'preload-performance-marker-missing',
sourceEpochMs: null,
});
const ambiguousCapture = createCapture();
@@ -122,8 +242,10 @@ test('fails closed for missing and ambiguous preload markers', () => {
type: 'request',
});
assert.deepEqual(ambiguous, {
ipcCallId: null,
operationId: null,
operationIdUnavailableReason: 'preload-performance-marker-ambiguous',
sourceEpochMs: null,
});
});
@@ -191,19 +313,25 @@ test('quarantines a key after response-before-marker ambiguity', () => {
});
const ambiguousIdentity = {
ipcCallId: null,
operationId: null,
operationIdUnavailableReason: 'preload-performance-marker-ambiguous',
sourceEpochMs: null,
};
assert.deepEqual(requestA, ambiguousIdentity);
assert.deepEqual(requestB, ambiguousIdentity);
assert.deepEqual(requestC, ambiguousIdentity);
assert.deepEqual(otherPlaylist, {
ipcCallId: 4,
operationId: 'other-playlist-operation',
operationIdUnavailableReason: null,
sourceEpochMs: 100,
});
assert.deepEqual(otherOperation, {
ipcCallId: 5,
operationId: 'other-db-operation',
operationIdUnavailableReason: null,
sourceEpochMs: 100,
});
api.stop();
@@ -223,7 +351,9 @@ test('quarantines a key after response-before-marker ambiguity', () => {
type: 'request',
});
assert.deepEqual(freshCaptureIdentity, {
ipcCallId: 6,
operationId: 'fresh-capture-operation',
operationIdUnavailableReason: null,
sourceEpochMs: 100,
});
});
@@ -4,14 +4,26 @@ export const DATABASE_REQUEST_IDENTITY_CAPTURE_STATE_KEY =
'__iptvnatorM3uRefreshDatabaseRequestIdentityCapture';
export interface DatabaseRequestIdentity {
ipcCallId: number | null;
operationId: string | null;
operationIdUnavailableReason: string | null;
sourceEpochMs: number | null;
}
export interface PreloadDatabaseSuccessMarker {
ipcCallId: number;
operation: 'DB_GET_APP_PLAYLIST' | 'DB_UPSERT_APP_PLAYLIST';
operationId: string | null;
playlistId: string;
sourceEpochMs: number;
}
export interface DatabaseRequestIdentityCaptureApi {
matchDatabaseRequest(message: unknown): DatabaseRequestIdentity;
start(): void;
stop(): void;
successMarkerCount(): number;
takeSuccessMarkers(): readonly PreloadDatabaseSuccessMarker[];
}
interface DatabaseRequestIdentityElectron {
@@ -31,8 +43,10 @@ export function installDatabaseRequestIdentityCaptureInMain(
interface PendingMarker {
readonly ipcCallId: number;
readonly operation: string;
readonly operationId: string;
readonly operationId: string | null;
readonly operationIdUnavailableReason: string | null;
readonly playlistId: string;
readonly sourceEpochMs: number;
}
interface PendingRequest {
readonly identity: DatabaseRequestIdentity;
@@ -58,6 +72,7 @@ export function installDatabaseRequestIdentityCaptureInMain(
let pendingMarkers: PendingMarker[] = [];
let pendingRequests: PendingRequest[] = [];
let quarantinedKeys: QuarantinedKey[] = [];
let successMarkers: PreloadDatabaseSuccessMarker[] = [];
const readRecord = (value: unknown): JsonRecord | null =>
typeof value === 'object' && value !== null
@@ -88,11 +103,37 @@ export function installDatabaseRequestIdentityCaptureInMain(
candidate.playlistId === playlistId;
const markAmbiguous = (requests: PendingRequest[]): void => {
for (const request of requests) {
request.identity.ipcCallId = null;
request.identity.operationId = null;
request.identity.operationIdUnavailableReason =
'preload-performance-marker-ambiguous';
request.identity.sourceEpochMs = null;
}
};
const unavailableIdentity = (reason: string): DatabaseRequestIdentity => ({
ipcCallId: null,
operationId: null,
operationIdUnavailableReason: reason,
sourceEpochMs: null,
});
const identityFromMarker = (
marker: PendingMarker
): DatabaseRequestIdentity => ({
ipcCallId: marker.ipcCallId,
operationId: marker.operationId,
operationIdUnavailableReason: marker.operationIdUnavailableReason,
sourceEpochMs: marker.sourceEpochMs,
});
const applyMarker = (
identity: DatabaseRequestIdentity,
marker: PendingMarker
): void => {
identity.ipcCallId = marker.ipcCallId;
identity.operationId = marker.operationId;
identity.operationIdUnavailableReason =
marker.operationIdUnavailableReason;
identity.sourceEpochMs = marker.sourceEpochMs;
};
const isQuarantined = (operation: string, playlistId: string): boolean =>
quarantinedKeys.some((key) => matches(key, operation, playlistId));
const quarantine = (operation: string, playlistId: string): void => {
@@ -116,32 +157,86 @@ export function installDatabaseRequestIdentityCaptureInMain(
return;
}
const marker = readRecord(input);
const correlationState = marker?.['correlationState'];
const operation =
typeof marker?.['method'] === 'string'
? markerMethodToOperation[marker['method']]
: undefined;
const operationId = marker?.['operationId'];
const playlistId = marker?.['playlistId'];
const ipcCallId = marker?.['ipcCallId'];
const sourceEpochMs = marker?.['sourceEpochMs'];
const validMarkerIdentity =
operation !== undefined &&
typeof playlistId === 'string' &&
playlistId.length > 0 &&
Number.isSafeInteger(ipcCallId) &&
Number(ipcCallId) >= 1 &&
typeof sourceEpochMs === 'number' &&
Number.isFinite(sourceEpochMs) &&
sourceEpochMs >= 0;
if (
marker?.['phase'] === 'success' &&
(correlationState === 'correlated' ||
correlationState === 'uncorrelated') &&
marker['invalidReason'] === null &&
validMarkerIdentity &&
(correlationState !== 'correlated' ||
(typeof operationId === 'string' &&
operationId.length > 0))
) {
successMarkers.push({
ipcCallId: Number(ipcCallId),
operation: operation as PreloadDatabaseSuccessMarker['operation'],
operationId:
correlationState === 'correlated'
? String(operationId)
: null,
playlistId,
sourceEpochMs,
});
return;
}
if (
!marker ||
marker['phase'] !== 'start' ||
marker['correlationState'] !== 'correlated' ||
(correlationState !== 'correlated' &&
correlationState !== 'uncorrelated') ||
marker['invalidReason'] !== null
) {
return;
}
const operation =
typeof marker['method'] === 'string'
? markerMethodToOperation[marker['method']]
: undefined;
const operationId = marker['operationId'];
const playlistId = marker['playlistId'];
const ipcCallId = marker['ipcCallId'];
const correlatedOperationId =
correlationState === 'correlated' &&
typeof operationId === 'string' &&
operationId.length > 0
? operationId
: null;
if (
operation === undefined ||
typeof operationId !== 'string' ||
operationId.length === 0 ||
(correlationState === 'correlated' &&
correlatedOperationId === null) ||
typeof playlistId !== 'string' ||
playlistId.length === 0 ||
!Number.isSafeInteger(ipcCallId) ||
Number(ipcCallId) < 1
Number(ipcCallId) < 1 ||
typeof sourceEpochMs !== 'number' ||
!Number.isFinite(sourceEpochMs) ||
sourceEpochMs < 0
) {
return;
}
const pendingMarker: PendingMarker = {
ipcCallId: Number(ipcCallId),
operation,
operationId: correlatedOperationId,
operationIdUnavailableReason:
correlationState === 'correlated'
? null
: 'preload-performance-marker-uncorrelated',
playlistId,
sourceEpochMs,
};
if (isQuarantined(operation, playlistId)) {
return;
@@ -154,8 +249,7 @@ export function installDatabaseRequestIdentityCaptureInMain(
if (!request) {
return;
}
request.identity.operationId = operationId;
request.identity.operationIdUnavailableReason = null;
applyMarker(request.identity, pendingMarker);
pendingRequests = pendingRequests.filter(
(candidate) => candidate !== request
);
@@ -166,12 +260,7 @@ export function installDatabaseRequestIdentityCaptureInMain(
return;
}
pendingMarkers.push({
ipcCallId: Number(ipcCallId),
operation,
operationId,
playlistId,
});
pendingMarkers.push(pendingMarker);
pendingMarkers.sort((left, right) => left.ipcCallId - right.ipcCallId);
});
@@ -189,19 +278,15 @@ export function installDatabaseRequestIdentityCaptureInMain(
operation !== 'DB_UPSERT_APP_PLAYLIST') ||
playlistId === null
) {
return {
operationId: null,
operationIdUnavailableReason:
'preload-performance-marker-not-applicable',
};
return unavailableIdentity(
'preload-performance-marker-not-applicable'
);
}
if (isQuarantined(operation, playlistId)) {
return {
operationId: null,
operationIdUnavailableReason:
'preload-performance-marker-ambiguous',
};
return unavailableIdentity(
'preload-performance-marker-ambiguous'
);
}
const markerCandidates = pendingMarkers.filter((marker) =>
matches(marker, operation, playlistId)
@@ -209,34 +294,25 @@ export function installDatabaseRequestIdentityCaptureInMain(
if (markerCandidates.length === 1) {
const marker = markerCandidates[0];
if (!marker) {
return {
operationId: null,
operationIdUnavailableReason:
'preload-performance-marker-missing',
};
return unavailableIdentity(
'preload-performance-marker-missing'
);
}
pendingMarkers = pendingMarkers.filter(
(candidate) => candidate !== marker
);
return {
operationId: marker.operationId,
operationIdUnavailableReason: null,
};
return identityFromMarker(marker);
}
if (markerCandidates.length > 1) {
quarantine(operation, playlistId);
return {
operationId: null,
operationIdUnavailableReason:
'preload-performance-marker-ambiguous',
};
return unavailableIdentity(
'preload-performance-marker-ambiguous'
);
}
const identity: DatabaseRequestIdentity = {
operationId: null,
operationIdUnavailableReason:
'preload-performance-marker-missing',
};
const identity = unavailableIdentity(
'preload-performance-marker-missing'
);
pendingRequests.push({
identity,
operation,
@@ -248,6 +324,7 @@ export function installDatabaseRequestIdentityCaptureInMain(
pendingMarkers = [];
pendingRequests = [];
quarantinedKeys = [];
successMarkers = [];
active = true;
},
stop: () => {
@@ -255,6 +332,13 @@ export function installDatabaseRequestIdentityCaptureInMain(
pendingMarkers = [];
pendingRequests = [];
quarantinedKeys = [];
successMarkers = [];
},
successMarkerCount: () => successMarkers.length,
takeSuccessMarkers: () => {
const captured = Object.freeze([...successMarkers]);
successMarkers = [];
return captured;
},
};
target[stateKey] = api;
@@ -0,0 +1,161 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { createDatabaseWorkerCancelCapture } from './database-worker-cancel-capture';
function dispatch(overrides: Record<string, unknown> = {}) {
return {
captureGeneration: 3,
ipcCallId: 9,
operationId: 'operation-1',
postedEpochMs: 110,
requestId: 'request-1',
sourceEpochMs: 100,
...overrides,
};
}
function receipt(overrides: Record<string, unknown> = {}) {
return {
type: 'performance-cancel-received',
epochMs: 120,
operationId: 'operation-1',
requestId: 'request-1',
...overrides,
};
}
describe('database worker cancel capture', () => {
it('restores without module closures for Electron-main injection', () => {
const source = createDatabaseWorkerCancelCapture.toString();
assert.doesNotMatch(source, /__name/);
const factory = new Function(
`"use strict"; return (${source});`
)() as typeof createDatabaseWorkerCancelCapture;
factory().start(1);
});
it('keeps dispatch, worker receipt, and terminal cancellation distinct', () => {
const capture = createDatabaseWorkerCancelCapture();
capture.start(3);
assert.equal(capture.acceptDispatch(dispatch()), true);
assert.equal(
capture.acceptReceipt({
captureGeneration: 3,
message: receipt(),
receivedEpochMs: 121,
recordGeneration: 3,
}),
true
);
assert.equal(
capture.acceptTerminal({
captureGeneration: 3,
operationId: 'operation-1',
receivedEpochMs: 130,
recordGeneration: 3,
requestId: 'request-1',
}),
true
);
const snapshot = capture.stop();
assert.deepEqual(snapshot.invalidReasons, []);
assert.deepEqual(
snapshot.timeline.map(({ epochMs, type }) => ({ epochMs, type })),
[
{ epochMs: 110, type: 'db-cancel-dispatched' },
{ epochMs: 120, type: 'db-cancel-received' },
{ epochMs: 130, type: 'db-cancel-terminal-received' },
]
);
});
it('rejects extra fields, non-monotonic epochs, and cross-generation receipts', () => {
for (const testCase of [
{
message: receipt({ credential: 'forbidden' }),
receivedEpochMs: 121,
recordGeneration: 3,
},
{
message: receipt({ epochMs: 109 }),
receivedEpochMs: 121,
recordGeneration: 3,
},
{
message: receipt({ epochMs: 122 }),
receivedEpochMs: 121,
recordGeneration: 3,
},
{
message: receipt(),
receivedEpochMs: 121,
recordGeneration: 2,
},
]) {
const capture = createDatabaseWorkerCancelCapture();
capture.start(3);
assert.equal(capture.acceptDispatch(dispatch()), true);
assert.equal(
capture.acceptReceipt({
captureGeneration: 3,
...testCase,
}),
false
);
assert.notEqual(capture.stop().invalidReasons.length, 0);
}
});
it('invalidates a missing receipt separately from a missing terminal', () => {
const missingReceipt = createDatabaseWorkerCancelCapture();
missingReceipt.start(3);
assert.equal(missingReceipt.acceptDispatch(dispatch()), true);
assert.deepEqual(missingReceipt.stop().invalidReasons, [
'cancel-receipt-missing',
'cancel-terminal-missing',
]);
const missingTerminal = createDatabaseWorkerCancelCapture();
missingTerminal.start(3);
assert.equal(missingTerminal.acceptDispatch(dispatch()), true);
assert.equal(
missingTerminal.acceptReceipt({
captureGeneration: 3,
message: receipt(),
receivedEpochMs: 121,
recordGeneration: 3,
}),
true
);
assert.deepEqual(missingTerminal.stop().invalidReasons, [
'cancel-terminal-missing',
]);
});
it('fails neutrally for revoked or throwing proxy input', () => {
const capture = createDatabaseWorkerCancelCapture();
capture.start(3);
const revoked = Proxy.revocable({}, {});
revoked.revoke();
const throwing = new Proxy(
{},
{
get() {
throw new Error('property access denied');
},
ownKeys() {
throw new Error('keys denied');
},
}
);
assert.doesNotThrow(() => capture.acceptDispatch(revoked.proxy));
assert.equal(capture.acceptDispatch(revoked.proxy), false);
assert.doesNotThrow(() => capture.acceptReceipt(throwing as never));
assert.equal(capture.acceptReceipt(throwing as never), false);
assert.doesNotThrow(() => capture.acceptTerminal(throwing as never));
assert.equal(capture.acceptTerminal(throwing as never), false);
});
});
@@ -0,0 +1,295 @@
export interface DatabaseWorkerCancelTimelineRecord {
readonly epochMs: number;
readonly ipcCallId: number;
readonly operationId: string;
readonly requestId: string;
readonly sourceEpochMs: number;
readonly type:
| 'db-cancel-dispatched'
| 'db-cancel-received'
| 'db-cancel-terminal-received';
}
export interface DatabaseWorkerCancelCaptureSnapshot {
readonly invalidReasons: readonly string[];
readonly timeline: readonly DatabaseWorkerCancelTimelineRecord[];
}
export interface DatabaseWorkerCancelCapture {
acceptDispatch(input: unknown): boolean;
acceptReceipt(input: {
readonly captureGeneration: number;
readonly message: unknown;
readonly receivedEpochMs: number;
readonly recordGeneration: number | null;
}): boolean;
acceptTerminal(input: {
readonly captureGeneration: number;
readonly operationId: string;
readonly receivedEpochMs: number;
readonly recordGeneration: number | null;
readonly requestId: string;
}): boolean;
start(captureGeneration: number): void;
stop(): DatabaseWorkerCancelCaptureSnapshot;
}
export function createDatabaseWorkerCancelCapture(): DatabaseWorkerCancelCapture {
type JsonRecord = Record<string, unknown>;
interface DispatchState {
readonly captureGeneration: number;
readonly ipcCallId: number;
readonly operationId: string;
readonly postedEpochMs: number;
readonly requestId: string;
readonly sourceEpochMs: number;
receiptEpochMs: number | null;
terminalEpochMs: number | null;
}
const dispatchKeys = new Set([
'captureGeneration',
'ipcCallId',
'operationId',
'postedEpochMs',
'requestId',
'sourceEpochMs',
]);
const receiptKeys = new Set([
'type',
'epochMs',
'operationId',
'requestId',
]);
const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
let active = false;
let generation = 0;
let invalidReasons: string[] = [];
let timeline: DatabaseWorkerCancelTimelineRecord[] = [];
const dispatches = new Map<string, DispatchState>();
const helpers = {
invalidate(reason: string): false {
invalidReasons.push(reason);
return false;
},
isEpoch(value: unknown): value is number {
return (
typeof value === 'number' && Number.isFinite(value) && value > 0
);
},
isGeneration(value: unknown): value is number {
return Number.isSafeInteger(value) && Number(value) > 0;
},
isIdentifier(value: unknown): value is string {
return typeof value === 'string' && identifierPattern.test(value);
},
isRecord(value: unknown): value is JsonRecord {
return (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value)
);
},
ownsOnly(value: JsonRecord, keys: ReadonlySet<string>): boolean {
const ownKeys = Reflect.ownKeys(value);
return (
ownKeys.length === keys.size &&
ownKeys.every((key) => typeof key === 'string' && keys.has(key))
);
},
timelineRecord(
dispatch: DispatchState,
epochMs: number,
type: DatabaseWorkerCancelTimelineRecord['type']
): DatabaseWorkerCancelTimelineRecord {
return {
epochMs,
ipcCallId: dispatch.ipcCallId,
operationId: dispatch.operationId,
requestId: dispatch.requestId,
sourceEpochMs: dispatch.sourceEpochMs,
type,
};
},
};
return {
acceptDispatch(input): boolean {
try {
if (
!active ||
!helpers.isRecord(input) ||
!helpers.ownsOnly(input, dispatchKeys) ||
input['captureGeneration'] !== generation ||
!helpers.isGeneration(input['captureGeneration']) ||
!Number.isSafeInteger(input['ipcCallId']) ||
Number(input['ipcCallId']) <= 0 ||
!helpers.isIdentifier(input['operationId']) ||
!helpers.isIdentifier(input['requestId']) ||
!helpers.isEpoch(input['postedEpochMs']) ||
!helpers.isEpoch(input['sourceEpochMs']) ||
Number(input['sourceEpochMs']) >
Number(input['postedEpochMs'])
) {
return helpers.invalidate('cancel-dispatch-invalid');
}
const requestId = input['requestId'];
if (dispatches.has(requestId)) {
return helpers.invalidate('cancel-dispatch-duplicate');
}
const dispatch = {
captureGeneration: generation,
ipcCallId: Number(input['ipcCallId']),
operationId: input['operationId'],
postedEpochMs: Number(input['postedEpochMs']),
receiptEpochMs: null,
requestId,
sourceEpochMs: Number(input['sourceEpochMs']),
terminalEpochMs: null,
};
dispatches.set(requestId, dispatch);
timeline.push(
helpers.timelineRecord(
dispatch,
dispatch.postedEpochMs,
'db-cancel-dispatched'
)
);
return true;
} catch {
return helpers.invalidate('cancel-dispatch-invalid');
}
},
acceptReceipt(input): boolean {
try {
if (
!active ||
input.captureGeneration !== generation ||
input.recordGeneration !== generation
) {
return helpers.invalidate(
'cancel-receipt-generation-mismatch'
);
}
const message = input.message;
if (
!helpers.isRecord(message) ||
!helpers.ownsOnly(message, receiptKeys) ||
message['type'] !== 'performance-cancel-received' ||
!helpers.isIdentifier(message['operationId']) ||
!helpers.isIdentifier(message['requestId']) ||
!helpers.isEpoch(message['epochMs']) ||
!helpers.isEpoch(input.receivedEpochMs)
) {
return helpers.invalidate('cancel-receipt-invalid-schema');
}
const dispatch = dispatches.get(message['requestId']);
if (!dispatch) {
return helpers.invalidate(
'cancel-receipt-dispatch-missing'
);
}
if (dispatch.operationId !== message['operationId']) {
return helpers.invalidate(
'cancel-receipt-correlation-mismatch'
);
}
if (
Number(message['epochMs']) < dispatch.postedEpochMs ||
Number(message['epochMs']) > input.receivedEpochMs
) {
return helpers.invalidate('cancel-receipt-epoch-invalid');
}
if (dispatch.receiptEpochMs !== null) {
return helpers.invalidate('cancel-receipt-duplicate');
}
dispatch.receiptEpochMs = Number(message['epochMs']);
timeline.push(
helpers.timelineRecord(
dispatch,
dispatch.receiptEpochMs,
'db-cancel-received'
)
);
return true;
} catch {
return helpers.invalidate('cancel-receipt-invalid-schema');
}
},
acceptTerminal(input): boolean {
try {
if (
!active ||
input.captureGeneration !== generation ||
input.recordGeneration !== generation
) {
return helpers.invalidate(
'cancel-terminal-generation-mismatch'
);
}
if (
!helpers.isIdentifier(input.operationId) ||
!helpers.isIdentifier(input.requestId) ||
!helpers.isEpoch(input.receivedEpochMs)
) {
return helpers.invalidate('cancel-terminal-invalid');
}
const dispatch = dispatches.get(input.requestId);
if (!dispatch) {
return helpers.invalidate(
'cancel-terminal-dispatch-missing'
);
}
if (dispatch.operationId !== input.operationId) {
return helpers.invalidate(
'cancel-terminal-correlation-mismatch'
);
}
if (
dispatch.receiptEpochMs === null ||
input.receivedEpochMs < dispatch.receiptEpochMs
) {
return helpers.invalidate('cancel-terminal-before-receipt');
}
if (dispatch.terminalEpochMs !== null) {
return helpers.invalidate('cancel-terminal-duplicate');
}
dispatch.terminalEpochMs = input.receivedEpochMs;
timeline.push(
helpers.timelineRecord(
dispatch,
dispatch.terminalEpochMs,
'db-cancel-terminal-received'
)
);
return true;
} catch {
return helpers.invalidate('cancel-terminal-invalid');
}
},
start(captureGeneration): void {
if (!helpers.isGeneration(captureGeneration)) {
throw new Error('database-worker-cancel-generation-invalid');
}
active = true;
generation = captureGeneration;
invalidReasons = [];
timeline = [];
dispatches.clear();
},
stop(): DatabaseWorkerCancelCaptureSnapshot {
for (const dispatch of dispatches.values()) {
if (dispatch.receiptEpochMs === null) {
invalidReasons.push('cancel-receipt-missing');
}
if (dispatch.terminalEpochMs === null) {
invalidReasons.push('cancel-terminal-missing');
}
}
active = false;
return Object.freeze({
invalidReasons: Object.freeze([...invalidReasons]),
timeline: Object.freeze([...timeline]),
});
},
};
}
@@ -0,0 +1,98 @@
/* eslint-disable playwright/expect-expect -- This is a Node assertion-based performance contract test. */
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
const source = readFileSync(
new URL('./m3u-refresh-main-capture.ts', import.meta.url),
'utf8'
);
function section(startMarker: string, endMarker: string): string {
const start = source.indexOf(startMarker);
const end = source.indexOf(endMarker, start);
assert.ok(start >= 0, `missing start marker: ${startMarker}`);
assert.ok(end > start, `missing end marker: ${endMarker}`);
return source.slice(start, end);
}
test('counts only finite nonnegative worker heap and external-memory samples per capture generation', () => {
const workerRecord = section(
'interface WorkerRecord',
'interface TimelineRecord'
);
const createRecord = section(
'const record: WorkerRecord = {',
'nextWorkerOrdinal += 1'
);
const sampleWorker = section(
'const sampleWorker =',
'const resetWorkerForCapture'
);
const resetWorker = section(
'const resetWorkerForCapture',
'const startWorker'
);
assert.match(workerRecord, /externalMemorySampleCount: number/);
assert.match(workerRecord, /heapUsedSampleCount: number/);
assert.match(createRecord, /externalMemorySampleCount: 0/);
assert.match(createRecord, /heapUsedSampleCount: 0/);
assert.match(resetWorker, /record\.externalMemorySampleCount = 0/);
assert.match(resetWorker, /record\.heapUsedSampleCount = 0/);
assert.match(sampleWorker, /const heapUsedSample = stats\.used_heap_size/);
assert.match(
sampleWorker,
/Number\.isFinite\(heapUsedSample\)[\s\S]*heapUsedSample >= 0/
);
assert.match(sampleWorker, /record\.heapUsedSampleCount \+= 1/);
assert.match(
sampleWorker,
/const externalMemorySample = stats\.external_memory/
);
assert.match(
sampleWorker,
/Number\.isFinite\(externalMemorySample\)[\s\S]*externalMemorySample >= 0/
);
assert.match(sampleWorker, /record\.externalMemorySampleCount \+= 1/);
assert.doesNotMatch(sampleWorker, /used_heap_size \?\? 0/);
assert.doesNotMatch(sampleWorker, /external_memory \?\? 0/);
});
test('raw worker metrics distinguish missing samples from measured zero-byte peaks', () => {
const transport = section(
'const workers = currentWorkerRecords',
'const transport: MainCaptureGenerationTransport'
);
assert.match(
transport,
/externalMemorySampleCount:\s*record\.externalMemorySampleCount/
);
assert.match(
transport,
/heapUsedSampleCount:\s*record\.heapUsedSampleCount/
);
assert.match(
transport,
/peakExternalBytes:\s*record\.externalMemorySampleCount > 0[\s\S]*\?\s*record\.externalPeak[\s\S]*:\s*null/
);
assert.match(
transport,
/peakHeapUsedBytes:\s*record\.heapUsedSampleCount > 0[\s\S]*\?\s*record\.heapPeak[\s\S]*:\s*null/
);
assert.match(
transport,
/peakExternalUnavailableReason:\s*record\.externalMemorySampleCount > 0[\s\S]*\?\s*null[\s\S]*:\s*'worker-external-memory-samples-missing'/
);
assert.match(
transport,
/peakHeapUsedUnavailableReason:\s*record\.heapUsedSampleCount > 0[\s\S]*\?\s*null[\s\S]*:\s*'worker-heap-used-samples-missing'/
);
assert.doesNotMatch(
transport,
/peakExternalBytes: record\.externalPeak[,}]/
);
assert.doesNotMatch(transport, /peakHeapUsedBytes: record\.heapPeak[,}]/);
});
@@ -0,0 +1,121 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
assessDatabaseWorkerPeakMemoryValidity,
DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON,
} from './database-worker-peak-memory-validity';
describe('database worker peak-memory validity', () => {
it('requires successful heap and external-memory samples in every measured run', () => {
const validity = assessDatabaseWorkerPeakMemoryValidity([
iteration('warmup', 'warmup', worker(0, 0)),
iteration('run-01', 'measured', worker(3, 4)),
iteration('diagnostic', 'diagnostic', worker(0, 0)),
]);
assert.deepEqual(validity, {
invalidMeasuredRuns: [],
measuredRunCount: 1,
validForComparison: true,
validMeasuredRunCount: 1,
});
});
it('fails closed for absent samples instead of accepting initialized zero peaks', () => {
const missingCounts = assessDatabaseWorkerPeakMemoryValidity([
iteration('run-01', 'measured', {
kind: 'database.worker',
peakExternalBytes: 0,
peakHeapUsedBytes: 0,
}),
iteration('run-02', 'measured', worker(0, 1)),
iteration('run-03', 'measured', worker(1, 0)),
]);
assert.deepEqual(missingCounts.invalidMeasuredRuns, [
{
reason: DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.HEAP_SAMPLES_MISSING,
runId: 'run-01',
},
{
reason: DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.HEAP_SAMPLES_MISSING,
runId: 'run-02',
},
{
reason: DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.EXTERNAL_MEMORY_SAMPLES_MISSING,
runId: 'run-03',
},
]);
assert.equal(missingCounts.validForComparison, false);
assert.equal(missingCounts.validMeasuredRunCount, 0);
});
it('reports stable worker-cardinality and malformed-peak reasons', () => {
const validity = assessDatabaseWorkerPeakMemoryValidity([
{ kind: 'measured', main: { workers: [] }, runId: 'missing' },
{
kind: 'measured',
main: { workers: [worker(1, 1), worker(1, 1)] },
runId: 'multiple',
},
iteration('heap-invalid', 'measured', {
...worker(1, 1),
peakHeapUsedBytes: Number.NaN,
}),
iteration('external-invalid', 'measured', {
...worker(1, 1),
peakExternalBytes: -1,
}),
iteration('heap-reason-incoherent', 'measured', {
...worker(1, 1),
peakHeapUsedUnavailableReason:
'worker-heap-used-samples-missing',
}),
iteration('external-reason-incoherent', 'measured', {
...worker(1, 1),
peakExternalUnavailableReason:
'worker-external-memory-samples-missing',
}),
]);
assert.deepEqual(
validity.invalidMeasuredRuns.map(({ reason }) => reason),
[
DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.DATABASE_WORKER_MISSING,
DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.MULTIPLE_DATABASE_WORKERS,
DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.HEAP_PEAK_INVALID,
DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.EXTERNAL_MEMORY_PEAK_INVALID,
DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.HEAP_PEAK_INVALID,
DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.EXTERNAL_MEMORY_PEAK_INVALID,
]
);
});
});
function iteration(
runId: string,
kind: string,
databaseWorker: Record<string, unknown>
) {
return {
kind,
main: { workers: [databaseWorker] },
runId,
};
}
function worker(
heapUsedSampleCount: number,
externalMemorySampleCount: number
) {
return {
externalMemorySampleCount,
heapUsedSampleCount,
kind: 'database.worker',
peakExternalBytes: 600,
peakExternalUnavailableReason: null,
peakHeapUsedBytes: 1_500,
peakHeapUsedUnavailableReason: null,
};
}
@@ -0,0 +1,114 @@
export const DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON = {
EXTERNAL_MEMORY_PEAK_INVALID:
'database-worker-external-memory-peak-invalid',
EXTERNAL_MEMORY_SAMPLES_MISSING:
'database-worker-external-memory-samples-missing',
HEAP_PEAK_INVALID: 'database-worker-heap-peak-invalid',
HEAP_SAMPLES_MISSING: 'database-worker-heap-samples-missing',
MULTIPLE_DATABASE_WORKERS: 'multiple-database-workers',
DATABASE_WORKER_MISSING: 'database-worker-missing',
} as const;
export type DatabaseWorkerPeakMemoryInvalidReason =
(typeof DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON)[keyof typeof DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON];
export interface DatabaseWorkerPeakMemoryValidity {
readonly invalidMeasuredRuns: readonly {
readonly reason: DatabaseWorkerPeakMemoryInvalidReason;
readonly runId: string;
}[];
readonly measuredRunCount: number;
readonly validForComparison: boolean;
readonly validMeasuredRunCount: number;
}
interface PeakMemoryWorker {
readonly externalMemorySampleCount?: unknown;
readonly heapUsedSampleCount?: unknown;
readonly kind: string;
readonly peakExternalBytes: unknown;
readonly peakExternalUnavailableReason?: unknown;
readonly peakHeapUsedBytes: unknown;
readonly peakHeapUsedUnavailableReason?: unknown;
}
interface PeakMemoryIteration {
readonly kind: string;
readonly main: {
readonly workers: readonly PeakMemoryWorker[];
};
readonly runId: string;
}
export function assessDatabaseWorkerPeakMemoryValidity(
iterations: readonly PeakMemoryIteration[]
): DatabaseWorkerPeakMemoryValidity {
const measured = iterations.filter(
(iteration) => iteration.kind === 'measured'
);
const invalidMeasuredRuns: {
reason: DatabaseWorkerPeakMemoryInvalidReason;
runId: string;
}[] = [];
for (const iteration of measured) {
const workers = iteration.main.workers.filter(
(worker) => worker.kind === 'database.worker'
);
const reason = assessWorkers(workers);
if (reason !== null) {
invalidMeasuredRuns.push({ reason, runId: iteration.runId });
}
}
return Object.freeze({
invalidMeasuredRuns: Object.freeze(invalidMeasuredRuns),
measuredRunCount: measured.length,
validForComparison:
measured.length > 0 && invalidMeasuredRuns.length === 0,
validMeasuredRunCount: measured.length - invalidMeasuredRuns.length,
});
}
function assessWorkers(
workers: readonly PeakMemoryWorker[]
): DatabaseWorkerPeakMemoryInvalidReason | null {
if (workers.length === 0) {
return DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.DATABASE_WORKER_MISSING;
}
if (workers.length !== 1) {
return DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.MULTIPLE_DATABASE_WORKERS;
}
const worker = workers[0];
if (!worker) {
return DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.DATABASE_WORKER_MISSING;
}
if (!isPositiveSampleCount(worker.heapUsedSampleCount)) {
return DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.HEAP_SAMPLES_MISSING;
}
if (
!isPeak(worker.peakHeapUsedBytes) ||
worker.peakHeapUsedUnavailableReason !== null
) {
return DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.HEAP_PEAK_INVALID;
}
if (!isPositiveSampleCount(worker.externalMemorySampleCount)) {
return DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.EXTERNAL_MEMORY_SAMPLES_MISSING;
}
if (
!isPeak(worker.peakExternalBytes) ||
worker.peakExternalUnavailableReason !== null
) {
return DATABASE_WORKER_PEAK_MEMORY_INVALID_REASON.EXTERNAL_MEMORY_PEAK_INVALID;
}
return null;
}
function isPositiveSampleCount(value: unknown): value is number {
return Number.isSafeInteger(value) && Number(value) > 0;
}
function isPeak(value: unknown): value is number {
return Number.isSafeInteger(value) && Number(value) >= 0;
}
@@ -370,21 +370,22 @@ test('worker capture failures use an artifact-neutral timeline label', () => {
assert.doesNotMatch(source, /worker-profile-error:\$\{stage\}/);
});
test('capture generation resets artifact paths and always emits current DB records', () => {
test('capture generation resets artifact paths while arming and always emits current DB records', () => {
const source = readFileSync(
new URL('./m3u-refresh-main-capture.ts', import.meta.url),
'utf8'
);
const start = source.indexOf('const startCapture = async (');
const diagnostic = source.indexOf('if (state.diagnostic)', start);
const api = source.indexOf('const api =', start);
const profileReset = source.indexOf('state.mainProfilePath = null', start);
const snapshotReset = source.indexOf(
'state.mainSnapshotPath = null',
start
);
assert.ok(profileReset > start && profileReset < diagnostic);
assert.ok(snapshotReset > start && snapshotReset < diagnostic);
assert.ok(profileReset > start && profileReset < api);
assert.ok(snapshotReset > start && snapshotReset < api);
assert.doesNotMatch(source.slice(start, api), /'Profiler\.start'/);
assert.match(
source,
/record\.captureGeneration === state\.captureGeneration[\s\S]*record\.kind === 'database\.worker'/
@@ -0,0 +1,127 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
assessDatabaseWorkerRequestMetricsValidity,
DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON,
} from './database-worker-request-metrics-validity';
import { createCompleteTestMainCapture } from './m3u-import-report.test-helpers';
import type {
WorkerCaptureMetrics,
WorkerRequestPerformanceMetrics,
} from './m3u-refresh-cancellation-contract';
describe('database worker request-metrics validity', () => {
it('requires exactly two complete request samples in every measured run', () => {
const worker = completeWorker();
const validity = assessDatabaseWorkerRequestMetricsValidity([
iteration('warmup', 'warmup', [worker]),
iteration('run-01', 'measured', [worker]),
iteration('run-02', 'measured', [worker]),
iteration('diagnostic', 'diagnostic', [worker]),
]);
assert.deepEqual(validity, {
expectedRequestCount: 4,
invalidRequests: [],
measuredRunCount: 2,
validForComparison: true,
validMeasuredRunCount: 2,
validRequestCount: 4,
});
});
it('fails each measured run with missing ELD, ELU, CPU, or raw capture metrics', () => {
const validity = assessDatabaseWorkerRequestMetricsValidity([
invalidRequestIteration('run-eld', {
eventLoopDelay: null,
eventLoopDelayUnavailableReason:
'event-loop-delay-capture-unavailable',
histogramFlushedEpochMs: null,
}),
invalidRequestIteration('run-elu', {
eventLoopUtilization: null,
eventLoopUtilizationUnavailableReason:
'event-loop-utilization-unavailable',
}),
invalidRequestIteration('run-cpu', {
threadCpuSystemMicros: null,
threadCpuUnavailableReason: 'thread-cpu-usage-unavailable',
threadCpuUserMicros: null,
}),
invalidRequestIteration('run-capture', {
performanceCaptureUnavailableReason:
'worker-performance-capture-invalid',
}),
]);
assert.equal(validity.expectedRequestCount, 8);
assert.equal(validity.validRequestCount, 4);
assert.equal(validity.validMeasuredRunCount, 0);
assert.equal(validity.validForComparison, false);
assert.deepEqual(
validity.invalidRequests.map(({ reason }) => reason),
[
DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.EVENT_LOOP_DELAY_INVALID,
DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.EVENT_LOOP_UTILIZATION_INVALID,
DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.THREAD_CPU_INVALID,
DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.WORKER_CAPTURE_INVALID,
]
);
});
it('fails closed for missing, multiple, and wrong request sets', () => {
const worker = completeWorker();
const validity = assessDatabaseWorkerRequestMetricsValidity([
iteration('missing', 'measured', []),
iteration('multiple', 'measured', [worker, worker]),
iteration('wrong-requests', 'measured', [
{
...worker,
requests: worker.requests.slice(0, 1),
},
]),
]);
assert.equal(validity.expectedRequestCount, 6);
assert.equal(validity.validRequestCount, 0);
assert.equal(validity.validForComparison, false);
assert.deepEqual(
validity.invalidRequests.map(({ reason }) => reason),
[
DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.DATABASE_WORKER_MISSING,
DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.MULTIPLE_DATABASE_WORKERS,
DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.REQUEST_SET_INVALID,
]
);
});
});
function completeWorker(): WorkerCaptureMetrics {
const worker = createCompleteTestMainCapture().workers[0];
assert.ok(worker);
return worker;
}
function invalidRequestIteration(
runId: string,
updates: Partial<WorkerRequestPerformanceMetrics>
) {
const worker = completeWorker();
const get = worker.requests[1];
assert.ok(get);
return iteration(runId, 'measured', [
{
...worker,
requests: [worker.requests[0], { ...get, ...updates }],
},
]);
}
function iteration(
runId: string,
kind: string,
workers: readonly WorkerCaptureMetrics[]
) {
return { kind, main: { workers }, runId };
}
@@ -0,0 +1,197 @@
import type {
WorkerCaptureMetrics,
WorkerRequestPerformanceMetrics,
} from './m3u-refresh-cancellation-contract';
import {
PERFORMANCE_ITERATION_KIND,
PERFORMANCE_WORKER_KIND,
} from './m3u-refresh-cancellation-contract';
export const DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON = {
DATABASE_WORKER_MISSING: 'database-worker-missing',
EVENT_LOOP_DELAY_INVALID: 'event-loop-delay-invalid',
EVENT_LOOP_UTILIZATION_INVALID: 'event-loop-utilization-invalid',
MULTIPLE_DATABASE_WORKERS: 'multiple-database-workers',
REQUEST_SET_INVALID: 'initial-import-request-set-invalid',
THREAD_CPU_INVALID: 'thread-cpu-invalid',
WORKER_CAPTURE_INVALID: 'worker-performance-capture-invalid',
} as const;
export type DatabaseWorkerRequestMetricsInvalidReason =
(typeof DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON)[keyof typeof DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON];
export interface DatabaseWorkerRequestMetricsValidity {
readonly expectedRequestCount: number;
readonly invalidRequests: readonly {
readonly operation: string | null;
readonly reason: DatabaseWorkerRequestMetricsInvalidReason;
readonly runId: string;
}[];
readonly measuredRunCount: number;
readonly validForComparison: boolean;
readonly validMeasuredRunCount: number;
readonly validRequestCount: number;
}
interface RequestMetricsIteration {
readonly kind: string;
readonly main: {
readonly workers: readonly WorkerCaptureMetrics[];
};
readonly runId: string;
}
export function assessDatabaseWorkerRequestMetricsValidity(
iterations: readonly RequestMetricsIteration[]
): DatabaseWorkerRequestMetricsValidity {
const measured = iterations.filter(
(iteration) =>
iteration.kind === PERFORMANCE_ITERATION_KIND.MEASURED
);
const invalidRequests: {
operation: string | null;
reason: DatabaseWorkerRequestMetricsInvalidReason;
runId: string;
}[] = [];
let validMeasuredRunCount = 0;
let validRequestCount = 0;
for (const iteration of measured) {
const workers = iteration.main.workers.filter(
(worker) => worker.kind === PERFORMANCE_WORKER_KIND.DATABASE
);
if (workers.length === 0) {
invalidRequests.push({
operation: null,
reason: DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.DATABASE_WORKER_MISSING,
runId: iteration.runId,
});
continue;
}
const worker = workers.length === 1 ? workers[0] : null;
if (!worker) {
invalidRequests.push({
operation: null,
reason: DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.MULTIPLE_DATABASE_WORKERS,
runId: iteration.runId,
});
continue;
}
const requests = requireInitialImportRequestPair(worker);
if (requests === null) {
invalidRequests.push({
operation: null,
reason: DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.REQUEST_SET_INVALID,
runId: iteration.runId,
});
continue;
}
let runIsValid = true;
for (const request of requests) {
const reason = assessRequest(request);
if (reason === null) {
validRequestCount += 1;
} else {
runIsValid = false;
invalidRequests.push({
operation: request.operation,
reason,
runId: iteration.runId,
});
}
}
if (runIsValid) {
validMeasuredRunCount += 1;
}
}
const expectedRequestCount = measured.length * 2;
return Object.freeze({
expectedRequestCount,
invalidRequests: Object.freeze(invalidRequests),
measuredRunCount: measured.length,
validForComparison:
measured.length > 0 &&
validMeasuredRunCount === measured.length &&
validRequestCount === expectedRequestCount,
validMeasuredRunCount,
validRequestCount,
});
}
function requireInitialImportRequestPair(
worker: WorkerCaptureMetrics
): readonly [
WorkerRequestPerformanceMetrics,
WorkerRequestPerformanceMetrics,
] | null {
const upserts = worker.requests.filter(
(request) =>
request.operation === 'DB_UPSERT_APP_PLAYLIST' && request.success
);
const gets = worker.requests.filter(
(request) =>
request.operation === 'DB_GET_APP_PLAYLIST' && request.success
);
return worker.requests.length === 2 &&
upserts.length === 1 &&
upserts[0] &&
gets.length === 1 &&
gets[0]
? [upserts[0], gets[0]]
: null;
}
function assessRequest(
request: WorkerRequestPerformanceMetrics
): DatabaseWorkerRequestMetricsInvalidReason | null {
if (
request.performanceCaptureUnavailableReason !== null ||
request.invalidReason !== null
) {
return DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.WORKER_CAPTURE_INVALID;
}
if (
request.eventLoopDelayUnavailableReason !== null ||
request.histogramFlushedEpochMs === null ||
!isEventLoopDelay(request.eventLoopDelay)
) {
return DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.EVENT_LOOP_DELAY_INVALID;
}
if (
request.eventLoopUtilizationUnavailableReason !== null ||
!isUtilization(request.eventLoopUtilization)
) {
return DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.EVENT_LOOP_UTILIZATION_INVALID;
}
if (
request.threadCpuUnavailableReason !== null ||
!isFiniteNonNegative(request.threadCpuSystemMicros) ||
!isFiniteNonNegative(request.threadCpuUserMicros)
) {
return DATABASE_WORKER_REQUEST_METRICS_INVALID_REASON.THREAD_CPU_INVALID;
}
return null;
}
function isEventLoopDelay(
value: WorkerRequestPerformanceMetrics['eventLoopDelay']
): value is NonNullable<WorkerRequestPerformanceMetrics['eventLoopDelay']> {
return (
value !== null &&
isFiniteNonNegative(value.maxMs) &&
isFiniteNonNegative(value.p95Ms) &&
isFiniteNonNegative(value.p99Ms) &&
value.p95Ms <= value.p99Ms &&
value.p99Ms <= value.maxMs
);
}
function isUtilization(value: number | null): value is number {
return isFiniteNonNegative(value) && value <= 1;
}
function isFiniteNonNegative(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
}
@@ -0,0 +1,105 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
buildElectronLaunchEnvironment,
ELECTRON_RUNTIME_ENVIRONMENT_KEYS,
} from '../electron-test-fixtures';
import { assertXtreamBenchmarkEnvironmentIsolated } from './xtream-benchmark-iteration-support';
describe('Electron launch environment isolation', () => {
it('inherits only portable runtime keys for a profiled benchmark child', () => {
const environment = buildElectronLaunchEnvironment(
'/tmp/iptvnator-performance',
{
environmentInheritance: 'runtime-only',
env: {
IPTVNATOR_PERF_CAPTURE: '1',
},
},
{
AWS_SECRET_ACCESS_KEY: 'aws-secret',
ELECTRON_RUN_AS_NODE: '1',
GITHUB_TOKEN: 'github-secret',
HTTPS_PROXY: 'https://user:password@proxy.invalid',
IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS: '0',
IPTVNATOR_REAL_PROVIDER_URL: 'https://provider.invalid',
IPTVNATOR_XTREAM_MOCK_CONTROL: '1',
IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN: 'secret-token',
NODE_OPTIONS: '--require credential-dumper.js',
PATH: '/usr/bin',
REAL_PROVIDER_PASSWORD: 'provider-password',
REAL_PROVIDER_URL: 'https://provider.invalid',
TMPDIR: '/tmp/runtime',
}
);
assert.equal(environment['IPTVNATOR_PERF_CAPTURE'], '1');
assert.equal(environment['IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS'], '1');
assert.equal(environment['PATH'], '/usr/bin');
assert.equal(environment['TMPDIR'], '/tmp/runtime');
for (const key of [
'AWS_SECRET_ACCESS_KEY',
'ELECTRON_RUN_AS_NODE',
'GITHUB_TOKEN',
'HTTPS_PROXY',
'IPTVNATOR_REAL_PROVIDER_URL',
'IPTVNATOR_XTREAM_MOCK_CONTROL',
'IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN',
'NODE_OPTIONS',
'REAL_PROVIDER_PASSWORD',
'REAL_PROVIDER_URL',
]) {
assert.equal(environment[key], undefined, key);
}
});
it('drops undefined overrides instead of serializing them into the child', () => {
const environment = buildElectronLaunchEnvironment(
'/tmp/iptvnator-performance',
{ env: { OPTIONAL_DEBUG_FLAG: undefined } },
{ OPTIONAL_DEBUG_FLAG: 'inherited' }
);
assert.equal(environment['OPTIONAL_DEBUG_FLAG'], undefined);
});
it('checks the effective Electron main environment, not only launch input', async () => {
const allowed = [
...ELECTRON_RUNTIME_ENVIRONMENT_KEYS,
'ELECTRON_IS_DEV',
'IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS',
'IPTVNATOR_DB_WORKER_BATCH_DELAY_MS',
'IPTVNATOR_E2E_DATA_DIR',
'IPTVNATOR_PERF_CAPTURE',
'IPTVNATOR_PERF_WORKER_PROFILING',
'IPTVNATOR_TRACE_RENDERER_CONSOLE',
'NODE_ENV',
];
await assert.doesNotReject(
assertXtreamBenchmarkEnvironmentIsolated(fakeApp(allowed))
);
for (const leaked of [
'GITHUB_TOKEN',
'HTTPS_PROXY',
'NODE_OPTIONS',
'REAL_PROVIDER_URL',
]) {
await assert.rejects(
assertXtreamBenchmarkEnvironmentIsolated(
fakeApp([...allowed, leaked])
),
/xtream-benchmark-environment-reached-electron/
);
}
});
});
function fakeApp(keys: readonly string[]) {
return {
electronApp: {
evaluate: async () => [...keys],
},
} as never;
}
@@ -0,0 +1,193 @@
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
export function isCpuProfile(value: unknown): boolean {
if (!isRecord(value)) {
return false;
}
const startTime = value['startTime'];
const endTime = value['endTime'];
const nodes = value['nodes'];
const samples = value['samples'];
const timeDeltas = value['timeDeltas'];
if (
!isNonNegativeSafeInteger(startTime) ||
!isNonNegativeSafeInteger(endTime) ||
endTime <= startTime ||
!Array.isArray(nodes) ||
nodes.length === 0 ||
!Array.isArray(samples) ||
samples.length === 0 ||
!Array.isArray(timeDeltas) ||
timeDeltas.length !== samples.length
) {
return false;
}
const nodeIds = new Set<number>();
for (const node of nodes) {
if (!isCpuProfileNode(node) || nodeIds.has(node['id'])) {
return false;
}
nodeIds.add(node['id']);
}
if (
nodes.some((node) =>
(node as Record<string, unknown>)['children'] instanceof Array
? (
(node as Record<string, unknown>)[
'children'
] as unknown[]
).some(
(childId) =>
!isNonNegativeSafeInteger(childId) ||
!nodeIds.has(childId)
)
: false
) ||
samples.some(
(sampleId) =>
!isNonNegativeSafeInteger(sampleId) ||
!nodeIds.has(sampleId)
)
) {
return false;
}
const duration = endTime - startTime;
let elapsed = 0;
for (const delta of timeDeltas) {
if (!Number.isSafeInteger(delta)) {
return false;
}
elapsed += delta as number;
if (
!Number.isSafeInteger(elapsed) ||
elapsed < 0 ||
elapsed > duration
) {
return false;
}
}
return elapsed > 0;
}
export function readCpuProfileDurationMicros(value: unknown): number | null {
if (!isCpuProfile(value)) {
return null;
}
const profile = value as Record<string, number>;
return profile['endTime'] - profile['startTime'];
}
export function isHeapSnapshot(value: unknown): boolean {
if (
!isRecord(value) ||
!isRecord(value['snapshot']) ||
!isRecord(value['snapshot']['meta'])
) {
return false;
}
const meta = value['snapshot']['meta'];
const nodeFields = meta['node_fields'];
const nodeTypes = meta['node_types'];
const edgeFields = meta['edge_fields'];
const edgeTypes = meta['edge_types'];
const nodes = value['nodes'];
const edges = value['edges'];
const strings = value['strings'];
return (
isNonEmptyStringArray(nodeFields) &&
isHeapTypeTable(nodeTypes, nodeFields.length) &&
isNonEmptyStringArray(edgeFields) &&
isHeapTypeTable(edgeTypes, edgeFields.length) &&
Array.isArray(nodes) &&
nodes.length > 0 &&
nodes.length % nodeFields.length === 0 &&
Array.isArray(edges) &&
edges.length > 0 &&
edges.length % edgeFields.length === 0 &&
Array.isArray(strings) &&
strings.length > 0
);
}
export function isRendererTrace(value: unknown): boolean {
if (!isRecord(value)) {
return false;
}
const events = value['traceEvents'];
return (
Array.isArray(events) &&
events.length > 0 &&
events.every(
(event) =>
isRecord(event) &&
isNonEmptyString(event['name']) &&
isNonEmptyString(event['ph']) &&
isNonNegativeFiniteNumber(event['ts'])
)
);
}
function isHeapTypeTable(value: unknown, fieldCount: number): boolean {
return (
Array.isArray(value) &&
value.length === fieldCount &&
value.every(
(entry) =>
isNonEmptyString(entry) || isNonEmptyStringArray(entry)
)
);
}
function isCpuProfileNode(
value: unknown
): value is Record<string, unknown> & { readonly id: number } {
if (
!isRecord(value) ||
!isNonNegativeSafeInteger(value['id']) ||
!isRecord(value['callFrame'])
) {
return false;
}
const callFrame = value['callFrame'];
const scriptId = callFrame['scriptId'];
const children = value['children'];
const hitCount = value['hitCount'];
return (
typeof callFrame['functionName'] === 'string' &&
(typeof scriptId === 'string' ||
isNonNegativeSafeInteger(scriptId)) &&
typeof callFrame['url'] === 'string' &&
Number.isSafeInteger(callFrame['lineNumber']) &&
Number.isSafeInteger(callFrame['columnNumber']) &&
(children === undefined ||
(Array.isArray(children) &&
children.every(isNonNegativeSafeInteger))) &&
(hitCount === undefined || isNonNegativeSafeInteger(hitCount))
);
}
function isNonEmptyStringArray(value: unknown): value is readonly string[] {
return (
Array.isArray(value) &&
value.length > 0 &&
value.every(isNonEmptyString)
);
}
function isNonEmptyString(value: unknown): value is string {
return typeof value === 'string' && value.length > 0;
}
function isNonNegativeSafeInteger(value: unknown): value is number {
return Number.isSafeInteger(value) && (value as number) >= 0;
}
function isNonNegativeFiniteNumber(value: unknown): value is number {
return (
typeof value === 'number' && Number.isFinite(value) && value >= 0
);
}
@@ -0,0 +1,234 @@
/* eslint-disable playwright/expect-expect -- This is a Node assertion-based performance contract test. */
import assert from 'node:assert/strict';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import {
type InitialImportDiagnosticCapture,
validateInitialImportDiagnosticArtifacts,
} from './initial-import-diagnostic-artifacts';
import { CPU_PROFILE } from './initial-import-diagnostic-test-fixtures';
const HEAP_META = {
edge_fields: ['type', 'name_or_index', 'to_node'],
edge_types: [['context'], 'string_or_number', 'node'],
node_fields: [
'type',
'name',
'id',
'self_size',
'edge_count',
'detachedness',
],
node_types: [
['hidden'],
'string',
'number',
'number',
'number',
'number',
],
};
const HEAP_SNAPSHOT = {
edges: [0, 0, 0],
nodes: [0, 0, 1, 0, 1, 0],
snapshot: { meta: HEAP_META },
strings: ['', 'root'],
};
const TRACE = {
traceEvents: [{ name: 'initial-import', ph: 'X', ts: 1 }],
};
function capture(directory: string): InitialImportDiagnosticCapture {
return {
main: {
cpuProfilePath: join(directory, 'main.cpuprofile'),
heapSnapshotPath: join(directory, 'main.heapsnapshot'),
workers: [
{
kind: 'database.worker',
ordinal: 4,
profilePath: join(
directory,
'database.worker-4.cpuprofile'
),
snapshotPath: join(
directory,
'database.worker-4.heapsnapshot'
),
},
],
},
renderer: {
cpuProfilePath: join(directory, 'renderer.cpuprofile'),
heapSnapshotPath: join(directory, 'renderer.heapsnapshot'),
tracePath: join(directory, 'renderer.trace.json'),
},
};
}
async function writeArtifacts(directory: string): Promise<void> {
const artifacts: Readonly<Record<string, unknown>> = {
'database.worker-4.cpuprofile': CPU_PROFILE,
'database.worker-4.heapsnapshot': HEAP_SNAPSHOT,
'main.cpuprofile': CPU_PROFILE,
'main.heapsnapshot': HEAP_SNAPSHOT,
'renderer.cpuprofile': CPU_PROFILE,
'renderer.heapsnapshot': HEAP_SNAPSHOT,
'renderer.trace.json': TRACE,
};
await Promise.all(
Object.entries(artifacts).map(([filename, value]) =>
writeFile(join(directory, filename), JSON.stringify(value))
)
);
}
async function withArtifacts(
run: (directory: string) => Promise<void>
): Promise<void> {
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-content-')
);
try {
await writeArtifacts(directory);
await run(directory);
} finally {
await rm(directory, { force: true, recursive: true });
}
}
test('accepts bounded signed CPU time deltas emitted by Chromium', async () => {
await withArtifacts(async (directory) => {
await writeFile(
join(directory, 'renderer.cpuprofile'),
JSON.stringify({
...CPU_PROFILE,
endTime: 5_000,
samples: [1, 1, 1],
timeDeltas: [1_000, -100, 1_000],
})
);
await assert.doesNotReject(() =>
validateInitialImportDiagnosticArtifacts(
capture(directory),
directory
)
);
});
});
test('rejects CPU profiles without useful bounded samples and stack references', async () => {
await withArtifacts(async (directory) => {
const invalidProfiles = [
{ ...CPU_PROFILE, samples: [], timeDeltas: [] },
{ ...CPU_PROFILE, timeDeltas: undefined },
{ ...CPU_PROFILE, timeDeltas: [] },
{
...CPU_PROFILE,
endTime: CPU_PROFILE.startTime,
},
{
...CPU_PROFILE,
samples: [2],
},
{
...CPU_PROFILE,
timeDeltas: [-1],
},
{
...CPU_PROFILE,
timeDeltas: [2_001],
},
{
...CPU_PROFILE,
nodes: [
...CPU_PROFILE.nodes,
{ ...CPU_PROFILE.nodes[0], id: 1 },
],
},
{
...CPU_PROFILE,
nodes: [{ id: 1 }],
},
];
for (const profile of invalidProfiles) {
await writeFile(
join(directory, 'main.cpuprofile'),
JSON.stringify(profile)
);
await assert.rejects(
() =>
validateInitialImportDiagnosticArtifacts(
capture(directory),
directory
),
/initial-import-diagnostic-artifact-invalid:main-cpu-profile/
);
}
});
});
test('rejects empty or malformed heap tables and invalid field strides', async () => {
await withArtifacts(async (directory) => {
const invalidSnapshots = [
{ snapshot: {}, nodes: [], edges: [], strings: [] },
{ ...HEAP_SNAPSHOT, nodes: [] },
{ ...HEAP_SNAPSHOT, edges: [] },
{ ...HEAP_SNAPSHOT, strings: [] },
{
...HEAP_SNAPSHOT,
snapshot: {
meta: { ...HEAP_META, node_types: undefined },
},
},
{ ...HEAP_SNAPSHOT, nodes: [...HEAP_SNAPSHOT.nodes, 0] },
{ ...HEAP_SNAPSHOT, edges: [...HEAP_SNAPSHOT.edges, 0] },
];
for (const snapshot of invalidSnapshots) {
await writeFile(
join(directory, 'main.heapsnapshot'),
JSON.stringify(snapshot)
);
await assert.rejects(
() =>
validateInitialImportDiagnosticArtifacts(
capture(directory),
directory
),
/initial-import-diagnostic-artifact-invalid:main-heap-snapshot/
);
}
});
});
test('rejects empty traces and malformed trace events', async () => {
await withArtifacts(async (directory) => {
const invalidTraces = [
{ traceEvents: [] },
{ traceEvents: [null] },
{ traceEvents: [{ name: '', ph: 'X', ts: 1 }] },
{ traceEvents: [{ name: 'initial-import', ph: '', ts: 1 }] },
{
traceEvents: [
{ name: 'initial-import', ph: 'X', ts: 'invalid' },
],
},
];
for (const trace of invalidTraces) {
await writeFile(
join(directory, 'renderer.trace.json'),
JSON.stringify(trace)
);
await assert.rejects(
() =>
validateInitialImportDiagnosticArtifacts(
capture(directory),
directory
),
/initial-import-diagnostic-artifact-invalid:renderer-trace/
);
}
});
});
@@ -0,0 +1,394 @@
/* eslint-disable playwright/expect-expect -- This is a Node assertion-based performance contract test. */
import assert from 'node:assert/strict';
import { mkdtemp, rm, symlink, unlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import { CPU_PROFILE } from './initial-import-diagnostic-test-fixtures';
interface DiagnosticCapture {
readonly main: {
readonly cpuProfilePath: string | null;
readonly heapSnapshotPath: string | null;
readonly workers: readonly {
readonly kind: string;
readonly ordinal: number;
readonly profilePath: string | null;
readonly snapshotPath: string | null;
}[];
};
readonly renderer: {
readonly cpuProfilePath: string | null;
readonly heapSnapshotPath: string | null;
readonly tracePath: string | null;
};
}
interface DiagnosticArtifactsModule {
validateInitialImportDiagnosticArtifacts?: (
capture: DiagnosticCapture,
iterationDirectory: string
) => Promise<void>;
}
const diagnosticArtifactsModulePromise = import(
new URL('./initial-import-diagnostic-artifacts.ts', import.meta.url).href
)
.then((module) => module as DiagnosticArtifactsModule)
.catch(() => null);
const HEAP_SNAPSHOT = {
edges: [0, 0, 0],
nodes: [0, 0, 1, 0, 1, 0],
snapshot: {
meta: {
edge_fields: ['type', 'name_or_index', 'to_node'],
edge_types: [['context'], 'string_or_number', 'node'],
node_fields: [
'type',
'name',
'id',
'self_size',
'edge_count',
'detachedness',
],
node_types: [
['hidden'],
'string',
'number',
'number',
'number',
'number',
],
},
},
strings: ['', 'root'],
};
const RENDERER_TRACE = {
traceEvents: [{ name: 'initial-import', ph: 'X', ts: 1 }],
};
function capture(directory: string, databaseOrdinal = 4): DiagnosticCapture {
return {
main: {
cpuProfilePath: join(directory, 'main.cpuprofile'),
heapSnapshotPath: join(directory, 'main.heapsnapshot'),
workers: [
{
kind: 'database.worker',
ordinal: databaseOrdinal,
profilePath: join(
directory,
`database.worker-${databaseOrdinal}.cpuprofile`
),
snapshotPath: join(
directory,
`database.worker-${databaseOrdinal}.heapsnapshot`
),
},
],
},
renderer: {
cpuProfilePath: join(directory, 'renderer.cpuprofile'),
heapSnapshotPath: join(directory, 'renderer.heapsnapshot'),
tracePath: join(directory, 'renderer.trace.json'),
},
};
}
async function writeValidArtifacts(
directory: string,
databaseOrdinal = 4
): Promise<void> {
await Promise.all([
writeFile(
join(directory, 'main.cpuprofile'),
JSON.stringify(CPU_PROFILE)
),
writeFile(
join(directory, 'renderer.cpuprofile'),
JSON.stringify(CPU_PROFILE)
),
writeFile(
join(directory, `database.worker-${databaseOrdinal}.cpuprofile`),
JSON.stringify(CPU_PROFILE)
),
writeFile(
join(directory, `database.worker-${databaseOrdinal}.heapsnapshot`),
JSON.stringify(HEAP_SNAPSHOT)
),
writeFile(
join(directory, 'main.heapsnapshot'),
JSON.stringify(HEAP_SNAPSHOT)
),
writeFile(
join(directory, 'renderer.heapsnapshot'),
JSON.stringify(HEAP_SNAPSHOT)
),
writeFile(
join(directory, 'renderer.trace.json'),
JSON.stringify(RENDERER_TRACE)
),
]);
}
test('accepts the complete parseable initial-import diagnostic artifact set', async () => {
const module = await diagnosticArtifactsModulePromise;
assert.ok(module, 'initial-import diagnostic validator must exist');
const validate = module.validateInitialImportDiagnosticArtifacts;
assert.equal(typeof validate, 'function');
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-diagnostic-')
);
try {
await writeValidArtifacts(directory);
await assert.doesNotReject(() =>
validate?.(capture(directory), directory)
);
} finally {
await rm(directory, { force: true, recursive: true });
}
});
test('fails closed for malformed or missing required process artifacts', async () => {
const module = await diagnosticArtifactsModulePromise;
assert.ok(module);
const validate = module.validateInitialImportDiagnosticArtifacts;
assert.equal(typeof validate, 'function');
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-invalid-')
);
try {
await writeValidArtifacts(directory);
await writeFile(join(directory, 'renderer.trace.json'), '{');
await assert.rejects(
() => validate?.(capture(directory), directory),
/initial-import-diagnostic-artifact-invalid:renderer-trace/
);
await writeFile(
join(directory, 'renderer.trace.json'),
JSON.stringify(RENDERER_TRACE)
);
const missingMainProfile: DiagnosticCapture = {
...capture(directory),
main: {
...capture(directory).main,
cpuProfilePath: null,
},
};
await assert.rejects(
() => validate?.(missingMainProfile, directory),
/initial-import-diagnostic-artifact-missing:main-cpu-profile/
);
} finally {
await rm(directory, { force: true, recursive: true });
}
});
test('rejects escaped paths and symlinks instead of reading outside the iteration directory', async () => {
const module = await diagnosticArtifactsModulePromise;
assert.ok(module);
const validate = module.validateInitialImportDiagnosticArtifacts;
assert.equal(typeof validate, 'function');
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-contained-')
);
const outsideDirectory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-outside-')
);
try {
await writeValidArtifacts(directory);
const escapedCapture: DiagnosticCapture = {
...capture(directory),
main: {
...capture(directory).main,
cpuProfilePath: join(outsideDirectory, 'main.cpuprofile'),
},
};
await writeFile(
join(outsideDirectory, 'main.cpuprofile'),
JSON.stringify(CPU_PROFILE)
);
await assert.rejects(
() => validate?.(escapedCapture, directory),
/initial-import-diagnostic-artifact-path-invalid:main-cpu-profile/
);
const rendererProfilePath = join(directory, 'renderer.cpuprofile');
await unlink(rendererProfilePath);
await symlink(
join(outsideDirectory, 'main.cpuprofile'),
rendererProfilePath
);
await assert.rejects(
() => validate?.(capture(directory), directory),
/initial-import-diagnostic-artifact-path-invalid:renderer-cpu-profile/
);
} finally {
await rm(directory, { force: true, recursive: true });
await rm(outsideDirectory, { force: true, recursive: true });
}
});
test('requires exactly one database profile and rejects every playlist-worker profile', async () => {
const module = await diagnosticArtifactsModulePromise;
assert.ok(module);
const validate = module.validateInitialImportDiagnosticArtifacts;
assert.equal(typeof validate, 'function');
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-workers-')
);
try {
await writeValidArtifacts(directory);
await writeFile(
join(directory, 'database.worker-9.cpuprofile'),
JSON.stringify(CPU_PROFILE)
);
await assert.rejects(
() => validate?.(capture(directory), directory),
/initial-import-diagnostic-database-profile-cardinality-invalid/
);
await unlink(join(directory, 'database.worker-9.cpuprofile'));
await writeFile(
join(directory, 'playlist-refresh.worker-2.cpuprofile'),
JSON.stringify(CPU_PROFILE)
);
await assert.rejects(
() => validate?.(capture(directory), directory),
/initial-import-diagnostic-playlist-profile-unexpected/
);
} finally {
await rm(directory, { force: true, recursive: true });
}
});
test('requires a structurally valid database worker heap snapshot', async () => {
const module = await diagnosticArtifactsModulePromise;
assert.ok(module);
const validate = module.validateInitialImportDiagnosticArtifacts;
assert.equal(typeof validate, 'function');
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-worker-snapshot-')
);
try {
await writeValidArtifacts(directory);
const missingSnapshot: DiagnosticCapture = {
...capture(directory),
main: {
...capture(directory).main,
workers: [
{
...capture(directory).main.workers[0],
snapshotPath: null,
},
],
},
};
await assert.rejects(
() => validate?.(missingSnapshot, directory),
/initial-import-diagnostic-artifact-missing:database-heap-snapshot/
);
await writeFile(
join(directory, 'database.worker-4.heapsnapshot'),
JSON.stringify({ snapshot: {}, nodes: [], edges: [] })
);
await assert.rejects(
() => validate?.(capture(directory), directory),
/initial-import-diagnostic-artifact-invalid:database-heap-snapshot/
);
} finally {
await rm(directory, { force: true, recursive: true });
}
});
test('rejects escaped and symlinked database worker heap snapshots', async () => {
const module = await diagnosticArtifactsModulePromise;
assert.ok(module);
const validate = module.validateInitialImportDiagnosticArtifacts;
assert.equal(typeof validate, 'function');
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-worker-contained-')
);
const outsideDirectory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-worker-outside-')
);
try {
await writeValidArtifacts(directory);
const outsideSnapshotPath = join(
outsideDirectory,
'database.worker-4.heapsnapshot'
);
await writeFile(outsideSnapshotPath, JSON.stringify(HEAP_SNAPSHOT));
const escapedSnapshot: DiagnosticCapture = {
...capture(directory),
main: {
...capture(directory).main,
workers: [
{
...capture(directory).main.workers[0],
snapshotPath: outsideSnapshotPath,
},
],
},
};
await assert.rejects(
() => validate?.(escapedSnapshot, directory),
/initial-import-diagnostic-artifact-path-invalid:database-heap-snapshot/
);
const snapshotPath = join(directory, 'database.worker-4.heapsnapshot');
await unlink(snapshotPath);
await symlink(outsideSnapshotPath, snapshotPath);
await assert.rejects(
() => validate?.(capture(directory), directory),
/initial-import-diagnostic-artifact-path-invalid:database-heap-snapshot/
);
} finally {
await rm(directory, { force: true, recursive: true });
await rm(outsideDirectory, { force: true, recursive: true });
}
});
test('rejects extra database snapshots and every playlist-worker snapshot', async () => {
const module = await diagnosticArtifactsModulePromise;
assert.ok(module);
const validate = module.validateInitialImportDiagnosticArtifacts;
assert.equal(typeof validate, 'function');
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-initial-import-worker-snapshot-count-')
);
try {
await writeValidArtifacts(directory);
await writeFile(
join(directory, 'database.worker-9.heapsnapshot'),
JSON.stringify(HEAP_SNAPSHOT)
);
await assert.rejects(
() => validate?.(capture(directory), directory),
/initial-import-diagnostic-database-snapshot-cardinality-invalid/
);
await unlink(join(directory, 'database.worker-9.heapsnapshot'));
await writeFile(
join(directory, 'playlist-refresh.worker-2.heapsnapshot'),
JSON.stringify(HEAP_SNAPSHOT)
);
await assert.rejects(
() => validate?.(capture(directory), directory),
/initial-import-diagnostic-playlist-snapshot-unexpected/
);
} finally {
await rm(directory, { force: true, recursive: true });
}
});
@@ -0,0 +1,262 @@
import { lstat, readFile, readdir, realpath } from 'node:fs/promises';
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
import {
isCpuProfile,
isHeapSnapshot,
isRendererTrace,
} from './initial-import-diagnostic-artifact-content-validation';
interface DiagnosticWorkerArtifact {
readonly kind: string;
readonly ordinal: number;
readonly profilePath: string | null;
readonly snapshotPath: string | null;
}
export interface InitialImportDiagnosticCapture {
readonly main: {
readonly cpuProfilePath: string | null;
readonly heapSnapshotPath: string | null;
readonly workers: readonly DiagnosticWorkerArtifact[];
};
readonly renderer: {
readonly cpuProfilePath: string | null;
readonly heapSnapshotPath: string | null;
readonly tracePath: string | null;
};
}
type ArtifactLabel =
| 'database-cpu-profile'
| 'database-heap-snapshot'
| 'main-cpu-profile'
| 'main-heap-snapshot'
| 'renderer-cpu-profile'
| 'renderer-heap-snapshot'
| 'renderer-trace';
interface ArtifactDescription {
readonly filename: string;
readonly label: ArtifactLabel;
readonly path: string | null;
readonly validate: (value: unknown) => boolean;
}
export async function validateInitialImportDiagnosticArtifacts(
capture: InitialImportDiagnosticCapture,
iterationDirectory: string
): Promise<void> {
const directory = resolve(iterationDirectory);
let realDirectory: string;
try {
realDirectory = await realpath(directory);
} catch {
throw new Error(
'initial-import-diagnostic-artifact-path-invalid:iteration-directory'
);
}
const databaseWorker = requireExactDatabaseWorker(capture.main.workers);
await assertWorkerArtifactCardinality(directory);
const artifacts: readonly ArtifactDescription[] = [
{
filename: 'main.cpuprofile',
label: 'main-cpu-profile',
path: capture.main.cpuProfilePath,
validate: isCpuProfile,
},
{
filename: 'renderer.cpuprofile',
label: 'renderer-cpu-profile',
path: capture.renderer.cpuProfilePath,
validate: isCpuProfile,
},
{
filename: 'renderer.trace.json',
label: 'renderer-trace',
path: capture.renderer.tracePath,
validate: isRendererTrace,
},
{
filename: 'main.heapsnapshot',
label: 'main-heap-snapshot',
path: capture.main.heapSnapshotPath,
validate: isHeapSnapshot,
},
{
filename: 'renderer.heapsnapshot',
label: 'renderer-heap-snapshot',
path: capture.renderer.heapSnapshotPath,
validate: isHeapSnapshot,
},
{
filename: `database.worker-${databaseWorker.ordinal}.cpuprofile`,
label: 'database-cpu-profile',
path: databaseWorker.profilePath,
validate: isCpuProfile,
},
{
filename: `database.worker-${databaseWorker.ordinal}.heapsnapshot`,
label: 'database-heap-snapshot',
path: databaseWorker.snapshotPath,
validate: isHeapSnapshot,
},
];
for (const artifact of artifacts) {
await validateJsonArtifact(directory, realDirectory, artifact);
}
}
function requireExactDatabaseWorker(
workers: readonly DiagnosticWorkerArtifact[]
): DiagnosticWorkerArtifact {
if (
workers.some(
(worker) =>
worker.kind === 'playlist-refresh.worker' &&
worker.profilePath !== null
)
) {
throw new Error(
'initial-import-diagnostic-playlist-profile-unexpected'
);
}
if (
workers.some(
(worker) =>
worker.kind === 'playlist-refresh.worker' &&
worker.snapshotPath !== null
)
) {
throw new Error(
'initial-import-diagnostic-playlist-snapshot-unexpected'
);
}
const databaseWorkers = workers.filter(
(worker) => worker.kind === 'database.worker'
);
const worker = databaseWorkers[0];
if (
databaseWorkers.length !== 1 ||
!worker ||
!Number.isSafeInteger(worker.ordinal) ||
worker.ordinal < 0
) {
throw new Error(
'initial-import-diagnostic-database-profile-cardinality-invalid'
);
}
return worker;
}
async function assertWorkerArtifactCardinality(
directory: string
): Promise<void> {
let entries: readonly string[];
try {
entries = await readdir(directory);
} catch {
throw new Error(
'initial-import-diagnostic-artifact-path-invalid:iteration-directory'
);
}
if (
entries.some((entry) =>
/^playlist-refresh\.worker-\d+\.cpuprofile$/u.test(entry)
)
) {
throw new Error(
'initial-import-diagnostic-playlist-profile-unexpected'
);
}
if (
entries.filter((entry) =>
/^database\.worker-\d+\.cpuprofile$/u.test(entry)
).length !== 1
) {
throw new Error(
'initial-import-diagnostic-database-profile-cardinality-invalid'
);
}
if (
entries.some((entry) =>
/^playlist-refresh\.worker-\d+\.heapsnapshot$/u.test(entry)
)
) {
throw new Error(
'initial-import-diagnostic-playlist-snapshot-unexpected'
);
}
if (
entries.filter((entry) =>
/^database\.worker-\d+\.heapsnapshot$/u.test(entry)
).length !== 1
) {
throw new Error(
'initial-import-diagnostic-database-snapshot-cardinality-invalid'
);
}
}
async function validateJsonArtifact(
directory: string,
realDirectory: string,
artifact: ArtifactDescription
): Promise<void> {
if (artifact.path === null) {
throw new Error(
`initial-import-diagnostic-artifact-missing:${artifact.label}`
);
}
const expectedPath = join(directory, artifact.filename);
if (resolve(artifact.path) !== expectedPath) {
throw new Error(
`initial-import-diagnostic-artifact-path-invalid:${artifact.label}`
);
}
let value: unknown;
try {
const stats = await lstat(expectedPath);
if (!stats.isFile() || stats.isSymbolicLink()) {
throw new Error('unsafe-artifact-type');
}
const realArtifactPath = await realpath(expectedPath);
if (!isContained(realDirectory, realArtifactPath)) {
throw new Error('escaped-artifact');
}
value = JSON.parse(await readFile(realArtifactPath, 'utf8')) as unknown;
} catch (error) {
if (
error instanceof Error &&
(error.message === 'unsafe-artifact-type' ||
error.message === 'escaped-artifact')
) {
throw new Error(
`initial-import-diagnostic-artifact-path-invalid:${artifact.label}`
);
}
throw new Error(
`initial-import-diagnostic-artifact-invalid:${artifact.label}`
);
}
if (!artifact.validate(value)) {
throw new Error(
`initial-import-diagnostic-artifact-invalid:${artifact.label}`
);
}
}
function isContained(directory: string, artifactPath: string): boolean {
const relativePath = relative(directory, artifactPath);
return (
relativePath.length > 0 &&
!isAbsolute(relativePath) &&
relativePath !== '..' &&
!relativePath.startsWith(`..${sep}`)
);
}
@@ -0,0 +1,19 @@
export const CPU_PROFILE = Object.freeze({
endTime: 3_000,
nodes: Object.freeze([
Object.freeze({
callFrame: Object.freeze({
columnNumber: -1,
functionName: '(root)',
lineNumber: -1,
scriptId: '0',
url: '',
}),
hitCount: 1,
id: 1,
}),
]),
samples: Object.freeze([1]),
startTime: 1_000,
timeDeltas: Object.freeze([1_000]),
});
@@ -0,0 +1,115 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { PERFORMANCE_ITERATION_KIND } from './m3u-refresh-cancellation-contract';
import {
assertSyntheticM3uImportUrl,
createM3uImportIterationDefinitions,
listM3uImportScenarios,
M3U_IMPORT_SCENARIO_ID,
} from './m3u-import-benchmark-contract';
describe('formal M3U import benchmark contract', () => {
it('defines the exact 10k, 50k, and 100k synthetic scenarios', () => {
assert.deepEqual(listM3uImportScenarios(), [
{
channelCount: 10_000,
id: M3U_IMPORT_SCENARIO_ID.IMPORT_10K,
},
{
channelCount: 50_000,
id: M3U_IMPORT_SCENARIO_ID.IMPORT_50K,
},
{
channelCount: 100_000,
id: M3U_IMPORT_SCENARIO_ID.IMPORT_100K,
},
]);
});
it('schedules one warm-up, five measured runs, and one diagnostic per size', () => {
const definitions = createM3uImportIterationDefinitions(false);
assert.equal(definitions.length, 21);
for (const scenario of listM3uImportScenarios()) {
const scenarioRuns = definitions.filter(
(definition) => definition.scenarioId === scenario.id
);
assert.deepEqual(
scenarioRuns.map(({ kind, runId }) => ({ kind, runId })),
[
{
kind: PERFORMANCE_ITERATION_KIND.WARMUP,
runId: 'warmup-01',
},
...Array.from({ length: 5 }, (_, index) => ({
kind: PERFORMANCE_ITERATION_KIND.MEASURED,
runId: `run-${String(index + 1).padStart(2, '0')}`,
})),
{
kind: PERFORMANCE_ITERATION_KIND.DIAGNOSTIC,
runId: 'diagnostic',
},
]
);
assert.ok(
scenarioRuns.every(
(definition) =>
definition.channelCount === scenario.channelCount
)
);
}
});
it('keeps fixture sizes unchanged while smoke mode reduces measured runs to one', () => {
const definitions = createM3uImportIterationDefinitions(true);
assert.equal(definitions.length, 9);
for (const scenario of listM3uImportScenarios()) {
assert.deepEqual(
definitions
.filter(
(definition) => definition.scenarioId === scenario.id
)
.map(({ kind, runId }) => ({ kind, runId })),
[
{
kind: PERFORMANCE_ITERATION_KIND.WARMUP,
runId: 'warmup-01',
},
{
kind: PERFORMANCE_ITERATION_KIND.MEASURED,
runId: 'run-01',
},
{
kind: PERFORMANCE_ITERATION_KIND.DIAGNOSTIC,
runId: 'diagnostic',
},
]
);
}
});
it('accepts only the exact credential-free loopback fixture URL', () => {
assert.doesNotThrow(() =>
assertSyntheticM3uImportUrl(
'http://127.0.0.1:43210/synthetic-performance.m3u'
)
);
for (const unsafe of [
'https://127.0.0.1:43210/synthetic-performance.m3u',
'http://localhost:43210/synthetic-performance.m3u',
'http://127.0.0.1/synthetic-performance.m3u',
'http://user:pass@127.0.0.1:43210/synthetic-performance.m3u',
'http://127.0.0.1:43210/other.m3u',
'http://127.0.0.1:43210/synthetic-performance.m3u?token=secret',
'http://127.0.0.1:43210/synthetic-performance.m3u#fragment',
]) {
assert.throws(
() => assertSyntheticM3uImportUrl(unsafe),
/exact synthetic HTTP loopback URL/
);
}
});
});
@@ -0,0 +1,118 @@
import {
PERFORMANCE_ITERATION_KIND,
type PerformanceIterationKind,
} from './m3u-refresh-cancellation-contract';
import { SYNTHETIC_M3U_RESOURCE_PATH } from './synthetic-m3u-server';
import type { SyntheticM3uChannelCount } from './synthetic-m3u';
export const M3U_IMPORT_SCENARIO_ID = {
IMPORT_10K: 'm3u-import-10k',
IMPORT_50K: 'm3u-import-50k',
IMPORT_100K: 'm3u-import-100k',
} as const;
export type M3uImportScenarioId =
(typeof M3U_IMPORT_SCENARIO_ID)[keyof typeof M3U_IMPORT_SCENARIO_ID];
export interface M3uImportScenario {
readonly channelCount: SyntheticM3uChannelCount;
readonly id: M3uImportScenarioId;
}
export interface M3uImportIterationDefinition {
readonly channelCount: SyntheticM3uChannelCount;
readonly kind: PerformanceIterationKind;
readonly runId: string;
readonly scenarioId: M3uImportScenarioId;
}
const SCENARIOS: readonly M3uImportScenario[] = Object.freeze([
Object.freeze({
channelCount: 10_000,
id: M3U_IMPORT_SCENARIO_ID.IMPORT_10K,
}),
Object.freeze({
channelCount: 50_000,
id: M3U_IMPORT_SCENARIO_ID.IMPORT_50K,
}),
Object.freeze({
channelCount: 100_000,
id: M3U_IMPORT_SCENARIO_ID.IMPORT_100K,
}),
]);
export function listM3uImportScenarios(): readonly M3uImportScenario[] {
return SCENARIOS;
}
export function createM3uImportIterationDefinitions(
smoke: boolean
): readonly M3uImportIterationDefinition[] {
const measuredRuns = smoke ? 1 : 5;
const definitions: M3uImportIterationDefinition[] = [];
for (const scenario of SCENARIOS) {
definitions.push(
iteration(scenario, PERFORMANCE_ITERATION_KIND.WARMUP, 'warmup-01')
);
for (let index = 1; index <= measuredRuns; index += 1) {
definitions.push(
iteration(
scenario,
PERFORMANCE_ITERATION_KIND.MEASURED,
`run-${String(index).padStart(2, '0')}`
)
);
}
definitions.push(
iteration(
scenario,
PERFORMANCE_ITERATION_KIND.DIAGNOSTIC,
'diagnostic'
)
);
}
return Object.freeze(definitions);
}
export function assertSyntheticM3uImportUrl(value: string): void {
let url: URL;
try {
url = new URL(value);
} catch {
throw invalidSyntheticUrl();
}
if (
url.protocol !== 'http:' ||
url.hostname !== '127.0.0.1' ||
url.port.length === 0 ||
url.username.length > 0 ||
url.password.length > 0 ||
url.pathname !== SYNTHETIC_M3U_RESOURCE_PATH ||
url.search.length > 0 ||
url.hash.length > 0
) {
throw invalidSyntheticUrl();
}
}
function iteration(
scenario: M3uImportScenario,
kind: PerformanceIterationKind,
runId: string
): M3uImportIterationDefinition {
return Object.freeze({
channelCount: scenario.channelCount,
kind,
runId,
scenarioId: scenario.id,
});
}
function invalidSyntheticUrl(): Error {
return new Error(
'M3U performance fixture must use the exact synthetic HTTP loopback URL'
);
}
@@ -0,0 +1,184 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { mkdir, mkdtemp, rm, symlink } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { describe, it } from 'node:test';
import {
assertM3uImportOutputPathHasNoSymlinks,
assertM3uImportSourceState,
M3U_IMPORT_RENDERER_CDP_PORT,
resolveM3uImportRendererCdpPort,
resolveM3uImportOutputLayout,
} from './m3u-import-benchmark-support';
import { assertM3uImportRequestDelta } from './m3u-import.benchmark';
describe('initial M3U import benchmark lifecycle', () => {
it('keeps requested output and variant strictly below dist/performance', () => {
const workspaceRoot = resolve('/workspace/iptvnator');
const outputRoot = resolve(
workspaceRoot,
'dist/performance/20260727T040000Z-m3u-import'
);
assert.deepEqual(
resolveM3uImportOutputLayout(workspaceRoot, outputRoot, 'baseline'),
{
outputRoot,
variantDirectory: resolve(outputRoot, 'baseline'),
}
);
for (const requestedRoot of [
'dist/performance/run',
resolve(workspaceRoot, 'dist/performance'),
resolve(workspaceRoot, 'dist/performance-sibling/run'),
resolve(workspaceRoot, '../outside'),
]) {
assert.throws(
() =>
resolveM3uImportOutputLayout(
workspaceRoot,
requestedRoot,
'baseline'
),
/absolute path below dist\/performance/
);
}
assert.throws(
() =>
resolveM3uImportOutputLayout(
workspaceRoot,
outputRoot,
'../escape'
),
/variant is invalid/i
);
});
it('requires a clean source tree for formal runs but permits dirty smoke evidence', () => {
const dirtyState = {
commit: 'a'.repeat(40),
diffSha256: 'b'.repeat(64),
dirty: true,
};
assert.throws(
() => assertM3uImportSourceState(false, dirtyState),
/Formal performance runs require a clean Git worktree/
);
assert.doesNotThrow(() => assertM3uImportSourceState(true, dirtyState));
assert.doesNotThrow(() =>
assertM3uImportSourceState(false, {
...dirtyState,
dirty: false,
})
);
});
it('rejects an existing output ancestor symlink before artifact creation', async () => {
const fixtureRoot = await mkdtemp(
join(tmpdir(), 'iptvnator-m3u-output-path-')
);
const repositoryRoot = join(fixtureRoot, 'repository');
const performanceRoot = join(
repositoryRoot,
'dist',
'performance'
);
const outside = join(fixtureRoot, 'outside');
await mkdir(performanceRoot, { recursive: true });
await mkdir(outside);
await symlink(outside, join(performanceRoot, 'redirect'));
try {
await assert.rejects(
assertM3uImportOutputPathHasNoSymlinks(
repositoryRoot,
join(performanceRoot, 'redirect', 'run')
),
/Performance output path contains a symbolic link/
);
await assert.doesNotReject(
assertM3uImportOutputPathHasNoSymlinks(
repositoryRoot,
join(performanceRoot, 'ordinary', 'run')
)
);
} finally {
await rm(fixtureRoot, { force: true, recursive: true });
}
});
it('keeps formal CDP capture on 9222 while allowing an isolated smoke port', () => {
assert.equal(
resolveM3uImportRendererCdpPort(false, undefined),
M3U_IMPORT_RENDERER_CDP_PORT
);
assert.equal(
resolveM3uImportRendererCdpPort(false, '9222'),
M3U_IMPORT_RENDERER_CDP_PORT
);
assert.equal(resolveM3uImportRendererCdpPort(true, '9322'), 9322);
assert.throws(
() => resolveM3uImportRendererCdpPort(false, '9322'),
/Formal performance runs require CDP port 9222/
);
for (const invalid of ['0', '1023', '65536', '9222.5', 'not-a-port']) {
assert.throws(
() => resolveM3uImportRendererCdpPort(true, invalid),
/IPTVNATOR_PERF_CDP_PORT is invalid/
);
}
});
it('fails closed unless one and only one app GET occurs', () => {
assert.doesNotThrow(() => assertM3uImportRequestDelta(3, 4));
assert.throws(
() => assertM3uImportRequestDelta(3, 3),
/exactly one application GET/
);
assert.throws(
() => assertM3uImportRequestDelta(3, 5),
/exactly one application GET/
);
});
it('waits for the exact upsert plus route-reload GET sequence', () => {
const source = readFileSync(
resolve(
process.cwd(),
'src/performance/m3u-import.benchmark.ts'
),
'utf8'
);
assert.match(source, /status\.databaseRequests\s*===\s*2/);
assert.match(source, /status\.databaseUpsertsCompleted\s*===\s*1/);
assert.match(source, /status\.databaseGetsCompleted\s*===\s*1/);
assert.match(source, /status\.databasePending\s*===\s*0/);
assert.match(source, /status\.preloadSuccessMarkers\s*===\s*2/);
});
it('always disposes renderer capture before closing Electron on iteration failure', () => {
const source = readFileSync(
resolve(
process.cwd(),
'src/performance/m3u-import.benchmark.ts'
),
'utf8'
);
const runIteration = source.indexOf('async function runIteration');
const finallyBlock = source.indexOf('} finally {', runIteration);
const rendererDispose = source.indexOf(
'await renderer.dispose()',
finallyBlock
);
const appClose = source.indexOf('await closeElectronApp(app)', finallyBlock);
assert.ok(finallyBlock > runIteration);
assert.ok(rendererDispose > finallyBlock);
assert.ok(appClose > rendererDispose);
});
});
@@ -0,0 +1,303 @@
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { access, lstat, mkdir, readFile, writeFile } from 'node:fs/promises';
import { createServer as createTcpServer } from 'node:net';
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
import { expect, type Page } from '@playwright/test';
import {
type LaunchedElectronApp,
workspaceRoot,
} from '../electron-test-fixtures';
import { assertPerformanceArtifactCapacity } from './performance-artifact-preflight';
import type { RendererWindowIdentity } from './renderer-window-rss-session';
const OUTPUT_VARIANT_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
export const M3U_IMPORT_RENDERER_CDP_PORT = 9222;
export interface M3uImportGitSourceState {
readonly commit: string;
readonly diffSha256: string;
readonly dirty: boolean;
}
export interface M3uImportOutputLayout {
readonly outputRoot: string;
readonly variantDirectory: string;
}
export interface M3uImportBenchmarkConfiguration extends M3uImportOutputLayout {
readonly electronVersion: string;
readonly measuredRuns: number;
readonly rendererCdpPort: number;
readonly smoke: boolean;
readonly sourceState: M3uImportGitSourceState;
readonly variant: string;
}
export async function resolveM3uImportBenchmarkConfiguration(): Promise<M3uImportBenchmarkConfiguration> {
const requestedRoot = process.env['IPTVNATOR_PERF_OUTPUT_DIR'];
if (!requestedRoot) {
throw new Error('IPTVNATOR_PERF_OUTPUT_DIR is required');
}
const variant = process.env['IPTVNATOR_PERF_VARIANT'] ?? 'baseline';
const layout = resolveM3uImportOutputLayout(
workspaceRoot,
requestedRoot,
variant
);
const smoke = process.env['IPTVNATOR_PERF_SMOKE'] === '1';
const rendererCdpPort = resolveM3uImportRendererCdpPort(
smoke,
process.env['IPTVNATOR_PERF_CDP_PORT']
);
const sourceState = readGitSourceState(workspaceRoot);
assertM3uImportSourceState(smoke, sourceState);
const electronPackage = JSON.parse(
await readFile(
join(workspaceRoot, 'node_modules', 'electron', 'package.json'),
'utf8'
)
) as { version?: unknown };
if (typeof electronPackage.version !== 'string') {
throw new Error('Unable to resolve the Electron runtime version');
}
await assertM3uImportOutputPathHasNoSymlinks(
workspaceRoot,
layout.outputRoot
);
await assertMissing(layout.variantDirectory);
await assertPerformanceArtifactCapacity(layout.variantDirectory);
await mkdir(layout.variantDirectory, {
mode: 0o700,
recursive: true,
});
return Object.freeze({
...layout,
electronVersion: electronPackage.version,
measuredRuns: smoke ? 1 : 5,
rendererCdpPort,
smoke,
sourceState,
variant,
});
}
export function resolveM3uImportOutputLayout(
repositoryRoot: string,
requestedRoot: string,
variant: string
): M3uImportOutputLayout {
const performanceRoot = resolve(repositoryRoot, 'dist', 'performance');
const outputRoot = resolve(requestedRoot);
if (
!isAbsolute(requestedRoot) ||
!isStrictDescendant(performanceRoot, outputRoot)
) {
throw new Error(
'Performance output must be an absolute path below dist/performance'
);
}
if (!OUTPUT_VARIANT_PATTERN.test(variant)) {
throw new Error('IPTVNATOR_PERF_VARIANT is invalid');
}
const variantDirectory = resolve(outputRoot, variant);
if (!isStrictDescendant(outputRoot, variantDirectory)) {
throw new Error('IPTVNATOR_PERF_VARIANT is invalid');
}
return Object.freeze({ outputRoot, variantDirectory });
}
export async function assertM3uImportOutputPathHasNoSymlinks(
repositoryRoot: string,
outputRoot: string
): Promise<void> {
const root = resolve(repositoryRoot);
const output = resolve(outputRoot);
if (!isStrictDescendant(root, output)) {
throw new Error('Performance output path escapes the repository');
}
const components = relative(root, output).split(sep);
let current = root;
for (const component of ['', ...components]) {
current = component === '' ? current : join(current, component);
try {
const stats = await lstat(current);
if (stats.isSymbolicLink()) {
throw new Error(
`Performance output path contains a symbolic link: ${current}`
);
}
} catch (error) {
if (isMissingPath(error)) {
return;
}
throw error;
}
}
}
export function assertM3uImportSourceState(
smoke: boolean,
state: M3uImportGitSourceState
): void {
if (!smoke && state.dirty) {
throw new Error(
'Formal performance runs require a clean Git worktree; commit or stash changes first'
);
}
}
export function resolveM3uImportRendererCdpPort(
smoke: boolean,
requestedPort: string | undefined
): number {
const port =
requestedPort === undefined
? M3U_IMPORT_RENDERER_CDP_PORT
: Number(requestedPort);
if (
(requestedPort !== undefined && !/^[1-9]\d*$/.test(requestedPort)) ||
!Number.isSafeInteger(port) ||
port < 1024 ||
port > 65_535
) {
throw new Error('IPTVNATOR_PERF_CDP_PORT is invalid');
}
if (!smoke && port !== M3U_IMPORT_RENDERER_CDP_PORT) {
throw new Error(
`Formal performance runs require CDP port ${M3U_IMPORT_RENDERER_CDP_PORT}`
);
}
return port;
}
export async function resolveRendererWindowIdentity(
app: LaunchedElectronApp
): Promise<RendererWindowIdentity> {
const handle = await app.electronApp.browserWindow(app.mainWindow);
try {
return await handle.evaluate((browserWindow) => {
const exactWindow = browserWindow as unknown as {
readonly id: number;
readonly webContents: { readonly id: number };
};
return {
browserWindowId: exactWindow.id,
webContentsId: exactWindow.webContents.id,
};
});
} finally {
await handle.dispose();
}
}
export async function assertTcpPortAvailable(port: number): Promise<void> {
const server = createTcpServer();
await new Promise<void>((resolvePromise, rejectPromise) => {
server.once('error', rejectPromise);
server.listen(
{ exclusive: true, host: '127.0.0.1', port },
resolvePromise
);
});
await new Promise<void>((resolvePromise, rejectPromise) => {
server.close((error) =>
error ? rejectPromise(error) : resolvePromise()
);
});
}
export async function assertRendererCdpTarget(
page: Page,
port: number
): Promise<void> {
await expect
.poll(
async () => {
try {
const response = await fetch(
`http://127.0.0.1:${port}/json/list`
);
const targets = response.ok
? ((await response.json()) as unknown)
: null;
return (
Array.isArray(targets) &&
targets.some(
(target) =>
typeof target === 'object' &&
target !== null &&
(target as Record<string, unknown>)['type'] ===
'page' &&
(target as Record<string, unknown>)['url'] ===
page.url()
)
);
} catch {
return false;
}
},
{ timeout: 10_000 }
)
.toBe(true);
}
export async function writeM3uImportJson(
path: string,
value: unknown
): Promise<void> {
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}
function readGitSourceState(repositoryRoot: string): M3uImportGitSourceState {
const git = (args: readonly string[]): string =>
execFileSync('git', [...args], {
cwd: repositoryRoot,
encoding: 'utf8',
});
const commit = git(['rev-parse', 'HEAD']).trim();
const status = git(['status', '--porcelain=v1', '--untracked-files=all']);
const diffSha256 = createHash('sha256')
.update(status)
.update('\0')
.update(git(['diff', '--binary', 'HEAD']))
.update('\0')
.update(git(['diff', '--binary', '--cached', 'HEAD']))
.digest('hex');
return Object.freeze({
commit,
diffSha256,
dirty: status.trim().length > 0,
});
}
function isStrictDescendant(parent: string, candidate: string): boolean {
const pathFromParent = relative(resolve(parent), resolve(candidate));
return (
pathFromParent.length > 0 &&
pathFromParent !== '..' &&
!pathFromParent.startsWith(`..${sep}`) &&
!isAbsolute(pathFromParent)
);
}
async function assertMissing(path: string): Promise<void> {
try {
await access(path);
} catch {
return;
}
throw new Error(`Performance output already exists: ${path}`);
}
function isMissingPath(error: unknown): boolean {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
error.code === 'ENOENT'
);
}
@@ -0,0 +1,250 @@
import type {
MainTimelineRecord,
WorkerRequestPerformanceMetrics,
} from './m3u-refresh-cancellation-contract';
type InitialImportDatabaseOperation =
| 'DB_GET_APP_PLAYLIST'
| 'DB_UPSERT_APP_PLAYLIST';
export interface M3uImportIpcAttribution {
readonly databaseWorkerToMainCloneProxyMs: number;
readonly ipcStructuredCloneProxyMs: number;
readonly mainToDatabaseWorkerCloneProxyMs: number;
readonly mainToRendererCloneProxyMs: number;
readonly rendererToMainCloneProxyMs: number;
}
interface RequestBoundaryAttribution {
readonly mainToDatabaseWorkerMs: number;
readonly mainToRendererMs: number;
readonly rendererToMainMs: number;
readonly successEpochMs: number;
readonly workerToMainMs: number;
}
export function deriveM3uImportIpcAttribution(input: {
readonly get: WorkerRequestPerformanceMetrics;
readonly getReadStartedEpochMs: number;
readonly importDispatchEndedEpochMs: number;
readonly importDispatchStartedEpochMs: number;
readonly normalizeEndedEpochMs: number;
readonly playlistId: string;
readonly publishChannelsStartedEpochMs: number;
readonly serializeStartedEpochMs: number;
readonly timeline: readonly MainTimelineRecord[];
readonly upsert: WorkerRequestPerformanceMetrics;
}): M3uImportIpcAttribution {
requireOrdered(
input.normalizeEndedEpochMs,
input.importDispatchStartedEpochMs
);
requireOrdered(
input.importDispatchStartedEpochMs,
input.importDispatchEndedEpochMs
);
const upsert = deriveRequestBoundaryAttribution({
operation: 'DB_UPSERT_APP_PLAYLIST',
playlistId: input.playlistId,
request: input.upsert,
timeline: input.timeline,
workPhaseStartedEpochMs: input.serializeStartedEpochMs,
});
requireOrdered(input.importDispatchEndedEpochMs, requireSource(input.upsert));
const get = deriveRequestBoundaryAttribution({
operation: 'DB_GET_APP_PLAYLIST',
playlistId: input.playlistId,
request: input.get,
timeline: input.timeline,
workPhaseStartedEpochMs: input.getReadStartedEpochMs,
});
requireOrdered(upsert.successEpochMs, requireSource(input.get));
requireOrdered(
get.successEpochMs,
input.publishChannelsStartedEpochMs
);
const initialMainToRendererMs = difference(
input.importDispatchStartedEpochMs,
input.normalizeEndedEpochMs
);
const mainToRendererCloneProxyMs =
initialMainToRendererMs +
upsert.mainToRendererMs +
get.mainToRendererMs;
const rendererToMainCloneProxyMs =
upsert.rendererToMainMs + get.rendererToMainMs;
const mainToDatabaseWorkerCloneProxyMs =
upsert.mainToDatabaseWorkerMs + get.mainToDatabaseWorkerMs;
const databaseWorkerToMainCloneProxyMs =
upsert.workerToMainMs + get.workerToMainMs;
return Object.freeze({
databaseWorkerToMainCloneProxyMs,
ipcStructuredCloneProxyMs:
mainToRendererCloneProxyMs +
rendererToMainCloneProxyMs +
mainToDatabaseWorkerCloneProxyMs +
databaseWorkerToMainCloneProxyMs,
mainToDatabaseWorkerCloneProxyMs,
mainToRendererCloneProxyMs,
rendererToMainCloneProxyMs,
});
}
function deriveRequestBoundaryAttribution(input: {
readonly operation: InitialImportDatabaseOperation;
readonly playlistId: string;
readonly request: WorkerRequestPerformanceMetrics;
readonly timeline: readonly MainTimelineRecord[];
readonly workPhaseStartedEpochMs: number;
}): RequestBoundaryAttribution {
requireInitialImportPreloadIdentity(input.request, input.operation);
const requestTimeline = requireTimelineRecord(
input.timeline,
'db-request',
input.request,
input.operation,
input.playlistId
);
requireTimelineRecord(
input.timeline,
'db-response',
input.request,
input.operation,
input.playlistId
);
const successTimeline = requireTimelineRecord(
input.timeline,
'preload-performance-success',
input.request,
input.operation,
input.playlistId
);
const sourceEpochMs = requireSource(input.request);
const requestReceivedEpochMs = requireTimestamp(
input.request.requestReceivedEpochMs
);
const workStartedEpochMs = requireTimestamp(
input.request.workStartedEpochMs
);
const workEndedEpochMs = requireTimestamp(input.request.workEndedEpochMs);
const responsePostedEpochMs = requireTimestamp(
input.request.responsePostedEpochMs
);
const responseEpochMs = requireTimestamp(input.request.responseEpochMs);
const successEpochMs = requireTimestamp(successTimeline.sourceEpochMs);
requireOrdered(sourceEpochMs, requestTimeline.epochMs);
requireOrdered(requestTimeline.epochMs, requestReceivedEpochMs);
requireOrdered(requestReceivedEpochMs, workStartedEpochMs);
requireOrdered(workStartedEpochMs, input.workPhaseStartedEpochMs);
requireOrdered(input.workPhaseStartedEpochMs, workEndedEpochMs);
requireOrdered(workEndedEpochMs, responsePostedEpochMs);
requireOrdered(responsePostedEpochMs, responseEpochMs);
requireOrdered(responseEpochMs, successEpochMs);
return {
mainToDatabaseWorkerMs: difference(
requestReceivedEpochMs,
requestTimeline.epochMs
),
mainToRendererMs: difference(successEpochMs, responseEpochMs),
rendererToMainMs: difference(requestTimeline.epochMs, sourceEpochMs),
successEpochMs,
workerToMainMs: difference(responseEpochMs, responsePostedEpochMs),
};
}
function requireTimelineRecord(
timeline: readonly MainTimelineRecord[],
type: 'db-request' | 'db-response' | 'preload-performance-success',
request: WorkerRequestPerformanceMetrics,
operation: InitialImportDatabaseOperation,
playlistId: string
): MainTimelineRecord {
if (!request.requestId || !Number.isSafeInteger(request.ipcCallId)) {
throw new Error('database-request-identity-missing');
}
const matches = timeline.filter((record) => {
if (
record.type !== type ||
record.operation !== operation ||
record.playlistId !== playlistId
) {
return false;
}
if (type === 'preload-performance-success') {
return (
record.ipcCallId === request.ipcCallId &&
record.sourceEpochMs !== undefined &&
record.success === true
);
}
return (
record.requestId === request.requestId &&
(type !== 'db-response' || record.success === true)
);
});
if (matches.length !== 1 || !matches[0]) {
throw new Error(`database-${type}-timeline-correlation`);
}
const record = matches[0];
if (
type === 'db-request' &&
(record.ipcCallId !== request.ipcCallId ||
record.sourceEpochMs !== request.sourceEpochMs)
) {
throw new Error('database-request-timeline-identity');
}
return record;
}
function requireInitialImportPreloadIdentity(
request: WorkerRequestPerformanceMetrics,
operation: InitialImportDatabaseOperation
): void {
if (
request.operation !== operation ||
!request.success ||
request.performanceCaptureUnavailableReason !== null ||
!Number.isSafeInteger(request.ipcCallId) ||
Number(request.ipcCallId) < 1 ||
typeof request.sourceEpochMs !== 'number' ||
!Number.isFinite(request.sourceEpochMs) ||
request.sourceEpochMs < 0 ||
request.operationId !== null ||
request.operationIdUnavailableReason !==
'preload-performance-marker-uncorrelated'
) {
throw new Error('database-request-preload-identity');
}
}
function requireSource(request: WorkerRequestPerformanceMetrics): number {
return requireTimestamp(request.sourceEpochMs);
}
function requireTimestamp(value: number | null | undefined): number {
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
throw new Error('database-request-timestamp-missing');
}
return value;
}
function requireOrdered(startEpochMs: number, endEpochMs: number): void {
if (
!Number.isFinite(startEpochMs) ||
!Number.isFinite(endEpochMs) ||
endEpochMs < startEpochMs
) {
throw new Error('phase-order');
}
}
function difference(endEpochMs: number, startEpochMs: number): number {
requireOrdered(startEpochMs, endEpochMs);
return endEpochMs - startEpochMs;
}
@@ -0,0 +1,221 @@
import { createWriteStream } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { finished } from 'node:stream/promises';
import type { CDPSession } from '@playwright/test';
interface TraceData {
readonly value: readonly unknown[];
}
export interface RendererHeapUsageSample {
readonly epochMs: number;
readonly usedSize: number;
}
export interface RendererArtifactCapture {
beginTraceOutput(): void;
readonly cpuProfilePath: string | null;
finishTraceOutput(): Promise<void>;
readonly heapSnapshotPath: string | null;
readonly onTraceComplete: () => void;
readonly onTraceData: (data: TraceData) => void;
readonly traceComplete: Promise<void>;
readonly tracePath: string | null;
}
export function createRendererArtifactCapture(options: {
readonly diagnostic: boolean;
readonly outputDirectory: string;
}): RendererArtifactCapture {
const tracePath = options.diagnostic
? join(options.outputDirectory, 'renderer.trace.json')
: null;
const traceOutput = tracePath
? createWriteStream(tracePath, { flags: 'wx', mode: 0o600 })
: null;
let traceOutputOpened = false;
let traceOutputFinish: Promise<void> | null = null;
let traceFirstEvent = true;
let completeTrace: (() => void) | null = null;
const traceComplete = new Promise<void>((resolve) => {
completeTrace = resolve;
});
return {
beginTraceOutput: () => {
if (
traceOutput &&
!traceOutputOpened &&
traceOutputFinish === null
) {
traceOutput.write('{"traceEvents":[');
traceOutputOpened = true;
}
},
cpuProfilePath: options.diagnostic
? join(options.outputDirectory, 'renderer.cpuprofile')
: null,
finishTraceOutput: () => {
traceOutputFinish ??= (async () => {
if (!traceOutput) {
return;
}
if (!traceOutputOpened) {
traceOutput.write('{"traceEvents":[');
traceOutputOpened = true;
}
if (!traceOutput.writableEnded) {
traceOutput.write(']}');
traceOutput.end();
}
await finished(traceOutput);
})();
return traceOutputFinish;
},
heapSnapshotPath: options.diagnostic
? join(options.outputDirectory, 'renderer.heapsnapshot')
: null,
onTraceComplete: () => {
completeTrace?.();
completeTrace = null;
},
onTraceData: (data) => {
if (!traceOutputOpened || traceOutput?.writableEnded) {
return;
}
for (const event of data.value) {
traceOutput?.write(
`${traceFirstEvent ? '' : ','}${JSON.stringify(event)}`
);
traceFirstEvent = false;
}
},
traceComplete,
tracePath,
};
}
export async function startRendererDiagnosticCapture(
session: CDPSession,
artifacts: RendererArtifactCapture
): Promise<void> {
await session.send('Profiler.enable');
await session.send('Profiler.start');
artifacts.beginTraceOutput();
session.on('Tracing.dataCollected', artifacts.onTraceData);
session.on('Tracing.tracingComplete', artifacts.onTraceComplete);
await session.send('Tracing.start', {
categories: [
'blink.user_timing',
'devtools.timeline',
'disabled-by-default-devtools.timeline',
'disabled-by-default-v8.cpu_profiler',
'v8',
].join(','),
options: 'sampling-frequency=1000',
transferMode: 'ReportEvents',
});
}
export async function stopRendererDiagnosticCapture(
session: CDPSession,
artifacts: RendererArtifactCapture
): Promise<void> {
try {
const cpuProfile = await session.send('Profiler.stop');
await writeFile(
requirePath(artifacts.cpuProfilePath, 'renderer CPU profile'),
JSON.stringify(cpuProfile['profile']),
{ encoding: 'utf8', flag: 'wx', mode: 0o600 }
);
await session.send('Tracing.end');
await artifacts.traceComplete;
} finally {
disposeRendererDiagnosticListeners(session, artifacts);
await artifacts.finishTraceOutput();
}
}
export async function disposeRendererDiagnosticCapture(
session: CDPSession,
artifacts: RendererArtifactCapture
): Promise<void> {
disposeRendererDiagnosticListeners(session, artifacts);
await artifacts.finishTraceOutput();
}
export async function takeRendererHeapSnapshot(
session: CDPSession,
path: string | null
): Promise<void> {
const output = createWriteStream(
requirePath(path, 'renderer heap snapshot'),
{ flags: 'wx', mode: 0o600 }
);
const onChunk = ({ chunk }: { readonly chunk: string }): void => {
output.write(chunk);
};
session.on('HeapProfiler.addHeapSnapshotChunk', onChunk);
try {
await session.send('HeapProfiler.takeHeapSnapshot', {
reportProgress: false,
});
} finally {
session.off('HeapProfiler.addHeapSnapshotChunk', onChunk);
output.end();
await finished(output);
}
}
export function selectRendererPeakHeapUsedBytes(
samples: readonly RendererHeapUsageSample[],
measurementStartEpochMs: number,
terminalEpochMs: number
): number {
const inWindow = samples.filter(
({ epochMs, usedSize }) =>
Number.isFinite(epochMs) &&
epochMs >= measurementStartEpochMs &&
epochMs <= terminalEpochMs &&
Number.isFinite(usedSize) &&
usedSize >= 0
);
if (inWindow.length === 0) {
throw new Error('renderer-heap-window-empty');
}
return Math.max(...inWindow.map(({ usedSize }) => usedSize));
}
export function assertRendererMeasurementWindow(input: {
readonly captureCutoffEpochMs: number;
readonly measurementStartEpochMs: number;
readonly operationStartEpochMs: number;
readonly terminalEpochMs: number;
}): void {
const epochs = Object.values(input);
if (
!epochs.every((epoch) => Number.isFinite(epoch) && epoch > 0) ||
input.measurementStartEpochMs < input.operationStartEpochMs ||
input.terminalEpochMs < input.measurementStartEpochMs ||
input.captureCutoffEpochMs < input.terminalEpochMs
) {
throw new Error('renderer-measurement-window-invalid');
}
}
function disposeRendererDiagnosticListeners(
session: CDPSession,
artifacts: RendererArtifactCapture
): void {
session.off('Tracing.dataCollected', artifacts.onTraceData);
session.off('Tracing.tracingComplete', artifacts.onTraceComplete);
artifacts.onTraceComplete();
}
function requirePath(value: string | null, label: string): string {
if (value === null) {
throw new Error(`${label} path is unavailable`);
}
return value;
}
@@ -0,0 +1,313 @@
/* eslint-disable playwright/expect-expect -- This is a Node assertion-based lifecycle test. */
import { RENDERER_PERFORMANCE_PHASE_HOOK_KEY } from '@iptvnator/shared/logging';
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import { startM3uImportRendererCapture } from './m3u-import-renderer-capture';
import { M3U_IMPORT_RENDERER_STATE_KEY } from './m3u-import-renderer-probe';
test('idempotently disposes failed-iteration timers, probe, trace stream, listeners, and CDP session', async () => {
const browser = installFakeProbeEnvironment();
const outputDirectory = await mkdtemp(
join(tmpdir(), 'iptvnator-renderer-capture-dispose-')
);
const session = new FakeCdpSession();
const control = {
click: async () => undefined,
fill: async () => undefined,
first: () => control,
getByRole: () => control,
isDisabled: async () => false,
last: () => control,
waitFor: async () => undefined,
};
const page = {
...browser.page,
context: () => ({
newCDPSession: async () => session,
}),
getByRole: () => control,
locator: () => control,
};
try {
const capture = await startM3uImportRendererCapture(
page as never,
{
diagnostic: true,
outputDirectory,
playlistTitle: 'Synthetic',
playlistUrl:
'http://127.0.0.1:43210/synthetic-performance.m3u',
}
);
assert.equal(session.listenerCount('Tracing.dataCollected'), 1);
assert.equal(session.listenerCount('Tracing.tracingComplete'), 1);
await capture.dispose();
await capture.dispose();
assert.equal(session.detachCount, 1);
assert.equal(session.listenerCount('Tracing.dataCollected'), 0);
assert.equal(session.listenerCount('Tracing.tracingComplete'), 0);
assert.equal(browser.readState(), undefined);
const heapSamplesAfterDispose = session.heapUsageCalls;
await new Promise((resolve) => setTimeout(resolve, 50));
assert.equal(session.heapUsageCalls, heapSamplesAfterDispose);
assert.deepEqual(
JSON.parse(
await readFile(
join(outputDirectory, 'renderer.trace.json'),
'utf8'
)
),
{ traceEvents: [] }
);
} finally {
browser.cleanup();
await rm(outputDirectory, { force: true, recursive: true });
}
});
test('failed-iteration disposal does not await a wedged renderer or CDP detach', async () => {
const browser = installFakeProbeEnvironment();
const outputDirectory = await mkdtemp(
join(tmpdir(), 'iptvnator-renderer-capture-wedged-')
);
const session = new FakeCdpSession({ hangDetach: true });
let evaluateCalls = 0;
const page = createFakePage(browser, session, async (callback, argument) => {
evaluateCalls += 1;
if (evaluateCalls > 1) {
return new Promise<never>(() => undefined);
}
return browser.page.evaluate(callback, argument);
});
try {
const capture = await startM3uImportRendererCapture(page as never, {
diagnostic: false,
outputDirectory,
playlistTitle: 'Synthetic',
playlistUrl:
'http://127.0.0.1:43210/synthetic-performance.m3u',
});
const outcome = await Promise.race([
capture.dispose().then(() => 'disposed'),
new Promise<'timeout'>((resolve) =>
setTimeout(() => resolve('timeout'), 150)
),
]);
assert.equal(outcome, 'disposed');
assert.equal(session.detachCount, 1);
const heapSamplesAfterDispose = session.heapUsageCalls;
await new Promise((resolve) => setTimeout(resolve, 50));
assert.equal(session.heapUsageCalls, heapSamplesAfterDispose);
} finally {
browser.cleanup();
await rm(outputDirectory, { force: true, recursive: true });
}
});
test('rolls back partially started capture when Chromium tracing rejects', async () => {
const browser = installFakeProbeEnvironment();
const outputDirectory = await mkdtemp(
join(tmpdir(), 'iptvnator-renderer-capture-startup-')
);
const session = new FakeCdpSession({ failMethod: 'Tracing.start' });
const intervalToken = Object.freeze({});
const setIntervalDescriptor = Object.getOwnPropertyDescriptor(
globalThis,
'setInterval'
);
const clearIntervalDescriptor = Object.getOwnPropertyDescriptor(
globalThis,
'clearInterval'
);
let clearIntervalCalls = 0;
Object.defineProperty(globalThis, 'setInterval', {
configurable: true,
value: () => intervalToken,
writable: true,
});
Object.defineProperty(globalThis, 'clearInterval', {
configurable: true,
value: (token: unknown) => {
if (token === intervalToken) {
clearIntervalCalls += 1;
}
},
writable: true,
});
try {
await assert.rejects(
startM3uImportRendererCapture(
createFakePage(browser, session) as never,
{
diagnostic: true,
outputDirectory,
playlistTitle: 'Synthetic',
playlistUrl:
'http://127.0.0.1:43210/synthetic-performance.m3u',
}
),
/synthetic Tracing\.start failure/
);
assert.equal(clearIntervalCalls, 1);
assert.equal(session.detachCount, 1);
assert.equal(session.listenerCount('Tracing.dataCollected'), 0);
assert.equal(session.listenerCount('Tracing.tracingComplete'), 0);
assert.equal(browser.readState(), undefined);
assert.deepEqual(
JSON.parse(
await readFile(
join(outputDirectory, 'renderer.trace.json'),
'utf8'
)
),
{ traceEvents: [] }
);
} finally {
restoreGlobal('setInterval', setIntervalDescriptor);
restoreGlobal('clearInterval', clearIntervalDescriptor);
browser.cleanup();
await rm(outputDirectory, { force: true, recursive: true });
}
});
class FakeCdpSession extends EventEmitter {
detachCount = 0;
heapUsageCalls = 0;
constructor(
private readonly options: {
readonly failMethod?: string;
readonly hangDetach?: boolean;
} = {}
) {
super();
}
async detach(): Promise<void> {
this.detachCount += 1;
if (this.options.hangDetach) {
return new Promise<never>(() => undefined);
}
}
async send(method: string): Promise<Record<string, unknown>> {
if (method === this.options.failMethod) {
throw new Error(`synthetic ${method} failure`);
}
if (method === 'Runtime.getHeapUsage') {
this.heapUsageCalls += 1;
return { usedSize: 1_024 };
}
return {};
}
}
function installFakeProbeEnvironment() {
const descriptors = new Map<string, PropertyDescriptor | undefined>();
let nextFrameId = 1;
const replaceGlobal = (key: string, value: unknown): void => {
descriptors.set(key, Object.getOwnPropertyDescriptor(globalThis, key));
Object.defineProperty(globalThis, key, {
configurable: true,
value,
writable: true,
});
};
class FakePerformanceObserver {
static readonly supportedEntryTypes = ['longtask'];
disconnect(): undefined {
return undefined;
}
observe(): undefined {
return undefined;
}
takeRecords(): PerformanceEntry[] {
return [];
}
}
replaceGlobal('performance', { now: () => 0, timeOrigin: 0 });
replaceGlobal('PerformanceObserver', FakePerformanceObserver);
replaceGlobal('cancelAnimationFrame', () => undefined);
replaceGlobal('requestAnimationFrame', () => nextFrameId++);
replaceGlobal('location', { pathname: '/' });
replaceGlobal('document', { querySelector: () => null });
return {
cleanup: () => {
for (const [key, descriptor] of descriptors) {
if (descriptor) {
Object.defineProperty(globalThis, key, descriptor);
} else {
Reflect.deleteProperty(globalThis, key);
}
}
Reflect.deleteProperty(
globalThis,
Symbol.for(RENDERER_PERFORMANCE_PHASE_HOOK_KEY)
);
Reflect.deleteProperty(
globalThis,
M3U_IMPORT_RENDERER_STATE_KEY
);
},
page: {
evaluate: async (
callback: (argument: never) => unknown,
argument: never
) => callback(argument),
},
readState: () =>
(globalThis as Record<string, unknown>)[
M3U_IMPORT_RENDERER_STATE_KEY
],
};
}
function createFakePage(
browser: ReturnType<typeof installFakeProbeEnvironment>,
session: FakeCdpSession,
evaluate = browser.page.evaluate
) {
const control = {
click: async () => undefined,
fill: async () => undefined,
first: () => control,
getByRole: () => control,
isDisabled: async () => false,
last: () => control,
waitFor: async () => undefined,
};
return {
...browser.page,
context: () => ({
newCDPSession: async () => session,
}),
evaluate,
getByRole: () => control,
locator: () => control,
};
}
function restoreGlobal(
key: 'clearInterval' | 'setInterval',
descriptor: PropertyDescriptor | undefined
): void {
if (descriptor) {
Object.defineProperty(globalThis, key, descriptor);
} else {
Reflect.deleteProperty(globalThis, key);
}
}
@@ -0,0 +1,389 @@
/* eslint-disable playwright/expect-expect -- These are Node assertion-based performance contract tests. */
import { RENDERER_PERFORMANCE_PHASE_HOOK_KEY } from '@iptvnator/shared/logging';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
interface ImportProbe {
firstChannelVisibleEpochMs: number | null;
frameGapsMs: readonly number[];
heartbeatDelaysMs: readonly number[];
longTasksMs: readonly number[];
operationStartEpochMs: number;
performancePhaseEvents: readonly unknown[];
playlistId: string | null;
routeReadyEpochMs: number | null;
terminalEpochMs: number | null;
uiPaintedEpochMs: number | null;
}
interface RendererCaptureModule {
advanceM3uImportHeartbeatDeadline?: (
deadlineEpochMs: number,
nowEpochMs: number
) => number;
assertCompleteM3uImportRendererProbe?: (probe: ImportProbe) => void;
startM3uImportRendererCapture?: unknown;
}
interface RendererProbeModule {
M3U_IMPORT_RENDERER_STATE_KEY?: string;
installM3uImportRendererProbe?: (page: unknown) => Promise<void>;
recordM3uImportHeartbeat?: (
page: unknown,
deadlineEpochMs: number
) => Promise<void>;
stopM3uImportRendererProbe?: (page: unknown) => Promise<ImportProbe>;
}
interface FakeProbeState {
frameGapsMs: number[];
heartbeatDelaysMs: number[];
operationStartEpochMs: number;
operationStartMonotonicMs: number;
terminalEpochMs: number | null;
}
const captureUrl = new URL('./m3u-import-renderer-capture.ts', import.meta.url);
const artifactsUrl = new URL(
'./m3u-import-renderer-artifacts.ts',
import.meta.url
);
const probeUrl = new URL('./m3u-import-renderer-probe.ts', import.meta.url);
const captureModulePromise = import(captureUrl.href)
.then((module) => module as RendererCaptureModule)
.catch(() => null);
const probeModulePromise = import(probeUrl.href)
.then((module) => module as unknown as RendererProbeModule)
.catch(() => null);
function installFakeBrowserEnvironment() {
const descriptors = new Map<string, PropertyDescriptor | undefined>();
const frames = new Map<number, (timestamp: number) => void>();
let nextFrameId = 1;
let nowMs = 0;
let observerRecords: PerformanceEntry[] | null = null;
const replaceGlobal = (key: string, value: unknown): void => {
descriptors.set(key, Object.getOwnPropertyDescriptor(globalThis, key));
Object.defineProperty(globalThis, key, {
configurable: true,
value,
writable: true,
});
};
class FakePerformanceObserver {
static readonly supportedEntryTypes = ['longtask'];
constructor() {
observerRecords = [];
}
disconnect(): undefined {
return undefined;
}
observe(): undefined {
return undefined;
}
takeRecords(): PerformanceEntry[] {
return observerRecords?.splice(0) ?? [];
}
}
replaceGlobal('performance', { now: () => nowMs, timeOrigin: 0 });
replaceGlobal('PerformanceObserver', FakePerformanceObserver);
replaceGlobal('cancelAnimationFrame', (requestId: number) => {
frames.delete(requestId);
});
replaceGlobal(
'requestAnimationFrame',
(callback: (timestamp: number) => void) => {
const requestId = nextFrameId++;
frames.set(requestId, callback);
return requestId;
}
);
replaceGlobal('location', { pathname: '/' });
replaceGlobal('document', { querySelector: () => null });
return {
cleanup: () => {
for (const [key, descriptor] of descriptors) {
if (descriptor) {
Object.defineProperty(globalThis, key, descriptor);
} else {
Reflect.deleteProperty(globalThis, key);
}
}
Reflect.deleteProperty(
globalThis,
Symbol.for(RENDERER_PERFORMANCE_PHASE_HOOK_KEY)
);
},
getObserver: () => observerRecords,
page: {
evaluate: async (
callback: (argument: unknown) => unknown,
argument: unknown
) => callback(argument),
},
readState: (stateKey: string) =>
(globalThis as Record<string, unknown>)[stateKey] as FakeProbeState,
runFrame: (timestamp: number) => {
nowMs = timestamp;
for (const [requestId, callback] of [...frames]) {
if (frames.delete(requestId)) {
callback(timestamp);
}
}
},
setNow: (value: number) => {
nowMs = value;
},
showTerminal: () => {
(
globalThis as unknown as {
location: { pathname: string };
}
).location.pathname =
'/Users/test/iptvnator/dist/apps/web/workspace/playlists/synthetic-id/all';
Object.defineProperty(globalThis, 'document', {
configurable: true,
value: { querySelector: () => ({}) },
writable: true,
});
},
};
}
function readSource(url: URL): string | null {
try {
return readFileSync(url, 'utf8');
} catch {
return null;
}
}
function completeProbe(): ImportProbe {
return {
firstChannelVisibleEpochMs: 130,
frameGapsMs: [16, 48],
heartbeatDelaysMs: [0, 12],
longTasksMs: [72],
operationStartEpochMs: 100,
performancePhaseEvents: [],
playlistId: 'synthetic-id',
routeReadyEpochMs: 120,
terminalEpochMs: 150,
uiPaintedEpochMs: 150,
};
}
test('exports the formal M3U import renderer collector', async () => {
const module = await captureModulePromise;
assert.ok(module, 'M3U import renderer capture module must exist');
assert.equal(typeof module.startM3uImportRendererCapture, 'function');
assert.equal(
typeof module.assertCompleteM3uImportRendererProbe,
'function'
);
});
test('prepares the URL dialog before starting any measured capture', () => {
const source = readSource(captureUrl);
assert.ok(source, 'M3U import renderer capture source must exist');
const dialogPreparation = source.indexOf(
'await prepareM3uImportDialog(page, options)'
);
const cdpSession = source.indexOf('newCDPSession(page)');
const initialHeapSample = source.indexOf('await sampleHeap()');
assert.ok(dialogPreparation >= 0);
assert.ok(cdpSession > dialogPreparation);
assert.ok(initialHeapSample > cdpSession);
});
test('captures heap samples in every run and raw CPU, trace, and heap artifacts only in diagnostics', () => {
const source = readSource(captureUrl);
const artifactSource = readSource(artifactsUrl);
assert.ok(source);
assert.ok(artifactSource);
assert.match(source, /setInterval\(\(\) => void sampleHeap\(\), 20\)/);
assert.match(source, /Runtime\.getHeapUsage/);
assert.match(source, /HeapProfiler\.collectGarbage/);
assert.match(
artifactSource,
/cpuProfilePath: options\.diagnostic[\s\S]*renderer\.cpuprofile/
);
assert.match(artifactSource, /Profiler\.start/);
assert.match(artifactSource, /Tracing\.start/);
assert.match(artifactSource, /HeapProfiler\.takeHeapSnapshot/);
});
test('clips drained long tasks and heartbeat samples to the operation window', async () => {
const module = await probeModulePromise;
assert.ok(module);
assert.equal(typeof module.installM3uImportRendererProbe, 'function');
assert.equal(typeof module.recordM3uImportHeartbeat, 'function');
assert.equal(typeof module.stopM3uImportRendererProbe, 'function');
assert.equal(typeof module.M3U_IMPORT_RENDERER_STATE_KEY, 'string');
const browser = installFakeBrowserEnvironment();
try {
await module.installM3uImportRendererProbe?.(browser.page);
const state = browser.readState(
module.M3U_IMPORT_RENDERER_STATE_KEY ?? ''
);
state.operationStartEpochMs = 100;
state.operationStartMonotonicMs = 100;
state.terminalEpochMs = 200;
const observerRecords = browser.getObserver();
assert.ok(observerRecords);
observerRecords.push(
{ duration: 40, startTime: 80 } as PerformanceEntry,
{ duration: 30, startTime: 190 } as PerformanceEntry,
{ duration: 30, startTime: 210 } as PerformanceEntry
);
browser.setNow(250);
await module.recordM3uImportHeartbeat?.(browser.page, 90);
await module.recordM3uImportHeartbeat?.(browser.page, 150);
await module.recordM3uImportHeartbeat?.(browser.page, 210);
const metrics = await module.stopM3uImportRendererProbe?.(browser.page);
assert.deepEqual(metrics?.heartbeatDelaysMs, [50]);
assert.deepEqual(metrics?.longTasksMs, [20, 10]);
} finally {
browser.cleanup();
}
});
test('records the terminal animation-frame gap exactly once', async () => {
const module = await probeModulePromise;
assert.ok(module?.installM3uImportRendererProbe);
assert.equal(typeof module.M3U_IMPORT_RENDERER_STATE_KEY, 'string');
const browser = installFakeBrowserEnvironment();
try {
await module.installM3uImportRendererProbe(browser.page);
browser.runFrame(80);
const state = browser.readState(
module.M3U_IMPORT_RENDERER_STATE_KEY ?? ''
);
state.operationStartEpochMs = 100;
state.operationStartMonotonicMs = 100;
browser.showTerminal();
browser.runFrame(120);
browser.runFrame(140);
browser.runFrame(160);
assert.equal(state.terminalEpochMs, 160);
assert.deepEqual(state.frameGapsMs, [20, 20, 20]);
await module.stopM3uImportRendererProbe?.(browser.page);
} finally {
browser.cleanup();
}
});
test('advances the external heartbeat on its fixed 50ms deadline grid', async () => {
const module = await captureModulePromise;
assert.ok(module);
const advance = module.advanceM3uImportHeartbeatDeadline;
assert.equal(typeof advance, 'function');
assert.equal(advance?.(1_000, 1_005), 1_050);
assert.equal(advance?.(1_000, 1_060), 1_100);
assert.equal(advance?.(1_000, 1_200), 1_250);
});
test('installs the production renderer phase hook and returns its raw events', () => {
const source = readSource(probeUrl);
assert.ok(source);
assert.match(source, /RENDERER_PERFORMANCE_PHASE_HOOK_KEY/);
assert.match(source, /Symbol\.for\(hookKey\)/);
assert.match(source, /performancePhaseEvents\.push\(event\)/);
assert.match(
source,
/performancePhaseEvents: \[\.\.\.state\.performancePhaseEvents\]/
);
assert.match(source, /delete target\[hookSymbol\]/);
});
test('timestamps the click immediately before final Add and requires route, first item, and two animation frames', () => {
const source = readSource(probeUrl);
assert.ok(source);
const operationStart = source.indexOf(
'state.operationStartMonotonicMs = performance.now()'
);
const click = source.indexOf('button.click()', operationStart);
assert.ok(operationStart >= 0);
assert.ok(click > operationStart);
assert.ok(source.includes('/\\/workspace\\/playlists\\/([^/]+)\\/all$/'));
assert.match(source, /\[data-test-id="channel-item"\]/);
const firstPaintFrame = source.indexOf(
'state.uiPaintFrameRequestId = requestAnimationFrame'
);
const secondPaintFrame = source.indexOf(
'state.uiPaintFrameRequestId = requestAnimationFrame',
firstPaintFrame + 1
);
assert.ok(firstPaintFrame >= 0);
assert.ok(secondPaintFrame > firstPaintFrame);
for (const phase of [
'operationStartEpochMs',
'routeReadyEpochMs',
'firstChannelVisibleEpochMs',
'uiPaintedEpochMs',
'terminalEpochMs',
]) {
assert.match(source, new RegExp(phase));
}
});
test('fails closed when any terminal proof or the probe itself is missing', async () => {
const module = await captureModulePromise;
assert.ok(module);
const assertComplete = module.assertCompleteM3uImportRendererProbe;
assert.equal(typeof assertComplete, 'function');
assert.doesNotThrow(() => assertComplete?.(completeProbe()));
for (const field of [
'playlistId',
'routeReadyEpochMs',
'firstChannelVisibleEpochMs',
'uiPaintedEpochMs',
'terminalEpochMs',
] as const) {
const incomplete = { ...completeProbe(), [field]: null };
assert.throws(
() => assertComplete?.(incomplete),
/m3u-import-renderer-probe-incomplete/
);
}
assert.throws(
() =>
assertComplete?.({
...completeProbe(),
operationStartEpochMs: 0,
}),
/m3u-import-renderer-probe-incomplete/
);
});
test('clears the harness heap sampler even when the renderer probe is missing', () => {
const source = readSource(captureUrl);
assert.ok(source);
const backgroundStop = source.indexOf(
'await stopBackgroundWork()',
source.indexOf('const stopCapture')
);
const probeStop = source.indexOf('probe = await stopProbe()', backgroundStop);
assert.match(source, /clearInterval\(heapSampleTimer\)/);
assert.ok(backgroundStop >= 0);
assert.ok(probeStop > backgroundStop);
});
@@ -0,0 +1,366 @@
import type { CDPSession, Page } from '@playwright/test';
import type { NumericDistribution } from './m3u-refresh-cancellation-contract';
import {
createRendererArtifactCapture,
disposeRendererDiagnosticCapture,
type RendererArtifactCapture,
startRendererDiagnosticCapture,
stopRendererDiagnosticCapture,
takeRendererHeapSnapshot,
} from './m3u-import-renderer-artifacts';
import {
installM3uImportRendererProbe,
type M3uImportRendererProbeMetrics,
prepareM3uImportDialog,
recordM3uImportHeartbeat,
stopM3uImportRendererProbe,
triggerM3uImport,
waitForM3uImportTerminal,
} from './m3u-import-renderer-probe';
import { summarizeNumbers } from './performance-statistics';
const HEARTBEAT_INTERVAL_MS = 50;
export interface M3uImportRendererCaptureStartOptions {
readonly diagnostic: boolean;
readonly outputDirectory: string;
readonly playlistTitle: string;
readonly playlistUrl: string;
}
export interface M3uImportRendererPhaseTimestamps {
readonly firstChannelVisibleEpochMs: number;
readonly operationStartEpochMs: number;
readonly routeReadyEpochMs: number;
readonly terminalEpochMs: number;
readonly uiPaintedEpochMs: number;
}
export interface M3uImportRendererCaptureMetrics {
readonly cpuProfilePath: string | null;
readonly frameGap: NumericDistribution;
readonly heapSnapshotPath: string | null;
readonly heartbeatDelay: NumericDistribution;
readonly longTask: NumericDistribution;
readonly peakHeapUsedBytes: number;
readonly phaseTimestamps: M3uImportRendererPhaseTimestamps;
readonly postGcHeapUsedBytes: number;
readonly probe: M3uImportRendererProbeMetrics;
readonly tracePath: string | null;
}
export interface RunningM3uImportRendererCapture {
readonly session: CDPSession;
dispose(): Promise<void>;
stop(): Promise<M3uImportRendererCaptureMetrics>;
triggerImport(): Promise<number>;
waitForTerminal(): Promise<void>;
}
interface HeapUsageResult {
readonly usedSize: number;
}
export async function startM3uImportRendererCapture(
page: Page,
options: M3uImportRendererCaptureStartOptions
): Promise<RunningM3uImportRendererCapture> {
await prepareM3uImportDialog(page, options);
const session = await page.context().newCDPSession(page);
let probeInstalled = false;
let heapSampleTimer: NodeJS.Timeout | null = null;
let artifacts: RendererArtifactCapture | null = null;
const heapSamples: number[] = [];
let heapSampleError: unknown = null;
let heapSamplePromise: Promise<void> | null = null;
const sampleHeap = (): Promise<void> => {
heapSamplePromise ??= (async () => {
try {
const sample = (await session.send(
'Runtime.getHeapUsage'
)) as HeapUsageResult;
if (Number.isFinite(sample.usedSize)) {
heapSamples.push(sample.usedSize);
}
} catch (error) {
heapSampleError ??= error;
} finally {
heapSamplePromise = null;
}
})();
return heapSamplePromise;
};
try {
await session.send('Runtime.enable');
await session.send('Performance.enable');
await installM3uImportRendererProbe(page);
probeInstalled = true;
await sampleHeap();
throwCaptureError(heapSampleError, 'Renderer heap sampling failed');
heapSampleTimer = setInterval(() => void sampleHeap(), 20);
artifacts = createRendererArtifactCapture(options);
if (options.diagnostic) {
await startRendererDiagnosticCapture(session, artifacts);
}
} catch (error) {
if (heapSampleTimer) {
clearInterval(heapSampleTimer);
heapSampleTimer = null;
}
if (artifacts && options.diagnostic) {
await disposeRendererDiagnosticCapture(
session,
artifacts
).catch(() => undefined);
}
if (probeInstalled) {
continueBestEffort(() => stopM3uImportRendererProbe(page));
}
continueBestEffort(() => session.detach());
throw error;
}
if (!artifacts || !heapSampleTimer) {
throw new Error('M3U import renderer capture initialization failed');
}
let heartbeatDeadlineEpochMs: number | null = null;
let heartbeatError: unknown = null;
let heartbeatInFlight: Promise<void> | null = null;
let heartbeatTimer: NodeJS.Timeout | null = null;
let stopped = false;
let backgroundStopPromise: Promise<void> | null = null;
let probeStopPromise: Promise<M3uImportRendererProbeMetrics> | null = null;
let sessionDetachPromise: Promise<void> | null = null;
let abortPromise: Promise<void> | null = null;
let stopPromise: Promise<M3uImportRendererCaptureMetrics> | null = null;
let stopRequested = false;
let disposeRequested = false;
const stopSchedulingBackgroundWork = (): void => {
stopped = true;
if (heartbeatTimer) {
clearTimeout(heartbeatTimer);
heartbeatTimer = null;
}
if (heapSampleTimer) {
clearInterval(heapSampleTimer);
heapSampleTimer = null;
}
};
const scheduleHeartbeat = (): void => {
if (stopped || heartbeatDeadlineEpochMs === null) {
return;
}
const deadline = heartbeatDeadlineEpochMs;
heartbeatTimer = setTimeout(
() => {
heartbeatTimer = null;
heartbeatInFlight = recordM3uImportHeartbeat(page, deadline)
.catch((error: unknown) => {
heartbeatError ??= error;
})
.finally(() => {
heartbeatInFlight = null;
if (stopped) {
return;
}
heartbeatDeadlineEpochMs =
advanceM3uImportHeartbeatDeadline(
deadline,
Date.now()
);
scheduleHeartbeat();
});
},
Math.max(0, deadline - Date.now())
);
};
const stopBackgroundWork = (): Promise<void> => {
backgroundStopPromise ??= (async () => {
stopSchedulingBackgroundWork();
await heartbeatInFlight;
await heapSamplePromise;
await sampleHeap();
})();
return backgroundStopPromise;
};
const stopProbe = (): Promise<M3uImportRendererProbeMetrics> => {
probeStopPromise ??= stopM3uImportRendererProbe(page);
return probeStopPromise;
};
const detachSession = (): Promise<void> => {
sessionDetachPromise ??= session.detach();
return sessionDetachPromise;
};
const abortResources = (): Promise<void> => {
abortPromise ??= (async () => {
stopSchedulingBackgroundWork();
continueBestEffort(() => heartbeatInFlight ?? Promise.resolve());
continueBestEffort(() => heapSamplePromise ?? Promise.resolve());
continueBestEffort(stopProbe);
if (options.diagnostic) {
await disposeRendererDiagnosticCapture(
session,
artifacts
).catch(() => undefined);
}
continueBestEffort(detachSession);
})();
return abortPromise;
};
const stopCapture =
async (): Promise<M3uImportRendererCaptureMetrics> => {
try {
await stopBackgroundWork();
let probe: M3uImportRendererProbeMetrics | null = null;
let probeError: unknown = null;
try {
probe = await stopProbe();
} catch (error) {
probeError = error;
}
if (options.diagnostic) {
await stopRendererDiagnosticCapture(session, artifacts);
}
await session.send('HeapProfiler.enable');
await session.send('HeapProfiler.collectGarbage');
const postGc = (await session.send(
'Runtime.getHeapUsage'
)) as HeapUsageResult;
if (options.diagnostic) {
await takeRendererHeapSnapshot(
session,
artifacts.heapSnapshotPath
);
}
await detachSession();
throwCaptureError(
heapSampleError,
'Renderer heap sampling failed'
);
throwCaptureError(heartbeatError, 'Renderer heartbeat failed');
throwCaptureError(probeError, 'Renderer probe stop failed');
assertCompleteM3uImportRendererProbe(probe);
return Object.freeze({
cpuProfilePath: artifacts.cpuProfilePath,
frameGap: summarizeNumbers(probe.frameGapsMs),
heapSnapshotPath: artifacts.heapSnapshotPath,
heartbeatDelay: summarizeNumbers(
probe.heartbeatDelaysMs
),
longTask: summarizeNumbers(probe.longTasksMs),
peakHeapUsedBytes: Math.max(0, ...heapSamples),
phaseTimestamps: Object.freeze({
firstChannelVisibleEpochMs:
probe.firstChannelVisibleEpochMs,
operationStartEpochMs: probe.operationStartEpochMs,
routeReadyEpochMs: probe.routeReadyEpochMs,
terminalEpochMs: probe.terminalEpochMs,
uiPaintedEpochMs: probe.uiPaintedEpochMs,
}),
postGcHeapUsedBytes: postGc.usedSize,
probe,
tracePath: artifacts.tracePath,
});
} catch (error) {
await abortResources();
throw error;
}
};
return Object.freeze({
dispose: async () => {
disposeRequested = true;
if (stopPromise) {
continueBestEffort(() => stopPromise as Promise<unknown>);
}
await abortResources();
},
session,
stop: () => {
if (stopRequested || disposeRequested) {
return Promise.reject(
new Error('M3U import renderer capture already stopped')
);
}
stopRequested = true;
stopPromise = stopCapture();
return stopPromise;
},
triggerImport: async () => {
const operationStartEpochMs = await triggerM3uImport(page);
heartbeatDeadlineEpochMs =
operationStartEpochMs + HEARTBEAT_INTERVAL_MS;
scheduleHeartbeat();
return operationStartEpochMs;
},
waitForTerminal: () => waitForM3uImportTerminal(page),
});
}
export function advanceM3uImportHeartbeatDeadline(
deadlineEpochMs: number,
nowEpochMs: number
): number {
const nextDeadline = deadlineEpochMs + HEARTBEAT_INTERVAL_MS;
if (nextDeadline > nowEpochMs) {
return nextDeadline;
}
const missedIntervals =
Math.floor((nowEpochMs - nextDeadline) / HEARTBEAT_INTERVAL_MS) + 1;
return nextDeadline + missedIntervals * HEARTBEAT_INTERVAL_MS;
}
export function assertCompleteM3uImportRendererProbe(
probe: M3uImportRendererProbeMetrics | null | undefined
): asserts probe is M3uImportRendererProbeMetrics & {
readonly firstChannelVisibleEpochMs: number;
readonly playlistId: string;
readonly routeReadyEpochMs: number;
readonly terminalEpochMs: number;
readonly uiPaintedEpochMs: number;
} {
if (
!probe ||
typeof probe.playlistId !== 'string' ||
probe.playlistId.length === 0 ||
!isPositiveFinite(probe.operationStartEpochMs) ||
!isPositiveFinite(probe.routeReadyEpochMs) ||
!isPositiveFinite(probe.firstChannelVisibleEpochMs) ||
!isPositiveFinite(probe.uiPaintedEpochMs) ||
!isPositiveFinite(probe.terminalEpochMs)
) {
throw new Error('m3u-import-renderer-probe-incomplete');
}
if (
probe.routeReadyEpochMs < probe.operationStartEpochMs ||
probe.firstChannelVisibleEpochMs < probe.routeReadyEpochMs ||
probe.uiPaintedEpochMs < probe.firstChannelVisibleEpochMs ||
probe.terminalEpochMs < probe.uiPaintedEpochMs
) {
throw new Error('m3u-import-renderer-probe-incomplete');
}
}
function throwCaptureError(error: unknown, label: string): void {
if (error instanceof Error) {
throw error;
}
if (error !== null) {
throw new Error(`${label}: ${String(error)}`);
}
}
function continueBestEffort(work: () => Promise<unknown>): void {
try {
void work().catch(() => undefined);
} catch {
// The owning Electron app is closed immediately after abort cleanup.
}
}
function isPositiveFinite(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value) && value > 0;
}
@@ -0,0 +1,378 @@
import type { Page } from '@playwright/test';
import {
RENDERER_PERFORMANCE_PHASE_HOOK_KEY,
type RendererPerformancePhaseEvent,
type RendererPerformancePhaseHook,
} from '@iptvnator/shared/logging';
export const M3U_IMPORT_RENDERER_STATE_KEY =
'__iptvnatorM3uImportRendererCapture';
export interface M3uImportDialogOptions {
readonly playlistTitle: string;
readonly playlistUrl: string;
}
export interface M3uImportRendererProbeMetrics {
readonly firstChannelVisibleEpochMs: number | null;
readonly frameGapsMs: readonly number[];
readonly heartbeatDelaysMs: readonly number[];
readonly longTasksMs: readonly number[];
readonly operationStartEpochMs: number;
readonly performancePhaseEvents: readonly RendererPerformancePhaseEvent[];
readonly playlistId: string | null;
readonly routeReadyEpochMs: number | null;
readonly terminalEpochMs: number | null;
readonly uiPaintedEpochMs: number | null;
}
export async function prepareM3uImportDialog(
page: Page,
options: M3uImportDialogOptions
): Promise<void> {
await page.getByRole('button', { name: 'Add playlist' }).first().click();
const dialog = page.locator('mat-dialog-container').last();
await dialog.waitFor({ state: 'visible' });
await dialog
.getByRole('textbox', { name: 'Playlist URL (m3u, m3u8)' })
.fill(options.playlistUrl);
await dialog
.getByRole('textbox', { name: 'Playlist title' })
.fill(options.playlistTitle);
const submit = dialog.getByRole('button', { name: 'Add playlist' }).last();
await submit.waitFor({ state: 'visible' });
if (await submit.isDisabled()) {
throw new Error('m3u-import-final-add-disabled');
}
}
export async function installM3uImportRendererProbe(page: Page): Promise<void> {
await page.evaluate(
({ hookKey, stateKey }) => {
interface ProbeState {
firstChannelVisibleEpochMs: number | null;
frameGapsMs: number[];
frameRequestId: number;
heartbeatDelaysMs: number[];
lastFrameAt: number | null;
longTaskObserver: PerformanceObserver | null;
longTasksMs: number[];
operationStartEpochMs: number;
operationStartMonotonicMs: number;
performancePhaseEvents: RendererPerformancePhaseEvent[];
playlistId: string | null;
recordFrameGap(timestamp: number): void;
recordLongTask(entry: PerformanceEntry): void;
routeReadyEpochMs: number | null;
terminalEpochMs: number | null;
uiPaintFrameRequestId: number | null;
uiPaintedEpochMs: number | null;
}
const target = globalThis as unknown as Record<
PropertyKey,
unknown
>;
const hookSymbol = Symbol.for(hookKey);
if (
target[stateKey] !== undefined ||
target[hookSymbol] !== undefined
) {
throw new Error('m3u-import-renderer-probe-already-installed');
}
const epoch = (): number =>
performance.timeOrigin + performance.now();
const state: ProbeState = {
firstChannelVisibleEpochMs: null,
frameGapsMs: [],
frameRequestId: 0,
heartbeatDelaysMs: [],
lastFrameAt: null,
longTaskObserver: null,
longTasksMs: [],
operationStartEpochMs: 0,
operationStartMonotonicMs: 0,
performancePhaseEvents: [],
playlistId: null,
recordFrameGap: (timestamp) => {
const previousFrameAt = state.lastFrameAt;
state.lastFrameAt = timestamp;
if (
previousFrameAt === null ||
state.operationStartEpochMs <= 0 ||
state.terminalEpochMs !== null
) {
return;
}
const measuredFrameStart = Math.max(
previousFrameAt,
state.operationStartMonotonicMs
);
if (timestamp > measuredFrameStart) {
state.frameGapsMs.push(timestamp - measuredFrameStart);
}
},
recordLongTask: (entry) => {
const entryEpochMs =
performance.timeOrigin + entry.startTime;
const measuredStartEpochMs = Math.max(
entryEpochMs,
state.operationStartEpochMs
);
const measuredEndEpochMs = Math.min(
entryEpochMs + entry.duration,
state.terminalEpochMs ?? Number.POSITIVE_INFINITY
);
if (
state.operationStartEpochMs > 0 &&
measuredEndEpochMs > measuredStartEpochMs
) {
state.longTasksMs.push(
measuredEndEpochMs - measuredStartEpochMs
);
}
},
routeReadyEpochMs: null,
terminalEpochMs: null,
uiPaintFrameRequestId: null,
uiPaintedEpochMs: null,
};
const performancePhaseHook: RendererPerformancePhaseHook = (
event
) => {
state.performancePhaseEvents.push(event);
};
target[hookSymbol] = performancePhaseHook;
const terminalTarget = (): string | null => {
const route = location.pathname.match(
/\/workspace\/playlists\/([^/]+)\/all$/
);
if (
!route ||
!document.querySelector('[data-test-id="channel-item"]')
) {
return null;
}
return route[1] ?? null;
};
const inspectTerminal = (): void => {
if (
state.operationStartEpochMs <= 0 ||
state.terminalEpochMs !== null
) {
return;
}
const route = location.pathname.match(
/\/workspace\/playlists\/([^/]+)\/all$/
);
if (!route) {
return;
}
state.routeReadyEpochMs ??= epoch();
if (!document.querySelector('[data-test-id="channel-item"]')) {
return;
}
state.firstChannelVisibleEpochMs ??= epoch();
if (state.uiPaintFrameRequestId !== null) {
return;
}
state.uiPaintFrameRequestId = requestAnimationFrame(() => {
state.uiPaintFrameRequestId = requestAnimationFrame(
(terminalFrameAt) => {
state.uiPaintFrameRequestId = null;
const playlistId = terminalTarget();
if (playlistId === null) {
return;
}
state.recordFrameGap(terminalFrameAt);
state.playlistId = playlistId;
state.uiPaintedEpochMs = epoch();
state.terminalEpochMs = state.uiPaintedEpochMs;
cancelAnimationFrame(state.frameRequestId);
state.frameRequestId = 0;
for (const entry of state.longTaskObserver?.takeRecords() ??
[]) {
state.recordLongTask(entry);
}
state.longTaskObserver?.disconnect();
state.longTaskObserver = null;
}
);
});
};
const frame = (timestamp: number): void => {
state.recordFrameGap(timestamp);
if (state.operationStartEpochMs > 0) {
inspectTerminal();
}
state.frameRequestId =
state.terminalEpochMs === null
? requestAnimationFrame(frame)
: 0;
};
state.frameRequestId = requestAnimationFrame(frame);
if (
typeof PerformanceObserver === 'function' &&
PerformanceObserver.supportedEntryTypes.includes('longtask')
) {
state.longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
state.recordLongTask(entry);
}
});
state.longTaskObserver.observe({ entryTypes: ['longtask'] });
}
target[stateKey] = state;
},
{
hookKey: RENDERER_PERFORMANCE_PHASE_HOOK_KEY,
stateKey: M3U_IMPORT_RENDERER_STATE_KEY,
}
);
}
export async function triggerM3uImport(page: Page): Promise<number> {
return page.evaluate((stateKey) => {
const target = globalThis as unknown as Record<string, unknown>;
const state = target[stateKey] as
| {
operationStartEpochMs: number;
operationStartMonotonicMs: number;
}
| undefined;
if (!state || state.operationStartEpochMs > 0) {
throw new Error('m3u-import-renderer-probe-unavailable');
}
const dialogs = Array.from(
document.querySelectorAll('mat-dialog-container')
);
const dialog = dialogs.at(-1);
const button = Array.from(dialog?.querySelectorAll('button') ?? [])
.reverse()
.find(
(candidate) =>
candidate.textContent?.trim() === 'Add playlist' &&
!candidate.disabled
);
if (!(button instanceof HTMLButtonElement)) {
throw new Error('m3u-import-final-add-unavailable');
}
state.operationStartMonotonicMs = performance.now();
state.operationStartEpochMs =
performance.timeOrigin + state.operationStartMonotonicMs;
button.click();
return state.operationStartEpochMs;
}, M3U_IMPORT_RENDERER_STATE_KEY);
}
export async function recordM3uImportHeartbeat(
page: Page,
deadlineEpochMs: number
): Promise<void> {
await page.evaluate(
({ deadlineEpochMs, stateKey }) => {
const target = globalThis as unknown as Record<string, unknown>;
const state = target[stateKey] as
| {
heartbeatDelaysMs: number[];
operationStartEpochMs: number;
terminalEpochMs: number | null;
}
| undefined;
if (!state || state.operationStartEpochMs <= 0) {
throw new Error('m3u-import-renderer-probe-unavailable');
}
if (
deadlineEpochMs >= state.operationStartEpochMs &&
(state.terminalEpochMs === null ||
deadlineEpochMs <= state.terminalEpochMs)
) {
const receivedEpochMs =
performance.timeOrigin + performance.now();
const measuredEpochMs =
state.terminalEpochMs === null
? receivedEpochMs
: Math.min(receivedEpochMs, state.terminalEpochMs);
state.heartbeatDelaysMs.push(
Math.max(0, measuredEpochMs - deadlineEpochMs)
);
}
},
{ deadlineEpochMs, stateKey: M3U_IMPORT_RENDERER_STATE_KEY }
);
}
export async function waitForM3uImportTerminal(page: Page): Promise<void> {
await page
.waitForFunction(
(stateKey) => {
const target = globalThis as unknown as Record<string, unknown>;
const state = target[stateKey] as
{ terminalEpochMs: number | null } | undefined;
return typeof state?.terminalEpochMs === 'number';
},
M3U_IMPORT_RENDERER_STATE_KEY,
{ timeout: 120_000 }
)
.then(() => undefined);
}
export async function stopM3uImportRendererProbe(
page: Page
): Promise<M3uImportRendererProbeMetrics> {
return page.evaluate(
({ hookKey, stateKey }) => {
const target = globalThis as unknown as Record<
PropertyKey,
unknown
>;
const hookSymbol = Symbol.for(hookKey);
const state = target[stateKey] as
| {
firstChannelVisibleEpochMs: number | null;
frameGapsMs: number[];
frameRequestId: number;
heartbeatDelaysMs: number[];
longTaskObserver: PerformanceObserver | null;
longTasksMs: number[];
operationStartEpochMs: number;
performancePhaseEvents: RendererPerformancePhaseEvent[];
playlistId: string | null;
recordLongTask(entry: PerformanceEntry): void;
routeReadyEpochMs: number | null;
terminalEpochMs: number | null;
uiPaintFrameRequestId: number | null;
uiPaintedEpochMs: number | null;
}
| undefined;
if (!state) {
throw new Error('m3u-import-renderer-probe-unavailable');
}
cancelAnimationFrame(state.frameRequestId);
if (state.uiPaintFrameRequestId !== null) {
cancelAnimationFrame(state.uiPaintFrameRequestId);
}
for (const entry of state.longTaskObserver?.takeRecords() ?? []) {
state.recordLongTask(entry);
}
state.longTaskObserver?.disconnect();
delete target[stateKey];
delete target[hookSymbol];
return {
firstChannelVisibleEpochMs: state.firstChannelVisibleEpochMs,
frameGapsMs: [...state.frameGapsMs],
heartbeatDelaysMs: [...state.heartbeatDelaysMs],
longTasksMs: [...state.longTasksMs],
operationStartEpochMs: state.operationStartEpochMs,
performancePhaseEvents: [...state.performancePhaseEvents],
playlistId: state.playlistId,
routeReadyEpochMs: state.routeReadyEpochMs,
terminalEpochMs: state.terminalEpochMs,
uiPaintedEpochMs: state.uiPaintedEpochMs,
};
},
{
hookKey: RENDERER_PERFORMANCE_PHASE_HOOK_KEY,
stateKey: M3U_IMPORT_RENDERER_STATE_KEY,
}
);
}
@@ -0,0 +1,338 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import type { MainCaptureMetrics } from './m3u-refresh-cancellation-contract';
import { deriveM3uImportPhaseMetrics } from './m3u-import-report';
import {
createTestMainCapture,
createTestRendererCapture,
TEST_M3U_CHANNEL_COUNT,
TEST_M3U_FIXTURE_BYTES,
} from './m3u-import-report.test-helpers';
describe('formal M3U import phase report', () => {
it('attributes every required phase without presenting indexes as zero work', () => {
const phases = deriveM3uImportPhaseMetrics({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
main: createTestMainCapture(),
renderer: createTestRendererCapture(),
});
assert.deepEqual(phases, {
angularRenderingMs: 10,
dataAcquireMs: 20,
databaseWorkerToMainCloneProxyMs: 12,
indexAndCommitMs: null,
indexAndCommitUnavailableReason:
'indexes-not-created-during-import;sqlite-autocommit-included-in-sqlite-write',
ipcStructuredCloneProxyMs: 44,
mainToDatabaseWorkerCloneProxyMs: 2,
mainToRendererCloneProxyMs: 28,
m3uParsingMs: 40,
normalizationMs: 10,
playlistDeserializationMs: 12,
playlistSerializationMs: 20,
rendererToMainCloneProxyMs: 2,
sqliteReadMs: 5,
sqliteWriteMs: 30,
storeImportDispatchMs: 2,
storePublishChannelsMs: 10,
storeUpdateMs: 12,
totalMs: 215,
});
assert.equal(
phases.ipcStructuredCloneProxyMs,
phases.mainToRendererCloneProxyMs +
phases.rendererToMainCloneProxyMs +
phases.mainToDatabaseWorkerCloneProxyMs +
phases.databaseWorkerToMainCloneProxyMs
);
});
it('fails closed when any structured-clone boundary is missing, ambiguous, or out of order', () => {
const main = createTestMainCapture();
const databaseWorker = main.workers[0];
const upsert = databaseWorker?.requests[0];
assert.ok(databaseWorker);
assert.ok(upsert);
for (const invalidMain of [
{
...main,
timeline: main.timeline.filter(
(event) => event.type !== 'db-request'
),
},
{
...main,
timeline: [
...main.timeline,
main.timeline.find((event) => event.type === 'db-request'),
].filter(
(event): event is (typeof main.timeline)[number] =>
event !== undefined
),
},
{
...main,
workers: [
{
...databaseWorker,
requests: [
{
...upsert,
sourceEpochMs: 1080,
},
...databaseWorker.requests.slice(1),
],
},
],
},
{
...main,
workers: [
{
...databaseWorker,
requests: [
{
...upsert,
requestReceivedEpochMs: 1078,
},
...databaseWorker.requests.slice(1),
],
},
],
},
{
...main,
workers: [
{
...databaseWorker,
requests: [
{
...upsert,
ipcCallId: null,
operationIdUnavailableReason:
'preload-performance-marker-missing',
sourceEpochMs: null,
},
...databaseWorker.requests.slice(1),
],
},
],
},
{
...main,
workers: [
{
...databaseWorker,
requests: [
{
...upsert,
workStartedEpochMs: 1079,
},
...databaseWorker.requests.slice(1),
],
},
],
},
]) {
assert.throws(
() =>
deriveM3uImportPhaseMetrics({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
main: invalidMain,
renderer: createTestRendererCapture(),
}),
/m3u-import-phase-attribution-invalid/
);
}
});
it('fails closed when a required phase, worker identity, or fixture count is ambiguous', () => {
const missingNormalize = createTestMainCapture();
const missingTimeline = missingNormalize.timeline.filter(
(event) => event.phase !== 'normalize'
);
assert.throws(
() =>
deriveM3uImportPhaseMetrics({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
main: {
...missingNormalize,
timeline: missingTimeline,
},
renderer: createTestRendererCapture(),
}),
/m3u-import-phase-attribution-invalid/
);
assert.throws(
() =>
deriveM3uImportPhaseMetrics({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
main: {
...createTestMainCapture(),
workers: [
...createTestMainCapture().workers,
{
...createTestMainCapture().workers[0],
ordinal: 2,
},
],
},
renderer: createTestRendererCapture(),
}),
/m3u-import-phase-attribution-invalid/
);
const wrongBytes = createTestMainCapture();
assert.throws(
() =>
deriveM3uImportPhaseMetrics({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES + 1,
main: wrongBytes,
renderer: createTestRendererCapture(),
}),
/m3u-import-phase-attribution-invalid/
);
});
it('requires the route-reload GET and both worker-to-renderer return paths', () => {
const main = createTestMainCapture();
const databaseWorker = main.workers[0];
const upsert = databaseWorker?.requests[0];
const get = databaseWorker?.requests[1];
assert.ok(databaseWorker);
assert.ok(upsert);
assert.ok(get);
const invalidCaptures: MainCaptureMetrics[] = [
{
...main,
workers: [
{
...databaseWorker,
requests: databaseWorker.requests.slice(0, 1),
},
],
},
{
...main,
timeline: main.timeline.filter(
(event) =>
!(
event.type === 'preload-performance-success' &&
event.operation === 'DB_GET_APP_PLAYLIST'
)
),
},
{
...main,
timeline: main.timeline.filter(
(event) =>
!(
event.type === 'preload-performance-success' &&
event.operation === 'DB_UPSERT_APP_PLAYLIST'
)
),
},
{
...main,
timeline: main.timeline.map((event) =>
event.type === 'preload-performance-success' &&
event.operation === 'DB_GET_APP_PLAYLIST'
? {
...event,
sourceEpochMs: 1196,
}
: event
),
},
{
...main,
workers: [
{
...databaseWorker,
requests: [
upsert,
{
...get,
responsePostedEpochMs: 1175,
},
],
},
],
},
];
for (const invalidMain of invalidCaptures) {
assert.throws(
() =>
deriveM3uImportPhaseMetrics({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
main: invalidMain,
renderer: createTestRendererCapture(),
}),
/m3u-import-phase-attribution-invalid/
);
}
});
it('rejects playlist workers and cross-playlist database writes', () => {
assert.throws(
() =>
deriveM3uImportPhaseMetrics({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
main: {
...createTestMainCapture(),
workers: [
...createTestMainCapture().workers,
{
kind: 'playlist-refresh.worker',
requests: [],
},
],
} as MainCaptureMetrics,
renderer: createTestRendererCapture(),
}),
/m3u-import-phase-attribution-invalid/
);
const main = createTestMainCapture();
const databaseWorker = main.workers[0];
assert.ok(databaseWorker);
const upsert = databaseWorker.requests[0];
assert.ok(upsert);
assert.throws(
() =>
deriveM3uImportPhaseMetrics({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
main: {
...main,
workers: [
{
...databaseWorker,
requests: [
{
...upsert,
playlistId: 'other-playlist',
},
],
},
],
},
renderer: createTestRendererCapture(),
}),
/m3u-import-phase-attribution-invalid/
);
});
});
@@ -0,0 +1,458 @@
/* eslint-disable max-lines -- The deterministic fixture keeps the full production-shaped import sequence visible. */
import type { M3uImportRendererCaptureMetrics } from './m3u-import-renderer-capture';
import type { MainCaptureMetrics } from './m3u-refresh-cancellation-contract';
import type { M3uImportBenchmarkManifest } from './m3u-import-summary';
export const TEST_M3U_CHANNEL_COUNT = 10_000;
export const TEST_M3U_FIXTURE_BYTES = 1_024_000;
export function createTestMainCapture(): MainCaptureMetrics {
return {
timeline: [
mainPhase('start', 'data.acquire', 1001, null),
mainPhase('end', 'data.acquire', 1021, 20, {
byteCount: TEST_M3U_FIXTURE_BYTES,
}),
mainPhase('start', 'parse.m3u', 1021, null),
mainPhase('end', 'parse.m3u', 1061, 40, {
itemCount: TEST_M3U_CHANNEL_COUNT,
}),
mainPhase('start', 'normalize', 1061, null),
mainPhase('end', 'normalize', 1071, 10, {
itemCount: TEST_M3U_CHANNEL_COUNT,
}),
{
epochMs: 1080,
ipcCallId: 7,
operation: 'DB_UPSERT_APP_PLAYLIST',
playlistId: 'playlist-1',
requestId: 'db-request-1',
sourceEpochMs: 1079,
type: 'db-request',
},
{
epochMs: 1137,
operation: 'DB_UPSERT_APP_PLAYLIST',
playlistId: 'playlist-1',
requestId: 'db-request-1',
success: true,
type: 'db-response',
},
{
epochMs: 1140,
ipcCallId: 7,
operation: 'DB_UPSERT_APP_PLAYLIST',
playlistId: 'playlist-1',
sourceEpochMs: 1140,
success: true,
type: 'preload-performance-success',
},
{
epochMs: 1142,
ipcCallId: 8,
operation: 'DB_GET_APP_PLAYLIST',
playlistId: 'playlist-1',
requestId: 'db-request-2',
sourceEpochMs: 1141,
type: 'db-request',
},
{
epochMs: 1174,
operation: 'DB_GET_APP_PLAYLIST',
playlistId: 'playlist-1',
requestId: 'db-request-2',
success: true,
type: 'db-response',
},
{
epochMs: 1194,
ipcCallId: 8,
operation: 'DB_GET_APP_PLAYLIST',
playlistId: 'playlist-1',
sourceEpochMs: 1194,
success: true,
type: 'preload-performance-success',
},
],
workers: [
{
kind: 'database.worker',
requests: [
{
ipcCallId: 7,
operation: 'DB_UPSERT_APP_PLAYLIST',
operationId: null,
operationIdUnavailableReason:
'preload-performance-marker-uncorrelated',
performanceCaptureUnavailableReason: null,
phaseEvents: [
workerPhase(
'start',
'serialize.playlist',
1082,
null,
'db-request-1'
),
workerPhase(
'end',
'serialize.playlist',
1102,
20,
'db-request-1',
{
itemCount: TEST_M3U_CHANNEL_COUNT,
}
),
workerPhase(
'start',
'sqlite.write',
1102,
null,
'db-request-1'
),
workerPhase(
'end',
'sqlite.write',
1132,
30,
'db-request-1',
{
itemCount: 1,
}
),
],
playlistId: 'playlist-1',
requestId: 'db-request-1',
requestReceivedEpochMs: 1081,
responseEpochMs: 1137,
responsePostedEpochMs: 1135,
sourceEpochMs: 1079,
success: true,
workEndedEpochMs: 1133,
workStartedEpochMs: 1082,
},
{
ipcCallId: 8,
operation: 'DB_GET_APP_PLAYLIST',
operationId: null,
operationIdUnavailableReason:
'preload-performance-marker-uncorrelated',
performanceCaptureUnavailableReason: null,
phaseEvents: [
workerPhase(
'start',
'sqlite.read',
1144,
null,
'db-request-2'
),
workerPhase(
'end',
'sqlite.read',
1149,
5,
'db-request-2',
{
itemCount: 1,
}
),
workerPhase(
'start',
'deserialize.playlist',
1149,
null,
'db-request-2'
),
workerPhase(
'end',
'deserialize.playlist',
1161,
12,
'db-request-2',
{
itemCount: TEST_M3U_CHANNEL_COUNT,
}
),
],
playlistId: 'playlist-1',
requestId: 'db-request-2',
requestReceivedEpochMs: 1143,
responseEpochMs: 1174,
responsePostedEpochMs: 1164,
sourceEpochMs: 1141,
success: true,
workEndedEpochMs: 1162,
workStartedEpochMs: 1144,
},
],
},
],
} as unknown as MainCaptureMetrics;
}
export function createTestRendererCapture(): M3uImportRendererCaptureMetrics {
return {
phaseTimestamps: {
firstChannelVisibleEpochMs: 1210,
operationStartEpochMs: 1000,
routeReadyEpochMs: 1140,
terminalEpochMs: 1215,
uiPaintedEpochMs: 1215,
},
probe: {
performancePhaseEvents: [
rendererPhase(
'start',
'store.m3u-import-dispatch',
'renderer-1',
1076,
76
),
rendererPhase(
'end',
'store.m3u-import-dispatch',
'renderer-1',
1078,
78
),
rendererPhase(
'start',
'store.m3u-publish-channels',
'renderer-2',
1195,
195,
TEST_M3U_CHANNEL_COUNT
),
rendererPhase(
'end',
'store.m3u-publish-channels',
'renderer-2',
1205,
205,
TEST_M3U_CHANNEL_COUNT
),
],
playlistId: 'playlist-1',
},
} as unknown as M3uImportRendererCaptureMetrics;
}
export function createCompleteTestMainCapture(
overrides: { mainRss?: number; rendererRss?: number } = {}
): MainCaptureMetrics {
const base = createTestMainCapture();
const databaseWorker = base.workers[0];
const upsert = databaseWorker?.requests[0];
const get = databaseWorker?.requests[1];
if (!databaseWorker || !upsert || !get) {
throw new Error('invalid-test-main-capture');
}
return {
...base,
cpuProfilePath: null,
cpuSystemMicros: 20,
cpuUserMicros: 30,
eventLoopDelay: { maxMs: 3, p95Ms: 1, p99Ms: 2 },
eventLoopUtilization: null,
eventLoopUtilizationUnavailableReason:
'electron-main-embedded-event-loop',
heapSnapshotPath: null,
memory: {
peakHeapUsedBytes: 2_000,
peakRssBytes: overrides.mainRss ?? 5_000,
postGcHeapUsedBytes: 1_000,
postGcRssBytes: 4_500,
},
rendererWindow: {
responsiveEvents: 0,
rss: {
identity: { creationTime: 100, pid: 42 },
missingSampleCount: 0,
peakRssBytes: overrides.rendererRss ?? 4_000,
unavailableReason: null,
validSampleCount: 5,
},
unresponsiveEvents: 0,
windowIdentity: {
browserWindowId: 1,
webContentsId: 2,
},
},
rssScope:
'electron-main-process-including-worker-threads-and-native-memory',
workers: [
{
...databaseWorker,
externalMemorySampleCount: 4,
heapUsedSampleCount: 4,
peakExternalBytes: 600,
peakExternalUnavailableReason: null,
peakHeapUsedBytes: 1_500,
peakHeapUsedUnavailableReason: null,
postGcHeapUnavailableReason: null,
postGcHeapUsedBytes: 900,
requests: [
{
...upsert,
eventLoopDelay: { maxMs: 6, p95Ms: 4, p99Ms: 5 },
eventLoopDelayUnavailableReason: null,
eventLoopUtilization: 0.8,
eventLoopUtilizationUnavailableReason: null,
histogramFlushedEpochMs: 1134,
invalidReason: null,
threadCpuSystemMicros: 40,
threadCpuUnavailableReason: null,
threadCpuUserMicros: 50,
},
{
...get,
eventLoopDelay: { maxMs: 9, p95Ms: 7, p99Ms: 8 },
eventLoopDelayUnavailableReason: null,
eventLoopUtilization: 0.9,
eventLoopUtilizationUnavailableReason: null,
histogramFlushedEpochMs: 1163,
invalidReason: null,
threadCpuSystemMicros: 60,
threadCpuUnavailableReason: null,
threadCpuUserMicros: 70,
},
],
},
],
};
}
export function createCompleteTestRendererCapture(
overrides: { rendererHeap?: number } = {}
): M3uImportRendererCaptureMetrics {
const base = createTestRendererCapture();
return {
...base,
cpuProfilePath: null,
frameGap: distribution([16, 40]),
heapSnapshotPath: null,
heartbeatDelay: distribution([0, 5]),
longTask: distribution([60]),
peakHeapUsedBytes: overrides.rendererHeap ?? 3_000,
postGcHeapUsedBytes: 1_100,
probe: {
...base.probe,
firstChannelVisibleEpochMs: 1210,
frameGapsMs: [16, 40],
heartbeatDelaysMs: [0, 5],
longTasksMs: [60],
operationStartEpochMs: 1000,
routeReadyEpochMs: 1140,
terminalEpochMs: 1215,
uiPaintedEpochMs: 1215,
},
tracePath: null,
};
}
export function createTestM3uImportManifest(): M3uImportBenchmarkManifest {
return {
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
fixtureSha256: 'fixture-sha',
gitCommit: 'commit',
gitDiffSha256: 'diff',
gitDirty: false,
measuredRuns: 1,
memoryAccounting: {
databaseWorkerRss: 'unavailable-per-worker-thread',
mainRss:
'electron-main-process-including-worker-threads-and-native-memory',
nativeAndSqliteMemory:
'included-in-main-rss-not-separately-attributable',
rendererRss: 'separate-renderer-process',
workerJsHeap: 'separate-v8-isolate',
},
runtime: {
arch: 'arm64',
cdpPort: 9222,
electronVersion: '1',
harnessNodeVersion: '1',
platform: 'darwin',
},
scenario: 'm3u-import-10k',
syntheticSeed: 240_724,
variant: 'baseline',
warmupRuns: 1,
};
}
function mainPhase(
boundary: 'end' | 'start',
phase: string,
epochMs: number,
durationMs: number | null,
metadata: { byteCount?: number; itemCount?: number } = {}
) {
return {
boundary,
durationMs,
epochMs,
...metadata,
phase,
requestId: 'main-request-1',
type: 'm3u-import-phase',
};
}
function workerPhase(
boundary: 'end' | 'start',
phase:
| 'deserialize.playlist'
| 'serialize.playlist'
| 'sqlite.read'
| 'sqlite.write',
epochMs: number,
durationMs: number | null,
requestId: string,
metadata?: { byteCount?: number; itemCount?: number }
) {
return {
boundary,
durationMs,
epochMs,
...(metadata ? { metadata } : {}),
phase,
requestId,
};
}
function rendererPhase(
boundary: 'end' | 'start',
phase: 'store.m3u-import-dispatch' | 'store.m3u-publish-channels',
phaseId: string,
epochMs: number,
monotonicMs: number,
items?: number
) {
return {
boundary,
epochMs,
...(items === undefined ? {} : { metadata: { items } }),
monotonicMs,
...(boundary === 'end' ? { outcome: 'success' as const } : {}),
phase,
phaseId,
schemaVersion: 1 as const,
};
}
function distribution(values: readonly number[]) {
const min = Math.min(...values);
const max = Math.max(...values);
const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
return {
count: values.length,
max,
mean,
median: mean,
min,
p95: values.length === 1 ? max : min + (max - min) * 0.95,
p99: values.length === 1 ? max : min + (max - min) * 0.99,
};
}
@@ -0,0 +1,367 @@
import {
APP_PLAYLIST_GET_PERFORMANCE_PHASE,
APP_PLAYLIST_UPSERT_PERFORMANCE_PHASE,
M3U_IMPORT_PERFORMANCE_PHASE,
} from '@iptvnator/shared/interfaces';
import type { M3uImportRendererCaptureMetrics } from './m3u-import-renderer-capture';
import type {
MainCaptureMetrics,
MainTimelineRecord,
WorkerRequestPerformanceMetrics,
} from './m3u-refresh-cancellation-contract';
import { deriveM3uImportIpcAttribution } from './m3u-import-ipc-attribution';
import {
pairPerformancePhaseEvents,
requirePerformancePhase,
type PairedPerformancePhase,
} from './performance-phase-events';
import {
pairRendererPerformancePhaseEvents,
requireRendererPerformancePhase,
} from './renderer-performance-phase-events';
export const M3U_IMPORT_INDEX_COMMIT_UNAVAILABLE_REASON =
'indexes-not-created-during-import;sqlite-autocommit-included-in-sqlite-write';
export interface M3uImportPhaseMetrics {
readonly angularRenderingMs: number;
readonly dataAcquireMs: number;
readonly databaseWorkerToMainCloneProxyMs: number;
readonly indexAndCommitMs: null;
readonly indexAndCommitUnavailableReason: typeof M3U_IMPORT_INDEX_COMMIT_UNAVAILABLE_REASON;
readonly ipcStructuredCloneProxyMs: number;
readonly mainToDatabaseWorkerCloneProxyMs: number;
readonly mainToRendererCloneProxyMs: number;
readonly m3uParsingMs: number;
readonly normalizationMs: number;
readonly playlistDeserializationMs: number;
readonly playlistSerializationMs: number;
readonly rendererToMainCloneProxyMs: number;
readonly sqliteReadMs: number;
readonly sqliteWriteMs: number;
readonly storeImportDispatchMs: number;
readonly storePublishChannelsMs: number;
readonly storeUpdateMs: number;
readonly totalMs: number;
}
export interface M3uImportPhaseDerivationInput {
readonly channelCount: number;
readonly fixtureBytes: number;
readonly main: MainCaptureMetrics;
readonly renderer: M3uImportRendererCaptureMetrics;
}
export function deriveM3uImportPhaseMetrics(
input: M3uImportPhaseDerivationInput
): M3uImportPhaseMetrics {
try {
return deriveM3uImportPhaseMetricsStrict(input);
} catch (error: unknown) {
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`m3u-import-phase-attribution-invalid:${detail}`);
}
}
function deriveM3uImportPhaseMetricsStrict(
input: M3uImportPhaseDerivationInput
): M3uImportPhaseMetrics {
requirePositiveSafeCount(input.channelCount, 'channel-count');
requirePositiveSafeCount(input.fixtureBytes, 'fixture-bytes');
const mainPhases = pairPerformancePhaseEvents(
input.main.timeline
.filter((event) => event.type === 'm3u-import-phase')
.map(toPerformancePhaseEvent)
);
if (mainPhases.length !== 3) {
throw new Error('main-phase-cardinality');
}
const mainRequestIds = new Set(mainPhases.map((phase) => phase.requestId));
if (mainRequestIds.size !== 1) {
throw new Error('main-request-correlation');
}
const mainRequestId = mainPhases[0]?.requestId;
if (!mainRequestId) {
throw new Error('main-request-missing');
}
const dataAcquire = requirePerformancePhase(
mainPhases,
mainRequestId,
M3U_IMPORT_PERFORMANCE_PHASE.DATA_ACQUIRE
);
const parseM3u = requirePerformancePhase(
mainPhases,
mainRequestId,
M3U_IMPORT_PERFORMANCE_PHASE.PARSE_M3U
);
const normalize = requirePerformancePhase(
mainPhases,
mainRequestId,
M3U_IMPORT_PERFORMANCE_PHASE.NORMALIZE
);
requireMetadataCount(
dataAcquire,
'byteCount',
input.fixtureBytes,
'data-acquire-bytes'
);
requireMetadataCount(
parseM3u,
'itemCount',
input.channelCount,
'parse-items'
);
requireMetadataCount(
normalize,
'itemCount',
input.channelCount,
'normalize-items'
);
requireOrdered(dataAcquire.endedEpochMs, parseM3u.startedEpochMs);
requireOrdered(parseM3u.endedEpochMs, normalize.startedEpochMs);
const rendererEvents = (
input.renderer.probe as typeof input.renderer.probe & {
readonly performancePhaseEvents?: readonly unknown[];
}
).performancePhaseEvents;
if (!rendererEvents) {
throw new Error('renderer-phase-events-missing');
}
const rendererPhases = pairRendererPerformancePhaseEvents(rendererEvents);
if (rendererPhases.length !== 2) {
throw new Error('renderer-phase-cardinality');
}
const importDispatch = requireRendererPerformancePhase(
rendererPhases,
'store.m3u-import-dispatch'
);
const publishChannels = requireRendererPerformancePhase(
rendererPhases,
'store.m3u-publish-channels'
);
requireRendererItems(
publishChannels.metadata?.items,
input.channelCount,
'publish-channels-items'
);
const playlistId = input.renderer.probe.playlistId;
if (typeof playlistId !== 'string' || playlistId.length === 0) {
throw new Error('renderer-playlist-id-missing');
}
const databaseWorkers = input.main.workers.filter(
(worker) => worker.kind === 'database.worker'
);
if (
databaseWorkers.length !== 1 ||
input.main.workers.some(
(worker) => worker.kind === 'playlist-refresh.worker'
)
) {
throw new Error('worker-cardinality');
}
const databaseWorker = databaseWorkers[0];
if (!databaseWorker) {
throw new Error('database-worker-missing');
}
const upserts = databaseWorker.requests.filter(
(request) =>
request.operation === 'DB_UPSERT_APP_PLAYLIST' &&
request.playlistId === playlistId &&
request.success
);
if (upserts.length !== 1 || !upserts[0]) {
throw new Error('database-upsert-correlation');
}
const upsert = upserts[0];
const gets = databaseWorker.requests.filter(
(request) =>
request.operation === 'DB_GET_APP_PLAYLIST' &&
request.playlistId === playlistId &&
request.success
);
if (
gets.length !== 1 ||
!gets[0] ||
databaseWorker.requests.length !== 2
) {
throw new Error('database-get-correlation');
}
const get = gets[0];
const databasePhases = pairPerformancePhaseEvents(upsert.phaseEvents);
if (
databasePhases.length !== 2 ||
!upsert.requestId ||
databasePhases.some((phase) => phase.requestId !== upsert.requestId)
) {
throw new Error('database-phase-cardinality');
}
const serializePlaylist = requireDatabasePhase(
databasePhases,
upsert,
APP_PLAYLIST_UPSERT_PERFORMANCE_PHASE.SERIALIZE_PLAYLIST
);
const sqliteWrite = requireDatabasePhase(
databasePhases,
upsert,
APP_PLAYLIST_UPSERT_PERFORMANCE_PHASE.SQLITE_WRITE
);
requireMetadataCount(
serializePlaylist,
'itemCount',
input.channelCount,
'serialization-items'
);
requireMetadataCount(sqliteWrite, 'itemCount', 1, 'sqlite-row-count');
const getPhases = pairPerformancePhaseEvents(get.phaseEvents);
if (
getPhases.length !== 2 ||
!get.requestId ||
getPhases.some((phase) => phase.requestId !== get.requestId)
) {
throw new Error('database-get-phase-cardinality');
}
const sqliteRead = requireDatabasePhase(
getPhases,
get,
APP_PLAYLIST_GET_PERFORMANCE_PHASE.SQLITE_READ
);
const deserializePlaylist = requireDatabasePhase(
getPhases,
get,
APP_PLAYLIST_GET_PERFORMANCE_PHASE.DESERIALIZE_PLAYLIST
);
requireMetadataCount(sqliteRead, 'itemCount', 1, 'sqlite-read-row-count');
requireMetadataCount(
deserializePlaylist,
'itemCount',
input.channelCount,
'deserialization-items'
);
const operationStart = input.renderer.phaseTimestamps.operationStartEpochMs;
const uiPainted = input.renderer.phaseTimestamps.uiPaintedEpochMs;
const terminal = input.renderer.phaseTimestamps.terminalEpochMs;
requireOrdered(operationStart, dataAcquire.startedEpochMs);
requireOrdered(serializePlaylist.endedEpochMs, sqliteWrite.startedEpochMs);
requireOrdered(sqliteWrite.endedEpochMs, sqliteRead.startedEpochMs);
requireOrdered(sqliteRead.endedEpochMs, deserializePlaylist.startedEpochMs);
requireOrdered(
deserializePlaylist.endedEpochMs,
publishChannels.startedEpochMs
);
requireOrdered(publishChannels.endedEpochMs, uiPainted);
requireOrdered(uiPainted, terminal);
const ipc = deriveM3uImportIpcAttribution({
get,
getReadStartedEpochMs: sqliteRead.startedEpochMs,
importDispatchEndedEpochMs: importDispatch.endedEpochMs,
importDispatchStartedEpochMs: importDispatch.startedEpochMs,
normalizeEndedEpochMs: normalize.endedEpochMs,
playlistId,
publishChannelsStartedEpochMs: publishChannels.startedEpochMs,
serializeStartedEpochMs: serializePlaylist.startedEpochMs,
timeline: input.main.timeline,
upsert,
});
const angularRenderingMs = difference(
uiPainted,
publishChannels.endedEpochMs
);
const totalMs = difference(terminal, operationStart);
return Object.freeze({
angularRenderingMs,
dataAcquireMs: dataAcquire.durationMs,
indexAndCommitMs: null,
indexAndCommitUnavailableReason:
M3U_IMPORT_INDEX_COMMIT_UNAVAILABLE_REASON,
...ipc,
m3uParsingMs: parseM3u.durationMs,
normalizationMs: normalize.durationMs,
playlistDeserializationMs: deserializePlaylist.durationMs,
playlistSerializationMs: serializePlaylist.durationMs,
sqliteReadMs: sqliteRead.durationMs,
sqliteWriteMs: sqliteWrite.durationMs,
storeImportDispatchMs: importDispatch.durationMs,
storePublishChannelsMs: publishChannels.durationMs,
storeUpdateMs: importDispatch.durationMs + publishChannels.durationMs,
totalMs,
});
}
function toPerformancePhaseEvent(event: MainTimelineRecord): unknown {
const metadata = {
...(event.byteCount === undefined
? {}
: { byteCount: event.byteCount }),
...(event.itemCount === undefined
? {}
: { itemCount: event.itemCount }),
};
return {
boundary: event.boundary,
durationMs: event.durationMs,
epochMs: event.epochMs,
...(Object.keys(metadata).length === 0 ? {} : { metadata }),
phase: event.phase,
requestId: event.requestId,
};
}
function requireDatabasePhase(
phases: readonly PairedPerformancePhase[],
request: WorkerRequestPerformanceMetrics,
phase: string
): PairedPerformancePhase {
if (!request.requestId) {
throw new Error('database-request-id-missing');
}
return requirePerformancePhase(phases, request.requestId, phase);
}
function requireMetadataCount(
phase: PairedPerformancePhase,
key: 'byteCount' | 'itemCount',
expected: number,
label: string
): void {
if (phase.metadata?.[key] !== expected) {
throw new Error(label);
}
}
function requireRendererItems(
value: number | undefined,
expected: number,
label: string
): void {
if (value !== expected) {
throw new Error(label);
}
}
function requirePositiveSafeCount(value: number, label: string): void {
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error(label);
}
}
function requireOrdered(startEpochMs: number, endEpochMs: number): void {
if (
!Number.isFinite(startEpochMs) ||
!Number.isFinite(endEpochMs) ||
endEpochMs < startEpochMs
) {
throw new Error('phase-order');
}
}
function difference(endEpochMs: number, startEpochMs: number): number {
requireOrdered(startEpochMs, endEpochMs);
return endEpochMs - startEpochMs;
}
@@ -0,0 +1,131 @@
import type { DatabaseWorkerPeakMemoryValidity } from './database-worker-peak-memory-validity';
import type { DatabaseWorkerRequestMetricsValidity } from './database-worker-request-metrics-validity';
import type { M3uImportScenarioId } from './m3u-import-benchmark-contract';
import type { M3uImportRendererCaptureMetrics } from './m3u-import-renderer-capture';
import {
M3U_IMPORT_INDEX_COMMIT_UNAVAILABLE_REASON,
type M3uImportPhaseMetrics,
} from './m3u-import-report';
import type {
DatabaseWorkerPostGcValidity,
MainCaptureMetrics,
NumericDistribution,
PerformanceIterationKind,
} from './m3u-refresh-cancellation-contract';
import type { RendererRssValidity } from './renderer-rss-validity';
export interface M3uImportIterationResult {
readonly channelCount: number;
readonly fixtureBytes: number;
readonly kind: PerformanceIterationKind;
readonly main: MainCaptureMetrics;
readonly phases: M3uImportPhaseMetrics;
readonly renderer: M3uImportRendererCaptureMetrics;
readonly runId: string;
readonly scenarioId: M3uImportScenarioId;
}
export interface M3uImportBenchmarkManifest {
readonly channelCount: number;
readonly fixtureBytes: number;
readonly fixtureSha256: string;
readonly gitCommit: string;
readonly gitDiffSha256: string;
readonly gitDirty: boolean;
readonly measuredRuns: number;
readonly memoryAccounting: {
readonly databaseWorkerRss: 'unavailable-per-worker-thread';
readonly mainRss: 'electron-main-process-including-worker-threads-and-native-memory';
readonly nativeAndSqliteMemory: 'included-in-main-rss-not-separately-attributable';
readonly rendererRss: 'separate-renderer-process';
readonly workerJsHeap: 'separate-v8-isolate';
};
readonly runtime: {
readonly arch: string;
readonly cdpPort: number;
readonly electronVersion: string;
readonly harnessNodeVersion: string;
readonly platform: string;
};
readonly scenario: M3uImportScenarioId;
readonly syntheticSeed: number;
readonly variant: string;
readonly warmupRuns: number;
}
export interface M3uImportMeasuredSummary {
readonly angularRenderingMs: NumericDistribution;
readonly dataAcquireMs: NumericDistribution;
readonly databaseWorkerGetEventLoopDelayMaxMs: NumericDistribution;
readonly databaseWorkerGetEventLoopDelayP95Ms: NumericDistribution;
readonly databaseWorkerGetEventLoopDelayP99Ms: NumericDistribution;
readonly databaseWorkerGetEventLoopUtilization: NumericDistribution;
readonly databaseWorkerGetThreadCpuSystemMicros: NumericDistribution;
readonly databaseWorkerGetThreadCpuUserMicros: NumericDistribution;
readonly databaseWorkerEventLoopDelayMaxMs: NumericDistribution;
readonly databaseWorkerEventLoopDelayP95Ms: NumericDistribution;
readonly databaseWorkerEventLoopDelayP99Ms: NumericDistribution;
readonly databaseWorkerEventLoopUtilization: NumericDistribution;
readonly databaseWorkerExternalPeakBytes: NumericDistribution;
readonly databaseWorkerHeapPeakBytes: NumericDistribution;
readonly databaseWorkerPostGcHeapBytes: NumericDistribution;
readonly databaseWorkerThreadCpuSystemMicros: NumericDistribution;
readonly databaseWorkerThreadCpuUserMicros: NumericDistribution;
readonly databaseWorkerToMainCloneProxyMs: NumericDistribution;
readonly databaseWorkerUpsertEventLoopDelayMaxMs: NumericDistribution;
readonly databaseWorkerUpsertEventLoopDelayP95Ms: NumericDistribution;
readonly databaseWorkerUpsertEventLoopDelayP99Ms: NumericDistribution;
readonly databaseWorkerUpsertEventLoopUtilization: NumericDistribution;
readonly databaseWorkerUpsertThreadCpuSystemMicros: NumericDistribution;
readonly databaseWorkerUpsertThreadCpuUserMicros: NumericDistribution;
readonly ipcStructuredCloneProxyMs: NumericDistribution;
readonly mainToDatabaseWorkerCloneProxyMs: NumericDistribution;
readonly mainToRendererCloneProxyMs: NumericDistribution;
readonly m3uParsingMs: NumericDistribution;
readonly mainEventLoopDelayMaxMs: NumericDistribution;
readonly mainEventLoopDelayP95Ms: NumericDistribution;
readonly mainEventLoopDelayP99Ms: NumericDistribution;
readonly mainEventLoopUtilization: NumericDistribution;
readonly mainHeapPeakBytes: NumericDistribution;
readonly mainHeapPostGcBytes: NumericDistribution;
readonly mainRssPeakBytes: NumericDistribution;
readonly mainRssPostGcBytes: NumericDistribution;
readonly normalizationMs: NumericDistribution;
readonly playlistDeserializationMs: NumericDistribution;
readonly playlistSerializationMs: NumericDistribution;
readonly rendererFrameGapMs: NumericDistribution;
readonly rendererHeartbeatDelayMs: NumericDistribution;
readonly rendererHeapPeakBytes: NumericDistribution;
readonly rendererHeapPostGcBytes: NumericDistribution;
readonly rendererLongTaskCount: number;
readonly rendererLongTaskMs: NumericDistribution;
readonly rendererRssPeakBytes: NumericDistribution;
readonly rendererToMainCloneProxyMs: NumericDistribution;
readonly responsiveEvents: number;
readonly sqliteReadMs: NumericDistribution;
readonly sqliteWriteMs: NumericDistribution;
readonly storeImportDispatchMs: NumericDistribution;
readonly storePublishChannelsMs: NumericDistribution;
readonly storeUpdateMs: NumericDistribution;
readonly totalMs: NumericDistribution;
readonly unresponsiveEvents: number;
}
export interface M3uImportBenchmarkSummary {
readonly iterations: readonly M3uImportIterationResult[];
readonly manifest: M3uImportBenchmarkManifest;
readonly measured: M3uImportMeasuredSummary;
readonly notApplicable: {
readonly cancelAcknowledgementLatencyMs: 'scenario-has-no-cancellation';
readonly databaseWorkerRss: 'unavailable-per-worker-thread';
readonly indexAndCommitMs: typeof M3U_IMPORT_INDEX_COMMIT_UNAVAILABLE_REASON;
readonly nativeAndSqliteMemory: 'included-in-main-rss-not-separately-attributable';
readonly playlistRefreshWorker: 'not-used-by-initial-import';
};
readonly validity: {
readonly databaseWorkerPeakMemory: DatabaseWorkerPeakMemoryValidity;
readonly databaseWorkerPostGc: DatabaseWorkerPostGcValidity;
readonly databaseWorkerRequestMetrics: DatabaseWorkerRequestMetricsValidity;
readonly rendererRss: RendererRssValidity;
};
}
@@ -0,0 +1,247 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { it } from 'node:test';
import { PERFORMANCE_ITERATION_KIND } from './m3u-refresh-cancellation-contract';
import {
createM3uImportBenchmarkSummary,
createM3uImportIterationResult,
type M3uImportBenchmarkSummary,
} from './m3u-import-summary';
import { assertM3uImportComparisonValidity } from './m3u-import.benchmark';
import {
createCompleteTestMainCapture,
createCompleteTestRendererCapture,
createTestM3uImportManifest,
TEST_M3U_CHANNEL_COUNT,
TEST_M3U_FIXTURE_BYTES,
} from './m3u-import-report.test-helpers';
it('keeps summary aggregation below the hard line limit without a suppression', () => {
const source = readFileSync(
resolve(process.cwd(), 'src/performance/m3u-import-summary.ts'),
'utf8'
);
assert.doesNotMatch(source, /eslint-disable max-lines/);
assert.ok(source.split(/\r?\n/).length <= 400);
});
it('summarizes measured runs only while preserving per-process memory boundaries', () => {
const warmup = createM3uImportIterationResult({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
kind: PERFORMANCE_ITERATION_KIND.WARMUP,
main: createCompleteTestMainCapture(),
renderer: createCompleteTestRendererCapture(),
runId: 'warmup-01',
scenarioId: 'm3u-import-10k',
});
const measured = createM3uImportIterationResult({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
kind: PERFORMANCE_ITERATION_KIND.MEASURED,
main: createCompleteTestMainCapture(),
renderer: createCompleteTestRendererCapture(),
runId: 'run-01',
scenarioId: 'm3u-import-10k',
});
const diagnostic = createM3uImportIterationResult({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
kind: PERFORMANCE_ITERATION_KIND.DIAGNOSTIC,
main: createCompleteTestMainCapture({
mainRss: 9_999,
rendererRss: 8_888,
}),
renderer: createCompleteTestRendererCapture({
rendererHeap: 7_777,
}),
runId: 'diagnostic',
scenarioId: 'm3u-import-10k',
});
const summary = createM3uImportBenchmarkSummary(
createTestM3uImportManifest(),
[warmup, measured, diagnostic]
);
assert.equal(summary.measured.totalMs.count, 1);
assert.equal(summary.measured.totalMs.mean, 215);
assert.equal(summary.measured.ipcStructuredCloneProxyMs.mean, 44);
assert.equal(summary.measured.mainToDatabaseWorkerCloneProxyMs.mean, 2);
assert.equal(summary.measured.databaseWorkerToMainCloneProxyMs.mean, 12);
assert.equal(summary.measured.mainToRendererCloneProxyMs.mean, 28);
assert.equal(summary.measured.rendererToMainCloneProxyMs.mean, 2);
assert.equal(summary.measured.sqliteReadMs.mean, 5);
assert.equal(summary.measured.playlistDeserializationMs.mean, 12);
assert.equal(summary.measured.databaseWorkerThreadCpuSystemMicros.mean, 100);
assert.equal(summary.measured.databaseWorkerThreadCpuUserMicros.mean, 120);
assert.equal(
summary.measured.databaseWorkerUpsertEventLoopDelayP95Ms.mean,
4
);
assert.equal(
summary.measured.databaseWorkerGetEventLoopDelayP95Ms.mean,
7
);
assert.equal(summary.measured.mainRssPeakBytes.mean, 5_000);
assert.equal(summary.measured.mainHeapPeakBytes.mean, 2_000);
assert.equal(summary.measured.rendererRssPeakBytes.mean, 4_000);
assert.equal(summary.measured.rendererHeapPeakBytes.mean, 3_000);
assert.equal(summary.measured.databaseWorkerHeapPeakBytes.mean, 1_500);
assert.equal(summary.measured.databaseWorkerPostGcHeapBytes.mean, 900);
assert.equal(summary.measured.rendererLongTaskCount, 1);
assert.equal(summary.measured.unresponsiveEvents, 0);
assert.deepEqual(summary.notApplicable, {
cancelAcknowledgementLatencyMs: 'scenario-has-no-cancellation',
databaseWorkerRss: 'unavailable-per-worker-thread',
indexAndCommitMs:
'indexes-not-created-during-import;sqlite-autocommit-included-in-sqlite-write',
nativeAndSqliteMemory:
'included-in-main-rss-not-separately-attributable',
playlistRefreshWorker: 'not-used-by-initial-import',
});
assert.equal(summary.validity.rendererRss.validForComparison, true);
assert.equal(
summary.validity.databaseWorkerPeakMemory.validForComparison,
true
);
assert.equal(
summary.validity.databaseWorkerPostGc.validForComparison,
true
);
assert.doesNotThrow(() => assertM3uImportComparisonValidity(summary));
const databaseWorker = measured.main.workers[0];
assert.ok(databaseWorker);
const invalidMeasured = createM3uImportIterationResult({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
kind: PERFORMANCE_ITERATION_KIND.MEASURED,
main: {
...measured.main,
workers: [
{
...databaseWorker,
externalMemorySampleCount: 0,
heapUsedSampleCount: 0,
peakExternalBytes: null,
peakExternalUnavailableReason:
'worker-external-memory-samples-missing',
peakHeapUsedBytes: null,
peakHeapUsedUnavailableReason:
'worker-heap-used-samples-missing',
},
],
},
renderer: measured.renderer,
runId: 'run-01',
scenarioId: 'm3u-import-10k',
});
const invalidPeakSummary = createM3uImportBenchmarkSummary(
createTestM3uImportManifest(),
[warmup, invalidMeasured, diagnostic]
);
assert.equal(
invalidPeakSummary.validity.databaseWorkerPeakMemory.validForComparison,
false
);
assert.equal(
invalidPeakSummary.measured.databaseWorkerHeapPeakBytes.count,
0
);
assert.throws(
() =>
assertM3uImportComparisonValidity(
invalidPeakSummary as M3uImportBenchmarkSummary
),
/Database worker peak-memory capture is invalid/
);
assert.throws(
() =>
createM3uImportBenchmarkSummary(createTestM3uImportManifest(), [
measured,
diagnostic,
]),
/m3u-import-summary-schedule-invalid/
);
});
it('fails comparison when any measured worker request omits mandatory event-loop or CPU metrics', () => {
const completeMain = createCompleteTestMainCapture();
const databaseWorker = completeMain.workers[0];
const get = databaseWorker?.requests[1];
assert.ok(databaseWorker);
assert.ok(get);
const invalidMain = {
...completeMain,
workers: [
{
...databaseWorker,
requests: [
databaseWorker.requests[0],
{
...get,
eventLoopDelay: null,
eventLoopDelayUnavailableReason:
'event-loop-delay-capture-unavailable',
histogramFlushedEpochMs: null,
},
],
},
],
};
const warmup = createM3uImportIterationResult({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
kind: PERFORMANCE_ITERATION_KIND.WARMUP,
main: createCompleteTestMainCapture(),
renderer: createCompleteTestRendererCapture(),
runId: 'warmup-01',
scenarioId: 'm3u-import-10k',
});
const measured = createM3uImportIterationResult({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
kind: PERFORMANCE_ITERATION_KIND.MEASURED,
main: invalidMain,
renderer: createCompleteTestRendererCapture(),
runId: 'run-01',
scenarioId: 'm3u-import-10k',
});
const diagnostic = createM3uImportIterationResult({
channelCount: TEST_M3U_CHANNEL_COUNT,
fixtureBytes: TEST_M3U_FIXTURE_BYTES,
kind: PERFORMANCE_ITERATION_KIND.DIAGNOSTIC,
main: createCompleteTestMainCapture(),
renderer: createCompleteTestRendererCapture(),
runId: 'diagnostic',
scenarioId: 'm3u-import-10k',
});
const summary = createM3uImportBenchmarkSummary(
createTestM3uImportManifest(),
[warmup, measured, diagnostic]
);
assert.equal(
summary.validity.databaseWorkerRequestMetrics.validForComparison,
false
);
assert.equal(
summary.validity.databaseWorkerRequestMetrics.expectedRequestCount,
2
);
assert.equal(
summary.validity.databaseWorkerRequestMetrics.validRequestCount,
1
);
assert.equal(
summary.measured.databaseWorkerGetEventLoopDelayP95Ms.count,
0
);
assert.throws(
() => assertM3uImportComparisonValidity(summary),
/Database worker request metrics are invalid/
);
});
@@ -0,0 +1,382 @@
import type { M3uImportRendererCaptureMetrics } from './m3u-import-renderer-capture';
import type { M3uImportScenarioId } from './m3u-import-benchmark-contract';
import type {
MainCaptureMetrics,
NumericDistribution,
PerformanceIterationKind,
WorkerCaptureMetrics,
WorkerRequestPerformanceMetrics,
} from './m3u-refresh-cancellation-contract';
import {
PERFORMANCE_ITERATION_KIND,
PERFORMANCE_WORKER_KIND,
} from './m3u-refresh-cancellation-contract';
import {
deriveM3uImportPhaseMetrics,
M3U_IMPORT_INDEX_COMMIT_UNAVAILABLE_REASON,
} from './m3u-import-report';
import { summarizeNumbers } from './performance-statistics';
import { assessDatabaseWorkerPeakMemoryValidity } from './database-worker-peak-memory-validity';
import { assessDatabaseWorkerPostGcValidity } from './database-worker-post-gc-validity';
import { assessDatabaseWorkerRequestMetricsValidity } from './database-worker-request-metrics-validity';
import { assessRendererRssValidity } from './renderer-rss-validity';
import type {
M3uImportBenchmarkManifest,
M3uImportBenchmarkSummary,
M3uImportIterationResult,
} from './m3u-import-summary-contract';
export type {
M3uImportBenchmarkManifest,
M3uImportBenchmarkSummary,
M3uImportIterationResult,
M3uImportMeasuredSummary,
} from './m3u-import-summary-contract';
export function createM3uImportIterationResult(input: {
readonly channelCount: number;
readonly fixtureBytes: number;
readonly kind: PerformanceIterationKind;
readonly main: MainCaptureMetrics;
readonly renderer: M3uImportRendererCaptureMetrics;
readonly runId: string;
readonly scenarioId: M3uImportScenarioId;
}): M3uImportIterationResult {
return Object.freeze({
channelCount: input.channelCount,
fixtureBytes: input.fixtureBytes,
kind: input.kind,
main: input.main,
phases: deriveM3uImportPhaseMetrics(input),
renderer: input.renderer,
runId: input.runId,
scenarioId: input.scenarioId,
});
}
export function createM3uImportBenchmarkSummary(
manifest: M3uImportBenchmarkManifest,
iterations: readonly M3uImportIterationResult[]
): M3uImportBenchmarkSummary {
validateIterationSchedule(manifest, iterations);
const measured = iterations.filter(
(iteration) => iteration.kind === PERFORMANCE_ITERATION_KIND.MEASURED
);
validateMeasuredIterations(manifest, measured);
const values = (
select: (iteration: M3uImportIterationResult) => number | null
): NumericDistribution => summarizeNumbers(measured.map(select));
const rendererSamples = (
select: (
probe: M3uImportRendererCaptureMetrics['probe']
) => readonly number[]
): NumericDistribution =>
summarizeNumbers(
measured.flatMap((iteration) => [
...select(iteration.renderer.probe),
])
);
const databaseWorkers = measured.map(requireDatabaseWorker);
const databaseRequestPairs = databaseWorkers.map(
requireInitialImportRequests
);
const databaseUpserts = databaseRequestPairs.map((pair) => pair.upsert);
const databaseGets = databaseRequestPairs.map((pair) => pair.get);
const databaseRequests = databaseRequestPairs.flatMap((pair) => [
pair.upsert,
pair.get,
]);
return Object.freeze({
iterations: Object.freeze([...iterations]),
manifest,
measured: Object.freeze({
angularRenderingMs: values(
(iteration) => iteration.phases.angularRenderingMs
),
dataAcquireMs: values(
(iteration) => iteration.phases.dataAcquireMs
),
databaseWorkerGetEventLoopDelayMaxMs: summarizeNumbers(
databaseGets.map((request) => request.eventLoopDelay?.maxMs)
),
databaseWorkerGetEventLoopDelayP95Ms: summarizeNumbers(
databaseGets.map((request) => request.eventLoopDelay?.p95Ms)
),
databaseWorkerGetEventLoopDelayP99Ms: summarizeNumbers(
databaseGets.map((request) => request.eventLoopDelay?.p99Ms)
),
databaseWorkerGetEventLoopUtilization: summarizeNumbers(
databaseGets.map((request) => request.eventLoopUtilization)
),
databaseWorkerGetThreadCpuSystemMicros: summarizeNumbers(
databaseGets.map((request) => request.threadCpuSystemMicros)
),
databaseWorkerGetThreadCpuUserMicros: summarizeNumbers(
databaseGets.map((request) => request.threadCpuUserMicros)
),
databaseWorkerEventLoopDelayMaxMs: summarizeNumbers(
databaseRequests.map((request) => request.eventLoopDelay?.maxMs)
),
databaseWorkerEventLoopDelayP95Ms: summarizeNumbers(
databaseRequests.map((request) => request.eventLoopDelay?.p95Ms)
),
databaseWorkerEventLoopDelayP99Ms: summarizeNumbers(
databaseRequests.map((request) => request.eventLoopDelay?.p99Ms)
),
databaseWorkerEventLoopUtilization: summarizeNumbers(
databaseRequests.map((request) => request.eventLoopUtilization)
),
databaseWorkerExternalPeakBytes: summarizeNumbers(
databaseWorkers.map((worker) => worker.peakExternalBytes)
),
databaseWorkerHeapPeakBytes: summarizeNumbers(
databaseWorkers.map((worker) => worker.peakHeapUsedBytes)
),
databaseWorkerPostGcHeapBytes: summarizeNumbers(
databaseWorkers.map((worker) => worker.postGcHeapUsedBytes)
),
databaseWorkerThreadCpuSystemMicros: summarizeNumbers(
databaseRequestPairs.map((pair) =>
sumRequestMetric(
pair.upsert.threadCpuSystemMicros,
pair.get.threadCpuSystemMicros
)
)
),
databaseWorkerThreadCpuUserMicros: summarizeNumbers(
databaseRequestPairs.map((pair) =>
sumRequestMetric(
pair.upsert.threadCpuUserMicros,
pair.get.threadCpuUserMicros
)
)
),
databaseWorkerToMainCloneProxyMs: values(
(iteration) =>
iteration.phases.databaseWorkerToMainCloneProxyMs
),
databaseWorkerUpsertEventLoopDelayMaxMs: summarizeNumbers(
databaseUpserts.map((request) => request.eventLoopDelay?.maxMs)
),
databaseWorkerUpsertEventLoopDelayP95Ms: summarizeNumbers(
databaseUpserts.map((request) => request.eventLoopDelay?.p95Ms)
),
databaseWorkerUpsertEventLoopDelayP99Ms: summarizeNumbers(
databaseUpserts.map((request) => request.eventLoopDelay?.p99Ms)
),
databaseWorkerUpsertEventLoopUtilization: summarizeNumbers(
databaseUpserts.map((request) => request.eventLoopUtilization)
),
databaseWorkerUpsertThreadCpuSystemMicros: summarizeNumbers(
databaseUpserts.map(
(request) => request.threadCpuSystemMicros
)
),
databaseWorkerUpsertThreadCpuUserMicros: summarizeNumbers(
databaseUpserts.map((request) => request.threadCpuUserMicros)
),
ipcStructuredCloneProxyMs: values(
(iteration) => iteration.phases.ipcStructuredCloneProxyMs
),
mainToDatabaseWorkerCloneProxyMs: values(
(iteration) => iteration.phases.mainToDatabaseWorkerCloneProxyMs
),
mainToRendererCloneProxyMs: values(
(iteration) => iteration.phases.mainToRendererCloneProxyMs
),
m3uParsingMs: values((iteration) => iteration.phases.m3uParsingMs),
mainEventLoopDelayMaxMs: values(
(iteration) => iteration.main.eventLoopDelay.maxMs
),
mainEventLoopDelayP95Ms: values(
(iteration) => iteration.main.eventLoopDelay.p95Ms
),
mainEventLoopDelayP99Ms: values(
(iteration) => iteration.main.eventLoopDelay.p99Ms
),
mainEventLoopUtilization: values(
(iteration) => iteration.main.eventLoopUtilization
),
mainHeapPeakBytes: values(
(iteration) => iteration.main.memory.peakHeapUsedBytes
),
mainHeapPostGcBytes: values(
(iteration) => iteration.main.memory.postGcHeapUsedBytes
),
mainRssPeakBytes: values(
(iteration) => iteration.main.memory.peakRssBytes
),
mainRssPostGcBytes: values(
(iteration) => iteration.main.memory.postGcRssBytes
),
normalizationMs: values(
(iteration) => iteration.phases.normalizationMs
),
playlistDeserializationMs: values(
(iteration) => iteration.phases.playlistDeserializationMs
),
playlistSerializationMs: values(
(iteration) => iteration.phases.playlistSerializationMs
),
rendererFrameGapMs: rendererSamples((probe) => probe.frameGapsMs),
rendererHeartbeatDelayMs: rendererSamples(
(probe) => probe.heartbeatDelaysMs
),
rendererHeapPeakBytes: values(
(iteration) => iteration.renderer.peakHeapUsedBytes
),
rendererHeapPostGcBytes: values(
(iteration) => iteration.renderer.postGcHeapUsedBytes
),
rendererLongTaskCount: measured.reduce(
(total, iteration) =>
total + iteration.renderer.probe.longTasksMs.length,
0
),
rendererLongTaskMs: rendererSamples((probe) => probe.longTasksMs),
rendererRssPeakBytes: values(
(iteration) => iteration.main.rendererWindow.rss.peakRssBytes
),
rendererToMainCloneProxyMs: values(
(iteration) => iteration.phases.rendererToMainCloneProxyMs
),
responsiveEvents: measured.reduce(
(total, iteration) =>
total + iteration.main.rendererWindow.responsiveEvents,
0
),
sqliteReadMs: values((iteration) => iteration.phases.sqliteReadMs),
sqliteWriteMs: values(
(iteration) => iteration.phases.sqliteWriteMs
),
storeImportDispatchMs: values(
(iteration) => iteration.phases.storeImportDispatchMs
),
storePublishChannelsMs: values(
(iteration) => iteration.phases.storePublishChannelsMs
),
storeUpdateMs: values(
(iteration) => iteration.phases.storeUpdateMs
),
totalMs: values((iteration) => iteration.phases.totalMs),
unresponsiveEvents: measured.reduce(
(total, iteration) =>
total + iteration.main.rendererWindow.unresponsiveEvents,
0
),
}),
notApplicable: Object.freeze({
cancelAcknowledgementLatencyMs: 'scenario-has-no-cancellation',
databaseWorkerRss: 'unavailable-per-worker-thread',
indexAndCommitMs: M3U_IMPORT_INDEX_COMMIT_UNAVAILABLE_REASON,
nativeAndSqliteMemory:
'included-in-main-rss-not-separately-attributable',
playlistRefreshWorker: 'not-used-by-initial-import',
}),
validity: Object.freeze({
databaseWorkerPeakMemory:
assessDatabaseWorkerPeakMemoryValidity(iterations),
databaseWorkerPostGc:
assessDatabaseWorkerPostGcValidity(iterations),
databaseWorkerRequestMetrics:
assessDatabaseWorkerRequestMetricsValidity(iterations),
rendererRss: assessRendererRssValidity(iterations),
}),
});
}
function validateIterationSchedule(
manifest: M3uImportBenchmarkManifest,
iterations: readonly M3uImportIterationResult[]
): void {
const warmupCount = iterations.filter(
(iteration) => iteration.kind === PERFORMANCE_ITERATION_KIND.WARMUP
).length;
const measuredCount = iterations.filter(
(iteration) => iteration.kind === PERFORMANCE_ITERATION_KIND.MEASURED
).length;
const diagnosticCount = iterations.filter(
(iteration) => iteration.kind === PERFORMANCE_ITERATION_KIND.DIAGNOSTIC
).length;
if (
warmupCount !== manifest.warmupRuns ||
measuredCount !== manifest.measuredRuns ||
diagnosticCount !== 1 ||
iterations.length !== warmupCount + measuredCount + diagnosticCount ||
new Set(iterations.map((iteration) => iteration.runId)).size !==
iterations.length ||
iterations.some(
(iteration) =>
iteration.channelCount !== manifest.channelCount ||
iteration.fixtureBytes !== manifest.fixtureBytes ||
iteration.scenarioId !== manifest.scenario
)
) {
throw new Error('m3u-import-summary-schedule-invalid');
}
}
function validateMeasuredIterations(
manifest: M3uImportBenchmarkManifest,
measured: readonly M3uImportIterationResult[]
): void {
if (
measured.length !== manifest.measuredRuns ||
new Set(measured.map((iteration) => iteration.runId)).size !==
measured.length ||
measured.some(
(iteration) =>
iteration.channelCount !== manifest.channelCount ||
iteration.fixtureBytes !== manifest.fixtureBytes ||
iteration.scenarioId !== manifest.scenario
)
) {
throw new Error('m3u-import-summary-measured-runs-invalid');
}
}
function requireDatabaseWorker(
iteration: M3uImportIterationResult
): WorkerCaptureMetrics {
const databaseWorkers = iteration.main.workers.filter(
(worker) => worker.kind === PERFORMANCE_WORKER_KIND.DATABASE
);
if (databaseWorkers.length !== 1 || !databaseWorkers[0]) {
throw new Error('m3u-import-summary-database-worker-invalid');
}
return databaseWorkers[0];
}
function requireInitialImportRequests(
worker: WorkerCaptureMetrics
): {
readonly get: WorkerRequestPerformanceMetrics;
readonly upsert: WorkerRequestPerformanceMetrics;
} {
const upserts = worker.requests.filter(
(request) =>
request.operation === 'DB_UPSERT_APP_PLAYLIST' && request.success
);
const gets = worker.requests.filter(
(request) =>
request.operation === 'DB_GET_APP_PLAYLIST' && request.success
);
if (
worker.requests.length !== 2 ||
upserts.length !== 1 ||
!upserts[0] ||
gets.length !== 1 ||
!gets[0]
) {
throw new Error('m3u-import-summary-database-request-invalid');
}
return { get: gets[0], upsert: upserts[0] };
}
function sumRequestMetric(
first: number | null,
second: number | null
): number | null {
return first === null || second === null ? null : first + second;
}
@@ -0,0 +1,345 @@
import { mkdir, mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
closeElectronApp,
expect,
launchElectronApp,
type LaunchedElectronApp,
} from '../electron-test-fixtures';
import { validateInitialImportDiagnosticArtifacts } from './initial-import-diagnostic-artifacts';
import {
assertRendererCdpTarget,
assertTcpPortAvailable,
type M3uImportBenchmarkConfiguration,
resolveM3uImportBenchmarkConfiguration,
resolveRendererWindowIdentity,
writeM3uImportJson as writeJson,
} from './m3u-import-benchmark-support';
import {
assertSyntheticM3uImportUrl,
createM3uImportIterationDefinitions,
listM3uImportScenarios,
type M3uImportIterationDefinition,
type M3uImportScenario,
} from './m3u-import-benchmark-contract';
import {
createM3uImportBenchmarkSummary,
createM3uImportIterationResult,
type M3uImportBenchmarkManifest,
type M3uImportBenchmarkSummary,
type M3uImportIterationResult,
} from './m3u-import-summary';
import {
startM3uImportRendererCapture,
type RunningM3uImportRendererCapture,
} from './m3u-import-renderer-capture';
import {
installMainCapture,
readMainCaptureStatus,
startMainCapture,
stopMainCapture,
} from './m3u-refresh-main-capture';
import { PERFORMANCE_ITERATION_KIND } from './m3u-refresh-cancellation-contract';
import { assertPerformanceArtifactCapacity } from './performance-artifact-preflight';
import {
startSyntheticM3uServer,
type SyntheticM3uServer,
} from './synthetic-m3u-server';
import {
createSyntheticM3uFixture,
SYNTHETIC_M3U_SEED,
type SyntheticM3uFixture,
} from './synthetic-m3u';
export async function runM3uImportBenchmark(): Promise<void> {
const config = await resolveM3uImportBenchmarkConfiguration();
const definitions = createM3uImportIterationDefinitions(config.smoke);
for (const scenario of listM3uImportScenarios()) {
await runScenario(
config,
scenario,
definitions.filter(
(definition) => definition.scenarioId === scenario.id
)
);
}
}
async function runScenario(
config: M3uImportBenchmarkConfiguration,
scenario: M3uImportScenario,
definitions: readonly M3uImportIterationDefinition[]
): Promise<void> {
const fixture = createSyntheticM3uFixture(scenario.channelCount);
const server = await startSyntheticM3uServer(fixture);
const scenarioDirectory = join(config.variantDirectory, scenario.id);
const iterations: M3uImportIterationResult[] = [];
try {
assertSyntheticM3uImportUrl(server.resourceUrl);
await server.prime();
await assertPerformanceArtifactCapacity(scenarioDirectory);
await mkdir(scenarioDirectory, {
mode: 0o700,
recursive: false,
});
for (const definition of definitions) {
console.log(
`[performance] ${config.variant}/${scenario.id}/${definition.runId} starting`
);
const result = await runIteration(
config,
definition,
fixture,
server,
scenarioDirectory
);
iterations.push(result);
console.log(
`[performance] ${definition.runId} total=${result.phases.totalMs.toFixed(3)}ms`
);
}
} finally {
await server.close();
}
if (server.requestCount() !== definitions.length + 1) {
throw new Error('Synthetic M3U server request total is invalid');
}
const manifest = createManifest(config, scenario, fixture);
const summary = createM3uImportBenchmarkSummary(manifest, iterations);
await writeJson(join(scenarioDirectory, 'manifest.json'), manifest);
await writeJson(join(scenarioDirectory, 'summary.json'), summary);
const diagnostic = requireDiagnosticIteration(iterations);
await validateInitialImportDiagnosticArtifacts(
{ main: diagnostic.main, renderer: diagnostic.renderer },
join(scenarioDirectory, diagnostic.runId)
);
assertM3uImportComparisonValidity(summary);
console.log(
`[performance] completed: ${join(scenarioDirectory, 'summary.json')}`
);
}
export function assertM3uImportComparisonValidity(
summary: M3uImportBenchmarkSummary
): void {
if (!summary.validity.rendererRss.validForComparison) {
throw new Error(
`Renderer RSS capture is invalid for comparison: ${JSON.stringify(
summary.validity.rendererRss.invalidMeasuredRuns
)}`
);
}
if (!summary.validity.databaseWorkerPostGc.validForComparison) {
throw new Error(
`Database worker post-GC capture is invalid for comparison: ${JSON.stringify(
summary.validity.databaseWorkerPostGc.invalidMeasuredRuns
)}`
);
}
if (!summary.validity.databaseWorkerPeakMemory.validForComparison) {
throw new Error(
`Database worker peak-memory capture is invalid for comparison: ${JSON.stringify(
summary.validity.databaseWorkerPeakMemory.invalidMeasuredRuns
)}`
);
}
if (!summary.validity.databaseWorkerRequestMetrics.validForComparison) {
throw new Error(
`Database worker request metrics are invalid for comparison: ${JSON.stringify(
summary.validity.databaseWorkerRequestMetrics.invalidRequests
)}`
);
}
}
async function runIteration(
config: M3uImportBenchmarkConfiguration,
definition: M3uImportIterationDefinition,
fixture: SyntheticM3uFixture,
server: SyntheticM3uServer,
scenarioDirectory: string
): Promise<M3uImportIterationResult> {
const iterationDirectory = join(scenarioDirectory, definition.runId);
await assertPerformanceArtifactCapacity(iterationDirectory);
await mkdir(iterationDirectory, {
mode: 0o700,
recursive: false,
});
const dataDirectory = await mkdtemp(
join(tmpdir(), 'iptvnator-m3u-import-performance-')
);
let app: LaunchedElectronApp | null = null;
let renderer: RunningM3uImportRendererCapture | null = null;
try {
await assertTcpPortAvailable(config.rendererCdpPort);
app = await launchElectronApp(dataDirectory, {
args: [
'--js-flags=--expose-gc',
'--remote-debugging-address=127.0.0.1',
`--remote-debugging-port=${config.rendererCdpPort}`,
`--user-data-dir=${join(dataDirectory, 'user-data')}`,
],
environmentInheritance: 'runtime-only',
env: {
IPTVNATOR_DB_WORKER_BATCH_DELAY_MS: '0',
IPTVNATOR_PERF_CAPTURE: '1',
IPTVNATOR_PERF_WORKER_PROFILING: '1',
IPTVNATOR_TRACE_RENDERER_CONSOLE:
process.env['IPTVNATOR_TRACE_RENDERER_CONSOLE'] ?? '0',
},
});
app.mainWindow.setDefaultTimeout(120_000);
await assertRendererCdpTarget(app.mainWindow, config.rendererCdpPort);
await installMainCapture(app.electronApp);
const rendererWindowIdentity = await resolveRendererWindowIdentity(app);
const requestsBefore = server.requestCount();
const diagnostic =
definition.kind === PERFORMANCE_ITERATION_KIND.DIAGNOSTIC;
renderer = await startM3uImportRendererCapture(app.mainWindow, {
diagnostic,
outputDirectory: iterationDirectory,
playlistTitle: `Synthetic M3U import ${definition.channelCount}`,
playlistUrl: server.resourceUrl,
});
await startMainCapture(app.electronApp, {
diagnostic,
outputDirectory: iterationDirectory,
rendererWindowIdentity,
});
await renderer.triggerImport();
await renderer.waitForTerminal();
await waitForDatabaseSettlement(app);
assertM3uImportRequestDelta(requestsBefore, server.requestCount());
const [rendererMetrics, mainMetrics] = await Promise.all([
renderer.stop(),
stopMainCapture(app.electronApp),
]);
await Promise.all([
writeJson(
join(iterationDirectory, 'main-capture.json'),
mainMetrics
),
writeJson(
join(iterationDirectory, 'renderer-capture.json'),
rendererMetrics
),
]);
const result = createM3uImportIterationResult({
channelCount: definition.channelCount,
fixtureBytes: fixture.bytes,
kind: definition.kind,
main: mainMetrics,
renderer: rendererMetrics,
runId: definition.runId,
scenarioId: definition.scenarioId,
});
await writeJson(join(iterationDirectory, 'result.json'), result);
return result;
} finally {
if (renderer) {
await renderer.dispose().catch((error: unknown) => {
console.error(
'[performance] Renderer capture cleanup failed',
error
);
});
}
if (app) {
await closeElectronApp(app).catch((error: unknown) => {
console.error('[performance] Electron cleanup failed', error);
});
}
await rm(dataDirectory, { force: true, recursive: true });
}
}
async function waitForDatabaseSettlement(
app: LaunchedElectronApp
): Promise<void> {
await expect
.poll(
async () => {
const status = await readMainCaptureStatus(app.electronApp);
return (
status.databaseRequests === 2 &&
status.databaseUpsertsCompleted === 1 &&
status.databaseGetsCompleted === 1 &&
status.databasePending === 0 &&
status.preloadSuccessMarkers === 2
);
},
{ timeout: 120_000 }
)
.toBe(true);
}
function createManifest(
config: M3uImportBenchmarkConfiguration,
scenario: M3uImportScenario,
fixture: SyntheticM3uFixture
): M3uImportBenchmarkManifest {
return Object.freeze({
channelCount: fixture.channelCount,
fixtureBytes: fixture.bytes,
fixtureSha256: fixture.sha256,
gitCommit: config.sourceState.commit,
gitDiffSha256: config.sourceState.diffSha256,
gitDirty: config.sourceState.dirty,
measuredRuns: config.measuredRuns,
memoryAccounting: Object.freeze({
databaseWorkerRss: 'unavailable-per-worker-thread',
mainRss:
'electron-main-process-including-worker-threads-and-native-memory',
nativeAndSqliteMemory:
'included-in-main-rss-not-separately-attributable',
rendererRss: 'separate-renderer-process',
workerJsHeap: 'separate-v8-isolate',
}),
runtime: Object.freeze({
arch: process.arch,
cdpPort: config.rendererCdpPort,
electronVersion: config.electronVersion,
harnessNodeVersion: process.versions.node,
platform: process.platform,
}),
scenario: scenario.id,
syntheticSeed: SYNTHETIC_M3U_SEED,
variant: config.variant,
warmupRuns: 1,
});
}
function requireDiagnosticIteration(
iterations: readonly M3uImportIterationResult[]
): M3uImportIterationResult {
const diagnostic = iterations.filter(
(iteration) => iteration.kind === PERFORMANCE_ITERATION_KIND.DIAGNOSTIC
);
if (diagnostic.length !== 1 || !diagnostic[0]) {
throw new Error('M3U import diagnostic iteration is missing');
}
return diagnostic[0];
}
export function assertM3uImportRequestDelta(
before: number,
after: number
): void {
const oneRequest =
Number.isSafeInteger(before) &&
Number.isSafeInteger(after) &&
after === before + 1;
if (!oneRequest) {
throw new Error(
'Synthetic M3U import must issue exactly one application GET'
);
}
}
@@ -1,7 +1,9 @@
import type { RendererProcessRssCapture } from './renderer-process-rss-capture';
import type { RendererRssValidity } from './renderer-rss-validity';
import type {
RendererProcessRssCapture,
RendererProcessRssUnavailableReason,
} from './renderer-process-rss-capture';
DatabaseWorkerPerformancePhase,
PerformancePhaseEvent,
} from '@iptvnator/shared/interfaces';
export const PERFORMANCE_ITERATION_KIND = {
DIAGNOSTIC: 'diagnostic',
@@ -54,6 +56,11 @@ export type WorkerPostGcHeapCapture =
readonly postGcHeapUsedBytes: null;
};
export const WORKER_PEAK_MEMORY_UNAVAILABLE_REASON = {
EXTERNAL_MEMORY_SAMPLES_MISSING: 'worker-external-memory-samples-missing',
HEAP_USED_SAMPLES_MISSING: 'worker-heap-used-samples-missing',
} as const;
export interface NumericDistribution {
readonly count: number;
readonly max: number | null;
@@ -78,12 +85,25 @@ export interface ProcessMemoryMetrics {
}
export interface MainTimelineRecord {
readonly action?: string | null;
readonly boundary?: 'end' | 'start';
readonly byteCount?: number;
readonly categoryType?: string | null;
readonly contentType?: string | null;
readonly durationMs?: number | null;
readonly epochMs: number;
readonly ipcCallId?: number;
readonly itemCount?: number | null;
readonly method?: string;
readonly operation?: string;
readonly operationId?: string;
readonly playlistId?: string;
readonly operationId?: string | null;
readonly outcome?: 'error' | 'success' | null;
readonly phase?: string;
readonly playlistId?: string | null;
readonly requestId?: string;
readonly sessionId?: string | null;
readonly success?: boolean;
readonly sourceEpochMs?: number;
readonly type: string;
}
@@ -93,15 +113,19 @@ export interface WorkerRequestPerformanceMetrics {
readonly eventLoopUtilization: number | null;
readonly eventLoopUtilizationUnavailableReason: string | null;
readonly histogramFlushedEpochMs: number | null;
readonly ipcCallId: number | null;
readonly invalidReason: string | null;
readonly operation: string | null;
readonly operationId: string | null;
readonly operationIdUnavailableReason: string | null;
readonly performanceCaptureUnavailableReason: string | null;
readonly phaseEvents: readonly PerformancePhaseEvent<DatabaseWorkerPerformancePhase>[];
readonly playlistId: string | null;
readonly requestId: string | null;
readonly requestReceivedEpochMs: number | null;
readonly responseEpochMs: number;
readonly responsePostedEpochMs: number | null;
readonly sourceEpochMs: number | null;
readonly success: boolean;
readonly threadCpuSystemMicros: number | null;
readonly threadCpuUnavailableReason: string | null;
@@ -117,11 +141,19 @@ export type WorkerCaptureMetrics = {
readonly eventLoopDelay: EventLoopDelayMetrics | null;
readonly eventLoopDelayUnavailableReason: string | null;
readonly eventLoopUtilization: number | null;
readonly externalMemorySampleCount: number;
readonly heapUsedSampleCount: number;
readonly kind: PerformanceWorkerKind;
readonly operationId: string | null;
readonly ordinal: number;
readonly peakExternalBytes: number;
readonly peakHeapUsedBytes: number;
readonly peakExternalBytes: number | null;
readonly peakExternalUnavailableReason:
| typeof WORKER_PEAK_MEMORY_UNAVAILABLE_REASON.EXTERNAL_MEMORY_SAMPLES_MISSING
| null;
readonly peakHeapUsedBytes: number | null;
readonly peakHeapUsedUnavailableReason:
| typeof WORKER_PEAK_MEMORY_UNAVAILABLE_REASON.HEAP_USED_SAMPLES_MISSING
| null;
readonly playlistId: string | null;
readonly profilePath: string | null;
readonly requests: readonly WorkerRequestPerformanceMetrics[];
@@ -257,22 +289,6 @@ export interface DatabaseWorkerPostGcValidity {
readonly validMeasuredRunCount: number;
}
export interface InvalidRendererRssMeasuredRun {
readonly missingSampleCount: number;
readonly reason:
RendererProcessRssUnavailableReason | 'renderer-rss-capture-invalid';
readonly runId: string;
readonly validSampleCount: number;
}
export interface RendererRssValidity {
readonly invalidMeasuredRuns: readonly InvalidRendererRssMeasuredRun[];
readonly measuredRunCount: number;
readonly validForBenchmark: boolean;
readonly validForComparison: boolean;
readonly validMeasuredRunCount: number;
}
export interface CancellationBenchmarkSummary {
readonly cancellationEffectRate: number;
readonly iterations: readonly CancellationIterationResult[];
@@ -9,7 +9,6 @@ import type {
NumericDistribution,
PerformanceWorkerKind,
RendererCaptureMetrics,
RendererRssValidity,
WorkerRequestPerformanceMetrics,
} from './m3u-refresh-cancellation-contract';
import {
@@ -21,6 +20,7 @@ import {
summarizeNumbers,
} from './performance-statistics';
import { assessDatabaseWorkerPostGcValidity } from './database-worker-post-gc-validity';
import { assessRendererRssValidity } from './renderer-rss-validity';
export function createCancellationIterationResult(input: {
readonly kind: CancellationIterationResult['kind'];
@@ -316,50 +316,6 @@ export function createCancellationBenchmarkSummary(
});
}
function assessRendererRssValidity(
iterations: readonly CancellationIterationResult[]
): RendererRssValidity {
const measured = iterations.filter(
(iteration) => iteration.kind === PERFORMANCE_ITERATION_KIND.MEASURED
);
const invalidMeasuredRuns: RendererRssValidity['invalidMeasuredRuns'][number][] =
[];
let validMeasuredRunCount = 0;
for (const iteration of measured) {
const rss = iteration.main.rendererWindow.rss;
if (
rss.identity !== null &&
Number.isSafeInteger(rss.peakRssBytes) &&
Number(rss.peakRssBytes) > 0 &&
rss.unavailableReason === null &&
rss.missingSampleCount === 0 &&
Number.isSafeInteger(rss.validSampleCount) &&
rss.validSampleCount > 0
) {
validMeasuredRunCount += 1;
continue;
}
invalidMeasuredRuns.push({
missingSampleCount: rss.missingSampleCount,
reason: rss.unavailableReason ?? 'renderer-rss-capture-invalid',
runId: iteration.runId,
validSampleCount: rss.validSampleCount,
});
}
const valid =
measured.length > 0 && validMeasuredRunCount === measured.length;
return Object.freeze({
invalidMeasuredRuns: Object.freeze(invalidMeasuredRuns),
measuredRunCount: measured.length,
validForBenchmark: valid,
validForComparison: valid,
validMeasuredRunCount,
});
}
function derivePhases(
main: MainCaptureMetrics,
renderer: RendererCaptureMetrics
@@ -186,7 +186,10 @@ async function runIteration(
): Promise<CancellationIterationResult> {
const iterationDirectory = join(config.outputDirectory, definition.runId);
await assertPerformanceArtifactCapacity(iterationDirectory);
await mkdir(iterationDirectory, { recursive: false });
await mkdir(iterationDirectory, {
mode: 0o700,
recursive: false,
});
const dataDirectory = await mkdtemp(
join(tmpdir(), 'iptvnator-m3u-performance-')
);
@@ -204,6 +207,7 @@ async function runIteration(
`--remote-debugging-port=${RENDERER_CDP_PORT}`,
`--user-data-dir=${join(dataDirectory, 'user-data')}`,
],
environmentInheritance: 'runtime-only',
env: {
IPTVNATOR_DB_WORKER_BATCH_DELAY_MS: '0',
IPTVNATOR_PERF_CAPTURE: '1',
@@ -448,7 +452,10 @@ async function resolveConfiguration(): Promise<BenchmarkConfiguration> {
const outputDirectory = join(outputRoot, variant);
await assertMissing(outputDirectory);
await assertPerformanceArtifactCapacity(outputDirectory);
await mkdir(outputDirectory, { recursive: true });
await mkdir(outputDirectory, {
mode: 0o700,
recursive: true,
});
return Object.freeze({
electronVersion: electronPackage.version,
gitCommit: sourceState.commit,
File diff suppressed because it is too large. Load diff
@@ -1,8 +1,8 @@
/* eslint-disable max-lines -- Renderer CDP and injected-page lifecycle are kept together so raw artifact boundaries stay auditable. */
import { once } from 'node:events';
import { createWriteStream } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { finished } from 'node:stream/promises';
import type { CDPSession, Page } from '@playwright/test';
@@ -106,7 +106,8 @@ export async function startRendererCapture(
await session.send('Profiler.enable');
await session.send('Profiler.start');
traceOutput = createWriteStream(
requireArtifactPath(tracePath, 'renderer trace')
requireArtifactPath(tracePath, 'renderer trace'),
{ flags: 'wx', mode: 0o600 }
);
traceOutput.write('{"traceEvents":[');
session.on('Tracing.dataCollected', onTraceData);
@@ -143,7 +144,7 @@ export async function startRendererCapture(
await writeFile(
requireArtifactPath(cpuProfilePath, 'renderer CPU profile'),
JSON.stringify(cpuProfile['profile']),
'utf8'
{ encoding: 'utf8', flag: 'wx', mode: 0o600 }
);
await session.send('Tracing.end');
await traceComplete;
@@ -152,7 +153,7 @@ export async function startRendererCapture(
traceOutput?.write(']}');
traceOutput?.end();
if (traceOutput) {
await once(traceOutput, 'finish');
await finished(traceOutput);
}
}
@@ -167,7 +168,8 @@ export async function startRendererCapture(
requireArtifactPath(
heapSnapshotPath,
'renderer heap snapshot'
)
),
{ flags: 'wx', mode: 0o600 }
);
const onSnapshotChunk = (message: {
readonly chunk: string;
@@ -186,7 +188,7 @@ export async function startRendererCapture(
onSnapshotChunk
);
output.end();
await once(output, 'finish');
await finished(output);
}
await session.detach();
@@ -211,8 +213,7 @@ export async function startRendererCapture(
unknown
>;
const state = target[stateKey] as
| { cancelClickEpochMs: number | null }
| undefined;
{ cancelClickEpochMs: number | null } | undefined;
return typeof state?.cancelClickEpochMs === 'number';
},
RENDERER_CAPTURE_STATE_KEY,
@@ -228,8 +229,7 @@ export async function startRendererCapture(
unknown
>;
const state = target[stateKey] as
| { uiPaintedEpochMs: number | null }
| undefined;
{ uiPaintedEpochMs: number | null } | undefined;
return typeof state?.uiPaintedEpochMs === 'number';
},
RENDERER_CAPTURE_STATE_KEY,
@@ -0,0 +1,81 @@
/* eslint-disable playwright/expect-expect -- This is a Node assertion-based performance contract test. */
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
const source = readFileSync(
new URL('./m3u-refresh-main-capture.ts', import.meta.url),
'utf8'
);
function section(startMarker: string, endMarker: string): string {
const start = source.indexOf(startMarker);
const end = source.indexOf(endMarker, start);
assert.ok(start >= 0, `missing start marker: ${startMarker}`);
assert.ok(end > start, `missing end marker: ${endMarker}`);
return source.slice(start, end);
}
test('samples main and worker memory from one shared interval scheduler', () => {
const workerRecord = section(
'interface WorkerRecord',
'interface TimelineRecord'
);
const resetWorker = section(
'const resetWorkerForCapture',
'const startWorker'
);
const startWorker = section(
'const startWorker',
'const stopWorkerSampling'
);
const stopWorker = section(
'const stopWorkerSampling',
'const joinFinalWorkerSample'
);
const joinFinalWorker = section(
'const joinFinalWorkerSample',
'const stopWorkerProfile'
);
const sampleMain = section('const sampleMain', 'const startCapture');
const beginMeasurement = section(
'const beginMeasurement = async',
'const startCapture'
);
const armCapture = section('const startCapture', 'const api =');
const stopCapture = section(
'stop: async (',
'xtreamStatus: (): XtreamMainCaptureStatus'
);
assert.match(workerRecord, /samplingStarted: boolean/);
assert.doesNotMatch(workerRecord, /sampleTimer/);
assert.match(resetWorker, /record\.samplingStarted = false/);
assert.match(startWorker, /if \(record\.samplingStarted\)/);
assert.match(
startWorker,
/record\.samplingStarted = true[\s\S]*const initialSample = sampleWorker\(record\)/
);
assert.doesNotMatch(startWorker, /setInterval/);
assert.match(stopWorker, /record\.samplingStarted = false/);
assert.match(joinFinalWorker, /await sampleWorker\(record\)/);
assert.match(
sampleMain,
/for \(const record of records\.values\(\)\)[\s\S]*record\.samplingStarted &&[\s\S]*isCurrentCaptureRecord\(record\)[\s\S]*void sampleWorker\(record\)/
);
assert.match(
beginMeasurement,
/sampleMain\(\)[\s\S]*state\.sampleTimer = setInterval\(sampleMain, 20\)/
);
assert.doesNotMatch(armCapture, /setInterval\(sampleMain/);
assert.match(
stopCapture,
/clearInterval\(state\.sampleTimer\)[\s\S]*state\.sampleTimer = null[\s\S]*await startCapture\(nextOptions\)/
);
assert.equal(
source.match(/\bsetInterval\s*\(/g)?.length,
1,
'the injected main capture must own exactly one interval timer'
);
});
@@ -270,3 +270,34 @@ test('the cancellation benchmark exposes GC to Electron worker isolates', () =>
assert.match(source, /'--js-flags=--expose-gc'/);
assert.doesNotMatch(source, /execArgv/);
});
test('the initial M3U import benchmark uses the production-equivalent performance build', () => {
const target = e2eProject.targets['benchmark-m3u-import'];
assert.ok(target, 'electron-backend-e2e must define benchmark-m3u-import');
assert.deepEqual(target.dependsOn, ['electron-backend:build-performance']);
});
test('the initial M3U import benchmark command is pinned to its Playwright test file', () => {
const target = e2eProject.targets['benchmark-m3u-import'];
assert.equal(
target.options?.['command'],
'pnpm exec playwright test --config=playwright.performance.config.ts src/m3u-import.performance.ts'
);
});
test('the initial M3U import benchmark enables opt-in capture and worker GC', () => {
const source = readFileSync(
join(
workspaceRoot,
'apps/electron-backend-e2e/src/performance/m3u-import.benchmark.ts'
),
'utf8'
);
assert.match(source, /IPTVNATOR_PERF_CAPTURE:\s*'1'/);
assert.match(source, /IPTVNATOR_PERF_WORKER_PROFILING:\s*'1'/);
assert.match(source, /'--js-flags=--expose-gc'/);
assert.doesNotMatch(source, /execArgv/);
});
@@ -0,0 +1,18 @@
/**
* Phase duration and epoch endpoints are read from separate clocks in separate
* calls. The enclosing request/operation window is the causal upper bound.
*/
export function fitsPerformanceDurationEnvelope(
durationMs: number,
envelopeStartEpochMs: number,
envelopeEndEpochMs: number
): boolean {
return (
Number.isFinite(durationMs) &&
durationMs >= 0 &&
Number.isFinite(envelopeStartEpochMs) &&
Number.isFinite(envelopeEndEpochMs) &&
envelopeEndEpochMs >= envelopeStartEpochMs &&
durationMs <= envelopeEndEpochMs - envelopeStartEpochMs
);
}
@@ -0,0 +1,221 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
createDiagnosticsPerformancePhaseEventParser,
pairPerformancePhaseEvents,
requirePerformancePhase,
} from './performance-phase-events';
describe('performance phase event pairing', () => {
it('restores and invokes the diagnostics parser without module-scoped helpers', () => {
const restore = <T>(source: string): T =>
new Function(`"use strict"; return (${source});`)() as T;
const restored = restore<
typeof createDiagnosticsPerformancePhaseEventParser
>(createDiagnosticsPerformancePhaseEventParser.toString());
const parse = restored({
allowedPhases: ['xtream.network.total'],
timelineType: 'xtream-main-phase',
});
assert.deepEqual(
parse({
boundary: 'start',
durationMs: null,
epochMs: 100,
phase: 'xtream.network.total',
requestId: 'xtream-mock1-1',
}),
{
boundary: 'start',
durationMs: null,
epochMs: 100,
phase: 'xtream.network.total',
requestId: 'xtream-mock1-1',
type: 'xtream-main-phase',
}
);
});
it('keeps the valid M3U diagnostics normalization byte-identical when subscriptions are parameterized', () => {
const parse = createDiagnosticsPerformancePhaseEventParser({
allowedPhases: ['data.acquire', 'parse.m3u', 'normalize'],
timelineType: 'm3u-import-phase',
});
const incoming = {
boundary: 'end',
durationMs: 12.5,
epochMs: 113,
metadata: { byteCount: 2048, itemCount: 10 },
phase: 'data.acquire',
requestId: 'request-1',
};
const legacyNormalized = {
boundary: 'end',
byteCount: 2048,
durationMs: 12.5,
epochMs: 113,
itemCount: 10,
phase: 'data.acquire',
requestId: 'request-1',
type: 'm3u-import-phase',
};
assert.equal(
JSON.stringify(parse(incoming)),
JSON.stringify(legacyNormalized)
);
assert.equal(parse({ ...incoming, secret: 'not allowed' }), null);
assert.equal(parse({ ...incoming, metadata: { itemCount: -1 } }), null);
});
it('pairs request-scoped boundaries and preserves end metadata', () => {
const phases = pairPerformancePhaseEvents([
{
boundary: 'start',
durationMs: null,
epochMs: 100,
phase: 'data.acquire',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 12.5,
epochMs: 113,
metadata: { byteCount: 2048, itemCount: 10 },
phase: 'data.acquire',
requestId: 'request-1',
},
]);
assert.deepEqual(phases, [
{
durationMs: 12.5,
endedEpochMs: 113,
metadata: { byteCount: 2048, itemCount: 10 },
phase: 'data.acquire',
requestId: 'request-1',
startedEpochMs: 100,
},
]);
assert.deepEqual(
requirePerformancePhase(phases, 'request-1', 'data.acquire'),
phases[0]
);
});
it('pairs interleaved requests without cross-correlating them', () => {
const phases = pairPerformancePhaseEvents([
{
boundary: 'start',
durationMs: null,
epochMs: 100,
phase: 'serialize.playlist',
requestId: 'request-a',
},
{
boundary: 'start',
durationMs: null,
epochMs: 101,
phase: 'serialize.playlist',
requestId: 'request-b',
},
{
boundary: 'end',
durationMs: 3,
epochMs: 104,
phase: 'serialize.playlist',
requestId: 'request-b',
},
{
boundary: 'end',
durationMs: 8,
epochMs: 108,
phase: 'serialize.playlist',
requestId: 'request-a',
},
]);
assert.deepEqual(
phases.map(({ durationMs, requestId }) => ({
durationMs,
requestId,
})),
[
{ durationMs: 8, requestId: 'request-a' },
{ durationMs: 3, requestId: 'request-b' },
]
);
});
it('fails closed for orphaned, duplicate, malformed, or reversed boundaries', () => {
const validStart = {
boundary: 'start',
durationMs: null,
epochMs: 100,
phase: 'normalize',
requestId: 'request-1',
} as const;
const validEnd = {
boundary: 'end',
durationMs: 2,
epochMs: 102,
phase: 'normalize',
requestId: 'request-1',
} as const;
for (const invalid of [
[validStart],
[validStart, validStart, validEnd],
[validStart, { ...validEnd, durationMs: null }],
[validStart, { ...validEnd, epochMs: 99 }],
[{ ...validStart, durationMs: 0 }, validEnd],
[
validStart,
{
...validEnd,
metadata: { byteCount: -1 },
},
],
]) {
assert.throws(
() => pairPerformancePhaseEvents(invalid),
/performance-phase-events-invalid/
);
}
});
it('requires one exact request and phase match', () => {
const phases = pairPerformancePhaseEvents([
{
boundary: 'start',
durationMs: null,
epochMs: 100,
phase: 'normalize',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 2,
epochMs: 102,
phase: 'normalize',
requestId: 'request-1',
},
]);
assert.throws(
() => requirePerformancePhase(phases, 'request-2', 'normalize'),
/performance-phase-missing:request-2:normalize/
);
assert.throws(
() =>
requirePerformancePhase(
[...phases, ...phases],
'request-1',
'normalize'
),
/performance-phase-ambiguous:request-1:normalize/
);
});
});
@@ -0,0 +1,267 @@
import type { PerformancePhaseMetadata } from '@iptvnator/shared/interfaces';
export interface PairedPerformancePhase {
readonly durationMs: number;
readonly endedEpochMs: number;
readonly metadata: PerformancePhaseMetadata | null;
readonly phase: string;
readonly requestId: string;
readonly startedEpochMs: number;
}
interface ParsedPerformancePhaseEvent {
readonly boundary: 'end' | 'start';
readonly durationMs: number | null;
readonly epochMs: number;
readonly metadata: PerformancePhaseMetadata | null;
readonly phase: string;
readonly requestId: string;
}
export interface DiagnosticsPerformancePhaseEventParserOptions {
readonly allowedPhases: readonly string[];
readonly timelineType: string;
}
export interface DiagnosticsPerformancePhaseTimelineEvent {
readonly boundary: 'end' | 'start';
readonly byteCount?: number;
readonly durationMs: number | null;
readonly epochMs: number;
readonly itemCount?: number;
readonly phase: string;
readonly requestId: string;
readonly type: string;
}
export function createDiagnosticsPerformancePhaseEventParser(
options: DiagnosticsPerformancePhaseEventParserOptions
): (input: unknown) => DiagnosticsPerformancePhaseTimelineEvent | null {
const allowedPhases = new Set(options.allowedPhases);
const eventKeys = new Set([
'boundary',
'durationMs',
'epochMs',
'metadata',
'phase',
'requestId',
]);
const metadataKeys = new Set(['byteCount', 'itemCount']);
const helpers = {
isCount(value: unknown): value is number {
return Number.isSafeInteger(value) && Number(value) >= 0;
},
isRecord(value: unknown): value is Record<string, unknown> {
return (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value)
);
},
};
return (input: unknown) => {
if (
!helpers.isRecord(input) ||
Object.keys(input).some((key) => !eventKeys.has(key))
) {
return null;
}
const boundary = input['boundary'];
const durationMs = input['durationMs'];
const epochMs = input['epochMs'];
const metadata = input['metadata'];
const phase = input['phase'];
const requestId = input['requestId'];
if (
(boundary !== 'start' && boundary !== 'end') ||
typeof epochMs !== 'number' ||
!Number.isFinite(epochMs) ||
epochMs < 0 ||
typeof phase !== 'string' ||
!allowedPhases.has(phase) ||
typeof requestId !== 'string' ||
requestId.length === 0 ||
(boundary === 'start' && durationMs !== null) ||
(boundary === 'end' &&
(typeof durationMs !== 'number' ||
!Number.isFinite(durationMs) ||
durationMs < 0))
) {
return null;
}
if (
metadata !== undefined &&
(!helpers.isRecord(metadata) ||
Object.keys(metadata).some((key) => !metadataKeys.has(key)) ||
Object.values(metadata).some(
(value) => !helpers.isCount(value)
))
) {
return null;
}
const byteCount = helpers.isRecord(metadata)
? metadata['byteCount']
: undefined;
const itemCount = helpers.isRecord(metadata)
? metadata['itemCount']
: undefined;
return {
boundary,
...(helpers.isCount(byteCount) ? { byteCount } : {}),
durationMs: durationMs as number | null,
epochMs,
...(helpers.isCount(itemCount) ? { itemCount } : {}),
phase,
requestId,
type: options.timelineType,
};
};
}
export function pairPerformancePhaseEvents(
input: readonly unknown[]
): readonly PairedPerformancePhase[] {
const grouped = new Map<string, ParsedPerformancePhaseEvent[]>();
for (const value of input) {
const event = parsePerformancePhaseEvent(value);
const key = `${event.requestId}\u0000${event.phase}`;
const events = grouped.get(key) ?? [];
events.push(event);
if (events.length > 2) {
throw invalidPhaseEvents();
}
grouped.set(key, events);
}
const paired: PairedPerformancePhase[] = [];
for (const events of grouped.values()) {
const start = events.find((event) => event.boundary === 'start');
const end = events.find((event) => event.boundary === 'end');
if (
events.length !== 2 ||
!start ||
!end ||
start.durationMs !== null ||
start.metadata !== null ||
end.durationMs === null ||
end.epochMs < start.epochMs
) {
throw invalidPhaseEvents();
}
paired.push(
Object.freeze({
durationMs: end.durationMs,
endedEpochMs: end.epochMs,
metadata: end.metadata,
phase: start.phase,
requestId: start.requestId,
startedEpochMs: start.epochMs,
})
);
}
paired.sort(
(left, right) =>
left.startedEpochMs - right.startedEpochMs ||
left.endedEpochMs - right.endedEpochMs ||
left.requestId.localeCompare(right.requestId) ||
left.phase.localeCompare(right.phase)
);
return Object.freeze(paired);
}
export function requirePerformancePhase(
phases: readonly PairedPerformancePhase[],
requestId: string,
phase: string
): PairedPerformancePhase {
const matches = phases.filter(
(candidate) =>
candidate.requestId === requestId && candidate.phase === phase
);
if (matches.length === 0) {
throw new Error(`performance-phase-missing:${requestId}:${phase}`);
}
if (matches.length !== 1 || !matches[0]) {
throw new Error(`performance-phase-ambiguous:${requestId}:${phase}`);
}
return matches[0];
}
function parsePerformancePhaseEvent(
value: unknown
): ParsedPerformancePhaseEvent {
if (!isRecord(value)) {
throw invalidPhaseEvents();
}
const boundary = value['boundary'];
const durationMs = value['durationMs'];
const epochMs = value['epochMs'];
const phase = value['phase'];
const requestId = value['requestId'];
if (
(boundary !== 'start' && boundary !== 'end') ||
(durationMs !== null && !isFiniteNonNegativeNumber(durationMs)) ||
!isFiniteNonNegativeNumber(epochMs) ||
typeof phase !== 'string' ||
phase.length === 0 ||
typeof requestId !== 'string' ||
requestId.length === 0
) {
throw invalidPhaseEvents();
}
return {
boundary,
durationMs,
epochMs,
metadata: parseMetadata(value['metadata']),
phase,
requestId,
};
}
function parseMetadata(value: unknown): PerformancePhaseMetadata | null {
if (value === undefined) {
return null;
}
if (!isRecord(value)) {
throw invalidPhaseEvents();
}
const keys = Object.keys(value);
if (
keys.some((key) => key !== 'byteCount' && key !== 'itemCount') ||
keys.some((key) => !isSafeCount(value[key]))
) {
throw invalidPhaseEvents();
}
return Object.freeze({
...(value['byteCount'] === undefined
? {}
: { byteCount: Number(value['byteCount']) }),
...(value['itemCount'] === undefined
? {}
: { itemCount: Number(value['itemCount']) }),
});
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function isFiniteNonNegativeNumber(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
}
function isSafeCount(value: unknown): boolean {
return (
value === undefined ||
(Number.isSafeInteger(value) && Number(value) >= 0)
);
}
function invalidPhaseEvents(): Error {
return new Error('performance-phase-events-invalid');
}
@@ -0,0 +1,126 @@
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, test } from 'node:test';
import {
createRendererArtifactCapture,
startRendererDiagnosticCapture,
stopRendererDiagnosticCapture,
takeRendererHeapSnapshot,
} from './m3u-import-renderer-artifacts';
const directories: string[] = [];
afterEach(async () => {
await Promise.all(
directories
.splice(0)
.map((directory) => rm(directory, { force: true, recursive: true }))
);
});
test('creates renderer raw profiles exclusively with owner-only permissions', async () => {
const directory = await createDirectory();
const session = new FakeCdpSession();
const artifacts = createRendererArtifactCapture({
diagnostic: true,
outputDirectory: directory,
});
await startRendererDiagnosticCapture(session as never, artifacts);
await stopRendererDiagnosticCapture(session as never, artifacts);
await takeRendererHeapSnapshot(
session as never,
artifacts.heapSnapshotPath
);
for (const filename of [
'renderer.cpuprofile',
'renderer.heapsnapshot',
'renderer.trace.json',
]) {
assert.equal(
(await stat(join(directory, filename))).mode & 0o777,
0o600,
filename
);
}
});
test('never replaces an existing renderer CPU profile', async () => {
const directory = await createDirectory();
const profilePath = join(directory, 'renderer.cpuprofile');
await writeFile(profilePath, 'original', 'utf8');
const session = new FakeCdpSession();
const artifacts = createRendererArtifactCapture({
diagnostic: true,
outputDirectory: directory,
});
await startRendererDiagnosticCapture(session as never, artifacts);
await assert.rejects(
stopRendererDiagnosticCapture(session as never, artifacts),
{ code: 'EEXIST' }
);
assert.equal(await readFile(profilePath, 'utf8'), 'original');
});
test('uses exclusive owner-only writes for main and worker raw artifacts', async () => {
const source = await readFile(
new URL('./m3u-refresh-main-capture.ts', import.meta.url),
'utf8'
);
assert.match(
source,
/fs\.writeFileSync\(\s*record\.profilePath,[\s\S]{0,220}flag: 'wx',[\s\S]{0,80}mode: 0o600/
);
assert.match(
source,
/fs\.createWriteStream\(record\.snapshotPath,\s*\{[\s\S]{0,80}flags: 'wx',[\s\S]{0,80}mode: 0o600/
);
assert.match(
source,
/fs\.writeFileSync\(\s*state\.mainProfilePath,[\s\S]{0,220}flag: 'wx',[\s\S]{0,80}mode: 0o600/
);
assert.match(
source,
/fs\.createWriteStream\(\s*state\.mainSnapshotPath,\s*\{[\s\S]{0,80}flags: 'wx',[\s\S]{0,80}mode: 0o600/
);
});
class FakeCdpSession extends EventEmitter {
async send(method: string): Promise<Record<string, unknown>> {
if (method === 'Profiler.stop') {
return {
profile: {
endTime: 2,
nodes: [{ callFrame: {}, hitCount: 1, id: 1 }],
samples: [1],
startTime: 1,
timeDeltas: [1],
},
};
}
if (method === 'Tracing.end') {
this.emit('Tracing.tracingComplete');
}
if (method === 'HeapProfiler.takeHeapSnapshot') {
this.emit('HeapProfiler.addHeapSnapshotChunk', {
chunk: '{"snapshot":{}}',
});
}
return {};
}
}
async function createDirectory(): Promise<string> {
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-performance-artifact-')
);
directories.push(directory);
return directory;
}
@@ -0,0 +1,53 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
createPerformanceTimelineMergeApi,
type PerformanceTimelineMergeApi,
} from './performance-timeline-merge';
describe('performance timeline merge', () => {
it('restores without module closures for Electron-main injection', () => {
const source = createPerformanceTimelineMergeApi.toString();
assert.doesNotMatch(source, /__name/);
const factory = new Function(
`"use strict"; return (${source});`
)() as typeof createPerformanceTimelineMergeApi;
assert.equal(factory().merge([], []).length, 0);
});
it('inserts Xtream records by epoch without reordering existing M3U JSON', () => {
const api: PerformanceTimelineMergeApi =
createPerformanceTimelineMergeApi();
const baseline = [
{ epochMs: 100, type: 'm3u-a' },
{ epochMs: 300, type: 'm3u-b' },
{ epochMs: 300, type: 'm3u-c' },
];
const additions = [
{ epochMs: 300, type: 'xtream-c' },
{ epochMs: 50, type: 'xtream-a' },
{ epochMs: 200, type: 'xtream-b' },
{ epochMs: 400, type: 'xtream-d' },
];
const merged = api.merge(baseline, additions);
assert.deepEqual(
merged.map(({ type }) => type),
[
'xtream-a',
'm3u-a',
'xtream-b',
'm3u-b',
'm3u-c',
'xtream-c',
'xtream-d',
]
);
assert.equal(
JSON.stringify(merged.filter(({ type }) => type.startsWith('m3u'))),
JSON.stringify(baseline)
);
});
});
@@ -0,0 +1,62 @@
export interface PerformanceTimelineRecord {
readonly epochMs: number;
}
export interface PerformanceTimelineMergeApi {
merge<T extends PerformanceTimelineRecord>(
baseline: readonly T[],
additions: readonly T[]
): T[];
}
export function createPerformanceTimelineMergeApi(): PerformanceTimelineMergeApi {
const helpers = {
compare(
left: {
readonly index: number;
readonly record: PerformanceTimelineRecord;
},
right: {
readonly index: number;
readonly record: PerformanceTimelineRecord;
}
): number {
return (
left.record.epochMs - right.record.epochMs ||
left.index - right.index
);
},
};
const api: PerformanceTimelineMergeApi = {
merge<T extends PerformanceTimelineRecord>(
baseline: readonly T[],
additions: readonly T[]
): T[] {
const indexed: Array<{ index: number; record: T }> = [];
for (let index = 0; index < additions.length; index += 1) {
indexed.push({ index, record: additions[index] as T });
}
indexed.sort(helpers.compare);
const merged: T[] = [];
let additionIndex = 0;
for (const record of baseline) {
while (
additionIndex < indexed.length &&
(indexed[additionIndex]?.record.epochMs ??
Number.POSITIVE_INFINITY) < record.epochMs
) {
merged.push(indexed[additionIndex]?.record as T);
additionIndex += 1;
}
merged.push(record);
}
while (additionIndex < indexed.length) {
merged.push(indexed[additionIndex]?.record as T);
additionIndex += 1;
}
return merged;
},
};
return Object.freeze(api);
}
@@ -0,0 +1,133 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
pairRendererPerformancePhaseEvents,
requireRendererPerformancePhase,
} from './renderer-performance-phase-events';
describe('renderer performance phase event pairing', () => {
it('pairs a successful renderer phase using monotonic duration', () => {
const phases = pairRendererPerformancePhaseEvents([
{
boundary: 'start',
epochMs: 100,
metadata: { items: 10_000 },
monotonicMs: 20,
phase: 'store.m3u-publish-channels',
phaseId: 'phase-1',
schemaVersion: 1,
},
{
boundary: 'end',
epochMs: 106,
metadata: { items: 10_000 },
monotonicMs: 25.5,
outcome: 'success',
phase: 'store.m3u-publish-channels',
phaseId: 'phase-1',
schemaVersion: 1,
},
]);
assert.deepEqual(phases, [
{
durationMs: 5.5,
endedEpochMs: 106,
metadata: { items: 10_000 },
outcome: 'success',
phase: 'store.m3u-publish-channels',
phaseId: 'phase-1',
startedEpochMs: 100,
},
]);
assert.deepEqual(
requireRendererPerformancePhase(
phases,
'store.m3u-publish-channels'
),
phases[0]
);
});
it('fails closed for inconsistent identity, metadata, outcome, and clocks', () => {
const start = {
boundary: 'start',
epochMs: 100,
metadata: { items: 50_000 },
monotonicMs: 20,
phase: 'store.m3u-import-dispatch',
phaseId: 'phase-1',
schemaVersion: 1,
} as const;
const end = {
boundary: 'end',
epochMs: 102,
metadata: { items: 50_000 },
monotonicMs: 22,
outcome: 'success',
phase: 'store.m3u-import-dispatch',
phaseId: 'phase-1',
schemaVersion: 1,
} as const;
for (const invalid of [
[start],
[start, start, end],
[start, { ...end, phase: 'store.m3u-publish-channels' }],
[start, { ...end, metadata: { items: 10_000 } }],
[{ ...start, outcome: 'success' }, end],
[start, { ...end, outcome: undefined }],
[start, { ...end, epochMs: 99 }],
[start, { ...end, monotonicMs: 19 }],
[start, { ...end, metadata: { items: -1 } }],
]) {
assert.throws(
() => pairRendererPerformancePhaseEvents(invalid),
/renderer-performance-phase-events-invalid/
);
}
});
it('requires exactly one successful event for a named phase', () => {
const successful = {
durationMs: 2,
endedEpochMs: 102,
metadata: { items: 10_000 },
outcome: 'success',
phase: 'store.m3u-import-dispatch',
phaseId: 'phase-1',
startedEpochMs: 100,
} as const;
const failed = {
...successful,
outcome: 'error',
phaseId: 'phase-2',
} as const;
assert.throws(
() =>
requireRendererPerformancePhase(
[successful],
'store.m3u-publish-channels'
),
/renderer-performance-phase-missing:store.m3u-publish-channels/
);
assert.throws(
() =>
requireRendererPerformancePhase(
[successful, successful],
'store.m3u-import-dispatch'
),
/renderer-performance-phase-ambiguous:store.m3u-import-dispatch/
);
assert.throws(
() =>
requireRendererPerformancePhase(
[failed],
'store.m3u-import-dispatch'
),
/renderer-performance-phase-failed:store.m3u-import-dispatch/
);
});
});
@@ -0,0 +1,163 @@
export interface PairedRendererPerformancePhase {
readonly durationMs: number;
readonly endedEpochMs: number;
readonly metadata: Readonly<{ items: number }> | null;
readonly outcome: 'error' | 'success';
readonly phase: string;
readonly phaseId: string;
readonly startedEpochMs: number;
}
interface ParsedRendererPerformancePhaseEvent {
readonly boundary: 'end' | 'start';
readonly epochMs: number;
readonly metadata: Readonly<{ items: number }> | null;
readonly monotonicMs: number;
readonly outcome: 'error' | 'success' | null;
readonly phase: string;
readonly phaseId: string;
}
export function pairRendererPerformancePhaseEvents(
input: readonly unknown[]
): readonly PairedRendererPerformancePhase[] {
const grouped = new Map<string, ParsedRendererPerformancePhaseEvent[]>();
for (const value of input) {
const event = parseRendererPerformancePhaseEvent(value);
const events = grouped.get(event.phaseId) ?? [];
events.push(event);
if (events.length > 2) {
throw invalidRendererEvents();
}
grouped.set(event.phaseId, events);
}
const paired: PairedRendererPerformancePhase[] = [];
for (const events of grouped.values()) {
const start = events.find((event) => event.boundary === 'start');
const end = events.find((event) => event.boundary === 'end');
if (
events.length !== 2 ||
!start ||
!end ||
start.outcome !== null ||
end.outcome === null ||
start.phase !== end.phase ||
!sameMetadata(start.metadata, end.metadata) ||
end.epochMs < start.epochMs ||
end.monotonicMs < start.monotonicMs
) {
throw invalidRendererEvents();
}
paired.push(
Object.freeze({
durationMs: end.monotonicMs - start.monotonicMs,
endedEpochMs: end.epochMs,
metadata: end.metadata,
outcome: end.outcome,
phase: end.phase,
phaseId: end.phaseId,
startedEpochMs: start.epochMs,
})
);
}
paired.sort(
(left, right) =>
left.startedEpochMs - right.startedEpochMs ||
left.endedEpochMs - right.endedEpochMs ||
left.phaseId.localeCompare(right.phaseId)
);
return Object.freeze(paired);
}
export function requireRendererPerformancePhase(
phases: readonly PairedRendererPerformancePhase[],
phase: string
): PairedRendererPerformancePhase {
const matches = phases.filter((candidate) => candidate.phase === phase);
if (matches.length === 0) {
throw new Error(`renderer-performance-phase-missing:${phase}`);
}
if (matches.length !== 1 || !matches[0]) {
throw new Error(`renderer-performance-phase-ambiguous:${phase}`);
}
if (matches[0].outcome !== 'success') {
throw new Error(`renderer-performance-phase-failed:${phase}`);
}
return matches[0];
}
function parseRendererPerformancePhaseEvent(
value: unknown
): ParsedRendererPerformancePhaseEvent {
if (!isRecord(value) || value['schemaVersion'] !== 1) {
throw invalidRendererEvents();
}
const boundary = value['boundary'];
const epochMs = value['epochMs'];
const monotonicMs = value['monotonicMs'];
const outcome = value['outcome'];
const phase = value['phase'];
const phaseId = value['phaseId'];
if (
(boundary !== 'start' && boundary !== 'end') ||
!isFiniteNonNegativeNumber(epochMs) ||
!isFiniteNonNegativeNumber(monotonicMs) ||
typeof phase !== 'string' ||
phase.length === 0 ||
typeof phaseId !== 'string' ||
phaseId.length === 0 ||
(boundary === 'start' && outcome !== undefined) ||
(boundary === 'end' && outcome !== 'success' && outcome !== 'error')
) {
throw invalidRendererEvents();
}
return {
boundary,
epochMs,
metadata: parseMetadata(value['metadata']),
monotonicMs,
outcome: outcome === 'success' || outcome === 'error' ? outcome : null,
phase,
phaseId,
};
}
function parseMetadata(value: unknown): Readonly<{ items: number }> | null {
if (value === undefined) {
return null;
}
if (
!isRecord(value) ||
Object.keys(value).length !== 1 ||
!Number.isSafeInteger(value['items']) ||
Number(value['items']) < 0
) {
throw invalidRendererEvents();
}
return Object.freeze({ items: Number(value['items']) });
}
function sameMetadata(
left: Readonly<{ items: number }> | null,
right: Readonly<{ items: number }> | null
): boolean {
return left === null || right === null
? left === right
: left.items === right.items;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function isFiniteNonNegativeNumber(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
}
function invalidRendererEvents(): Error {
return new Error('renderer-performance-phase-events-invalid');
}
@@ -0,0 +1,172 @@
/* eslint-disable playwright/expect-expect -- This is a Node assertion-based performance contract test. */
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
interface TestIteration {
readonly kind: 'diagnostic' | 'measured' | 'warmup';
readonly main: {
readonly rendererWindow: {
readonly rss: {
readonly identity: {
readonly creationTime: number;
readonly pid: number;
} | null;
readonly missingSampleCount: number;
readonly peakRssBytes: number | null;
readonly unavailableReason: string | null;
readonly validSampleCount: number;
};
};
};
readonly runId: string;
}
interface RendererRssValidity {
readonly invalidMeasuredRuns: readonly {
readonly missingSampleCount: number;
readonly reason: string;
readonly runId: string;
readonly validSampleCount: number;
}[];
readonly measuredRunCount: number;
readonly validForBenchmark: boolean;
readonly validForComparison: boolean;
readonly validMeasuredRunCount: number;
}
interface RendererRssValidityModule {
assessRendererRssValidity?: (
iterations: readonly TestIteration[]
) => RendererRssValidity;
}
const validityModulePromise = import(
new URL('./renderer-rss-validity.ts', import.meta.url).href
)
.then((module) => module as RendererRssValidityModule)
.catch(() => null);
function iteration(
runId: string,
kind: TestIteration['kind'],
input: Partial<TestIteration['main']['rendererWindow']['rss']> = {}
): TestIteration {
return {
kind,
main: {
rendererWindow: {
rss: {
identity: {
creationTime: 1_721_234_567_890,
pid: 42,
},
missingSampleCount: 0,
peakRssBytes: 4_096,
unavailableReason: null,
validSampleCount: 3,
...input,
},
},
},
runId,
};
}
test('validates only measured renderer RSS runs and preserves the exact summary shape', async () => {
const module = await validityModulePromise;
assert.ok(module, 'generic renderer RSS validity helper must exist');
const assess = module.assessRendererRssValidity;
assert.equal(typeof assess, 'function');
const result = assess([
iteration('warmup-invalid', 'warmup', { identity: null }),
iteration('run-01', 'measured'),
iteration('diagnostic-invalid', 'diagnostic', {
unavailableReason: 'renderer-process-gone',
}),
]);
assert.deepEqual(result, {
invalidMeasuredRuns: [],
measuredRunCount: 1,
validForBenchmark: true,
validForComparison: true,
validMeasuredRunCount: 1,
});
assert.ok(Object.isFrozen(result));
assert.ok(Object.isFrozen(result.invalidMeasuredRuns));
});
test('fails every incomplete measured capture without losing its raw reason and counts', async () => {
const module = await validityModulePromise;
assert.ok(module);
const assess = module.assessRendererRssValidity;
assert.equal(typeof assess, 'function');
const result = assess([
iteration('run-no-identity', 'measured', { identity: null }),
iteration('run-missing-sample', 'measured', {
missingSampleCount: 1,
peakRssBytes: null,
unavailableReason: 'renderer-process-metric-missing-during-capture',
validSampleCount: 2,
}),
iteration('run-zero-peak', 'measured', { peakRssBytes: 0 }),
]);
assert.deepEqual(result, {
invalidMeasuredRuns: [
{
missingSampleCount: 0,
reason: 'renderer-rss-capture-invalid',
runId: 'run-no-identity',
validSampleCount: 3,
},
{
missingSampleCount: 1,
reason: 'renderer-process-metric-missing-during-capture',
runId: 'run-missing-sample',
validSampleCount: 2,
},
{
missingSampleCount: 0,
reason: 'renderer-rss-capture-invalid',
runId: 'run-zero-peak',
validSampleCount: 3,
},
],
measuredRunCount: 3,
validForBenchmark: false,
validForComparison: false,
validMeasuredRunCount: 0,
});
});
test('requires at least one measured run', async () => {
const module = await validityModulePromise;
assert.ok(module);
const assess = module.assessRendererRssValidity;
assert.equal(typeof assess, 'function');
assert.deepEqual(assess([iteration('warmup-01', 'warmup')]), {
invalidMeasuredRuns: [],
measuredRunCount: 0,
validForBenchmark: false,
validForComparison: false,
validMeasuredRunCount: 0,
});
});
test('the cancellation report delegates renderer RSS validity to the generic helper', () => {
const source = readFileSync(
new URL('./m3u-refresh-cancellation-report.ts', import.meta.url),
'utf8'
);
assert.match(
source,
/import \{ assessRendererRssValidity \} from '\.\/renderer-rss-validity';/
);
assert.doesNotMatch(source, /function assessRendererRssValidity\(/);
});
@@ -0,0 +1,72 @@
import type { RendererProcessRssCapture } from './renderer-process-rss-capture';
export interface RendererRssValidityIteration {
readonly kind: string;
readonly main: {
readonly rendererWindow: {
readonly rss: RendererProcessRssCapture;
};
};
readonly runId: string;
}
export interface InvalidRendererRssMeasuredRun {
readonly missingSampleCount: number;
readonly reason:
| Exclude<RendererProcessRssCapture['unavailableReason'], null>
| 'renderer-rss-capture-invalid';
readonly runId: string;
readonly validSampleCount: number;
}
export interface RendererRssValidity {
readonly invalidMeasuredRuns: readonly InvalidRendererRssMeasuredRun[];
readonly measuredRunCount: number;
readonly validForBenchmark: boolean;
readonly validForComparison: boolean;
readonly validMeasuredRunCount: number;
}
export function assessRendererRssValidity(
iterations: readonly RendererRssValidityIteration[]
): RendererRssValidity {
const measured = iterations.filter(
(iteration) => iteration.kind === 'measured'
);
const invalidMeasuredRuns: RendererRssValidity['invalidMeasuredRuns'][number][] =
[];
let validMeasuredRunCount = 0;
for (const iteration of measured) {
const rss = iteration.main.rendererWindow.rss;
if (
rss.identity !== null &&
Number.isSafeInteger(rss.peakRssBytes) &&
Number(rss.peakRssBytes) > 0 &&
rss.unavailableReason === null &&
rss.missingSampleCount === 0 &&
Number.isSafeInteger(rss.validSampleCount) &&
rss.validSampleCount > 0
) {
validMeasuredRunCount += 1;
continue;
}
invalidMeasuredRuns.push({
missingSampleCount: rss.missingSampleCount,
reason: rss.unavailableReason ?? 'renderer-rss-capture-invalid',
runId: iteration.runId,
validSampleCount: rss.validSampleCount,
});
}
const valid =
measured.length > 0 && validMeasuredRunCount === measured.length;
return Object.freeze({
invalidMeasuredRuns: Object.freeze(invalidMeasuredRuns),
measuredRunCount: measured.length,
validForBenchmark: valid,
validForComparison: valid,
validMeasuredRunCount,
});
}
@@ -0,0 +1,49 @@
/* eslint-disable playwright/expect-expect -- Node assertions define the loopback-only fixture server contract. */
import assert from 'node:assert/strict';
import test from 'node:test';
import { createSyntheticM3uFixture } from './synthetic-m3u';
import { startSyntheticM3uServer } from './synthetic-m3u-server';
test('serves and primes only the deterministic fixture on ephemeral IPv4 loopback', async () => {
const fixture = createSyntheticM3uFixture(10_000);
const server = await startSyntheticM3uServer(fixture);
try {
const url = new URL(server.resourceUrl);
assert.equal(url.protocol, 'http:');
assert.equal(url.hostname, '127.0.0.1');
assert.notEqual(url.port, '');
assert.equal(url.username, '');
assert.equal(url.password, '');
assert.equal(server.requestCount(), 0);
await server.prime();
assert.equal(server.requestCount(), 1);
const response = await fetch(server.resourceUrl);
assert.equal(response.status, 200);
assert.equal(
response.headers.get('content-length'),
String(fixture.bytes)
);
assert.equal(await response.text(), fixture.body);
assert.equal(server.requestCount(), 2);
const missing = await fetch(`${server.origin}/not-the-fixture.m3u`);
assert.equal(missing.status, 404);
assert.equal(server.requestCount(), 2);
} finally {
await server.close();
}
});
test('closes idempotently and rejects priming after close', async () => {
const server = await startSyntheticM3uServer(
createSyntheticM3uFixture(10_000)
);
await server.close();
await server.close();
await assert.rejects(server.prime(), /closed/i);
});
@@ -0,0 +1,114 @@
import { once } from 'node:events';
import { createServer, type Server } from 'node:http';
import type { SyntheticM3uFixture } from './synthetic-m3u';
export const SYNTHETIC_M3U_RESOURCE_PATH = '/synthetic-performance.m3u';
export interface SyntheticM3uServer {
readonly origin: string;
readonly resourceUrl: string;
close(): Promise<void>;
prime(): Promise<void>;
requestCount(): number;
}
export async function startSyntheticM3uServer(
fixture: SyntheticM3uFixture
): Promise<SyntheticM3uServer> {
let successfulRequestCount = 0;
const server = createServer((request, response) => {
const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1');
if (
request.method !== 'GET' ||
requestUrl.pathname !== SYNTHETIC_M3U_RESOURCE_PATH ||
requestUrl.search.length > 0
) {
response.writeHead(404, {
'Content-Length': '0',
'Content-Type': 'text/plain; charset=utf-8',
});
response.end();
return;
}
successfulRequestCount += 1;
response.writeHead(200, {
'Cache-Control': 'no-store',
'Content-Length': String(fixture.bytes),
'Content-Type': 'application/vnd.apple.mpegurl; charset=utf-8',
});
response.end(fixture.body);
});
server.listen({
exclusive: true,
host: '127.0.0.1',
port: 0,
});
try {
await once(server, 'listening');
} catch (error: unknown) {
await closeServer(server).catch(() => undefined);
throw error;
}
const address = server.address();
if (
address === null ||
typeof address === 'string' ||
address.address !== '127.0.0.1' ||
!Number.isSafeInteger(address.port) ||
address.port <= 0
) {
await closeServer(server);
throw new Error(
'Synthetic M3U server did not bind exact IPv4 loopback'
);
}
const origin = `http://127.0.0.1:${address.port}`;
const resourceUrl = `${origin}${SYNTHETIC_M3U_RESOURCE_PATH}`;
let closePromise: Promise<void> | null = null;
return Object.freeze({
origin,
resourceUrl,
close: () => {
closePromise ??= closeServer(server);
return closePromise;
},
prime: async () => {
if (closePromise !== null) {
throw new Error('Synthetic M3U server is closed');
}
const response = await fetch(resourceUrl);
const body = await response.arrayBuffer();
if (
response.status !== 200 ||
Number(response.headers.get('content-length')) !==
fixture.bytes ||
body.byteLength !== fixture.bytes
) {
throw new Error(
'Synthetic M3U server priming response was invalid'
);
}
},
requestCount: () => successfulRequestCount,
});
}
async function closeServer(server: Server): Promise<void> {
if (!server.listening) {
return;
}
await new Promise<void>((resolvePromise, rejectPromise) => {
server.close((error) => {
if (error) {
rejectPromise(error);
return;
}
resolvePromise();
});
});
}
@@ -0,0 +1,68 @@
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { describe, it } from 'node:test';
import {
createSyntheticM3uFixture,
SUPPORTED_SYNTHETIC_M3U_CHANNEL_COUNTS,
SYNTHETIC_M3U_SEED,
} from './synthetic-m3u';
const GOLDEN_SHA256 = {
10_000: '1d151ab2a878db8818f2bbfbdd689007cbe390182cdd1520c316bd163b7e7db3',
50_000: '4a5336a5d1c66b8b706752784815617a012499cb7a231ab35c88df9bd55ee78a',
100_000: '2ffe912f61ea106ba8de3414b0a927cdaa3264ac637d44c4b219f199eb94c353',
} as const;
describe('synthetic M3U performance fixture', () => {
it('generates each supported catalog deterministically with exact metadata', () => {
assert.deepEqual(
SUPPORTED_SYNTHETIC_M3U_CHANNEL_COUNTS,
[10_000, 50_000, 100_000]
);
assert.equal(SYNTHETIC_M3U_SEED, 240_724);
for (const channelCount of SUPPORTED_SYNTHETIC_M3U_CHANNEL_COUNTS) {
const fixture = createSyntheticM3uFixture(channelCount);
const lines = fixture.body.trimEnd().split('\n');
assert.equal(fixture.channelCount, channelCount);
assert.equal(
fixture.bytes,
Buffer.byteLength(fixture.body, 'utf8')
);
assert.equal(
fixture.sha256,
createHash('sha256').update(fixture.body, 'utf8').digest('hex')
);
assert.equal(fixture.sha256, GOLDEN_SHA256[channelCount]);
assert.equal(lines[0], '#EXTM3U');
assert.equal(lines.length, 1 + channelCount * 2);
assert.equal(
lines.filter((line) => line.startsWith('#EXTINF:')).length,
channelCount
);
assert.doesNotMatch(fixture.body, /\b(?:tvg-|logo|provider)\b/i);
assertOnlyLoopbackUrls(fixture.body);
}
});
it('rejects unsupported channel counts', () => {
assert.throws(
() => createSyntheticM3uFixture(9_999),
/Unsupported synthetic M3U channel count/
);
});
});
function assertOnlyLoopbackUrls(body: string): void {
const urls = body.match(/https?:\/\/[^\s"]+/g) ?? [];
for (const rawUrl of urls) {
const url = new URL(rawUrl);
assert.equal(url.protocol, 'http:');
assert.equal(url.hostname, '127.0.0.1');
assert.equal(url.username, '');
assert.equal(url.password, '');
}
}
@@ -2,19 +2,30 @@ import { createHash } from 'node:crypto';
export const SYNTHETIC_M3U_SEED = 240_724;
export const SYNTHETIC_M3U_CHANNEL_COUNT = 100_000;
export const SUPPORTED_SYNTHETIC_M3U_CHANNEL_COUNTS = [
10_000,
50_000,
SYNTHETIC_M3U_CHANNEL_COUNT,
] as const;
export type SyntheticM3uChannelCount =
(typeof SUPPORTED_SYNTHETIC_M3U_CHANNEL_COUNTS)[number];
export interface SyntheticM3uFixture {
readonly body: string;
readonly bytes: number;
readonly channelCount: number;
readonly channelCount: SyntheticM3uChannelCount;
readonly sha256: string;
}
export function createSyntheticM3uFixture(): SyntheticM3uFixture {
const lines = new Array<string>(1 + SYNTHETIC_M3U_CHANNEL_COUNT * 2);
export function createSyntheticM3uFixture(
channelCount: number = SYNTHETIC_M3U_CHANNEL_COUNT
): SyntheticM3uFixture {
assertSupportedChannelCount(channelCount);
const lines = new Array<string>(1 + channelCount * 2);
lines[0] = '#EXTM3U';
for (let offset = 0; offset < SYNTHETIC_M3U_CHANNEL_COUNT; offset += 1) {
for (let offset = 0; offset < channelCount; offset += 1) {
const index = offset + 1;
const group = (offset % 100) + 1;
const lineOffset = 1 + offset * 2;
@@ -34,7 +45,21 @@ export function createSyntheticM3uFixture(): SyntheticM3uFixture {
return Object.freeze({
body,
bytes: Buffer.byteLength(body, 'utf8'),
channelCount: SYNTHETIC_M3U_CHANNEL_COUNT,
channelCount,
sha256: createHash('sha256').update(body, 'utf8').digest('hex'),
});
}
function assertSupportedChannelCount(
channelCount: number
): asserts channelCount is SyntheticM3uChannelCount {
if (
!SUPPORTED_SYNTHETIC_M3U_CHANNEL_COUNTS.some(
(supported) => supported === channelCount
)
) {
throw new Error(
`Unsupported synthetic M3U channel count: ${channelCount}`
);
}
}
@@ -0,0 +1,107 @@
/* eslint-disable playwright/expect-expect -- These are Node assertion-based performance contract tests. */
import assert from 'node:assert/strict';
import test from 'node:test';
import { parseWorkerPerformancePhaseEvents } from './worker-performance-phase-events';
import { parseXtreamWorkerPerformancePhaseEvents } from './xtream-worker-phase-events';
function phasePair(
phase: string,
startEpochMs: number,
durationMs: number,
requestId = 'request-1'
) {
return [
{
boundary: 'start',
durationMs: null,
epochMs: startEpochMs,
phase,
requestId,
},
{
boundary: 'end',
durationMs,
epochMs: startEpochMs + 1,
metadata: { itemCount: 1 },
phase,
requestId,
},
];
}
test('accepts producer-shaped worker output when separately read clocks have a wider monotonic duration', () => {
const events = phasePair('sqlite.categories.read', 11, 1.6115);
const parsed = parseXtreamWorkerPerformancePhaseEvents(
'DB_GET_CATEGORIES',
events,
10,
30,
true
);
assert.deepEqual(parsed, events);
assert.equal(Number(events[1]?.epochMs) - Number(events[0]?.epochMs), 1);
});
test('rejects an Xtream worker duration outside its request envelope', () => {
assert.equal(
parseXtreamWorkerPerformancePhaseEvents(
'DB_GET_CATEGORIES',
phasePair('sqlite.categories.read', 11, 999),
10,
30,
true
),
null
);
});
test('rejects an out-of-envelope duration in a failed partial DB_SAVE_CONTENT capture', () => {
const events = [
...phasePair('sqlite.content.category-map-read', 11, 1),
...phasePair('normalize.content', 13, 999),
];
assert.equal(
parseXtreamWorkerPerformancePhaseEvents(
'DB_SAVE_CONTENT',
events,
10,
30,
false
),
null
);
});
test('accepts cross-clock skew but rejects an app-playlist duration outside the request envelope', () => {
const producerShaped = [
...phasePair('sqlite.read', 11, 1.6115),
...phasePair('deserialize.playlist', 13, 1),
];
assert.deepEqual(
parseWorkerPerformancePhaseEvents(
'DB_GET_APP_PLAYLIST',
producerShaped,
10,
30,
true
),
producerShaped
);
const outsideEnvelope = [
...phasePair('sqlite.read', 11, 20.001),
...phasePair('deserialize.playlist', 13, 1),
];
assert.equal(
parseWorkerPerformancePhaseEvents(
'DB_GET_APP_PLAYLIST',
outsideEnvelope,
10,
30,
true
),
null
);
});
@@ -0,0 +1,172 @@
import {
APP_PLAYLIST_GET_PERFORMANCE_PHASE,
APP_PLAYLIST_UPSERT_PERFORMANCE_PHASE,
type AppPlaylistPerformancePhase,
type DatabaseWorkerPerformancePhase,
type PerformancePhaseEvent,
type PerformancePhaseMetadata,
} from '@iptvnator/shared/interfaces';
import { fitsPerformanceDurationEnvelope } from './performance-phase-duration-envelope';
import { parseXtreamWorkerPerformancePhaseEvents } from './xtream-worker-phase-events';
const PHASE_SEQUENCES = {
DB_GET_APP_PLAYLIST: [
APP_PLAYLIST_GET_PERFORMANCE_PHASE.SQLITE_READ,
APP_PLAYLIST_GET_PERFORMANCE_PHASE.DESERIALIZE_PLAYLIST,
],
DB_UPSERT_APP_PLAYLIST: [
APP_PLAYLIST_UPSERT_PERFORMANCE_PHASE.SERIALIZE_PLAYLIST,
APP_PLAYLIST_UPSERT_PERFORMANCE_PHASE.SQLITE_WRITE,
],
} as const satisfies Readonly<
Record<string, readonly AppPlaylistPerformancePhase[]>
>;
const EVENT_KEYS = new Set([
'boundary',
'durationMs',
'epochMs',
'metadata',
'phase',
'requestId',
]);
export function parseWorkerPerformancePhaseEvents(
operation: string | null,
input: unknown,
workStartedEpochMs: number,
workEndedEpochMs: number,
requestSucceeded: boolean
): readonly PerformancePhaseEvent<DatabaseWorkerPerformancePhase>[] | null {
const xtreamEvents = parseXtreamWorkerPerformancePhaseEvents(
operation,
input,
workStartedEpochMs,
workEndedEpochMs,
requestSucceeded
);
if (xtreamEvents !== undefined) {
return xtreamEvents;
}
const phaseSequence = getPhaseSequence(operation);
if (phaseSequence.length === 0) {
return input === undefined ||
(Array.isArray(input) && input.length === 0)
? []
: null;
}
if (!Array.isArray(input) || input.length !== phaseSequence.length * 2) {
return null;
}
const parsed: PerformancePhaseEvent<AppPlaylistPerformancePhase>[] = [];
let lastEpochMs = workStartedEpochMs;
for (let index = 0; index < input.length; index += 1) {
const raw = input[index];
if (
!isRecord(raw) ||
Object.keys(raw).some((key) => !EVENT_KEYS.has(key))
) {
return null;
}
const boundary = raw['boundary'];
const durationMs = raw['durationMs'];
const epochMs = raw['epochMs'];
const metadata = parseMetadata(raw['metadata']);
const phase = raw['phase'];
const requestId = raw['requestId'];
if (
boundary !== (index % 2 === 0 ? 'start' : 'end') ||
phase !== phaseSequence[Math.floor(index / 2)] ||
typeof requestId !== 'string' ||
requestId.length === 0 ||
!isFiniteNonNegativeNumber(epochMs) ||
epochMs < lastEpochMs ||
epochMs > workEndedEpochMs ||
(boundary === 'start' &&
(durationMs !== null || metadata !== undefined)) ||
(boundary === 'end' && !isFiniteNonNegativeNumber(durationMs)) ||
metadata === null ||
(parsed.length > 0 && parsed[0]?.requestId !== requestId)
) {
return null;
}
const phaseStart = parsed[parsed.length - 1];
if (
boundary === 'end' &&
(!phaseStart ||
!fitsPerformanceDurationEnvelope(
Number(durationMs),
workStartedEpochMs,
workEndedEpochMs
))
) {
return null;
}
parsed.push({
boundary: boundary as 'end' | 'start',
durationMs: durationMs as number | null,
epochMs,
...(metadata === undefined ? {} : { metadata }),
phase: phase as AppPlaylistPerformancePhase,
requestId,
});
lastEpochMs = epochMs;
}
return parsed;
}
function getPhaseSequence(
operation: string | null
): readonly AppPlaylistPerformancePhase[] {
if (operation === 'DB_GET_APP_PLAYLIST') {
return PHASE_SEQUENCES.DB_GET_APP_PLAYLIST;
}
if (operation === 'DB_UPSERT_APP_PLAYLIST') {
return PHASE_SEQUENCES.DB_UPSERT_APP_PLAYLIST;
}
return [];
}
function parseMetadata(
input: unknown
): PerformancePhaseMetadata | undefined | null {
if (input === undefined) {
return undefined;
}
if (
!isRecord(input) ||
Object.keys(input).some(
(key) => key !== 'byteCount' && key !== 'itemCount'
)
) {
return null;
}
const byteCount = input['byteCount'];
const itemCount = input['itemCount'];
if (
(byteCount !== undefined && !isSafeCount(byteCount)) ||
(itemCount !== undefined && !isSafeCount(itemCount))
) {
return null;
}
return {
...(byteCount === undefined ? {} : { byteCount: Number(byteCount) }),
...(itemCount === undefined ? {} : { itemCount: Number(itemCount) }),
};
}
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 isSafeCount(value: unknown): boolean {
return Number.isSafeInteger(value) && Number(value) >= 0;
}
@@ -13,6 +13,7 @@ const validCapture = {
histogramFlushedEpochMs: 1_050,
invalidReason: null,
requestReceivedEpochMs: 1_000,
responsePostedEpochMs: 1_055,
threadCpuSystemMicros: 50,
threadCpuUnavailableReason: null,
threadCpuUserMicros: 100,
@@ -20,19 +21,25 @@ const validCapture = {
workStartedEpochMs: 1_010,
} as const;
function normalize(performanceCapture: unknown) {
function normalizeForOperation(operation: string, performanceCapture: unknown) {
return normalizeWorkerRequestPerformanceOutcome({
operation: 'DB_GET_APP_PLAYLIST',
ipcCallId: 2,
operation,
operationId: 'operation-42',
operationIdUnavailableReason: null,
performanceCapture,
playlistId: 'playlist-1',
requestId: 'request-1',
responseEpochMs: 1_060,
sourceEpochMs: 990,
success: true,
});
}
function normalize(performanceCapture: unknown) {
return normalizeForOperation('DB_HAS_CONTENT', performanceCapture);
}
function assertClosedCapture(
capture: WorkerRequestPerformanceMetrics,
reason: string
@@ -47,6 +54,7 @@ function assertClosedCapture(
capture.histogramFlushedEpochMs,
capture.invalidReason,
capture.requestReceivedEpochMs,
capture.responsePostedEpochMs,
capture.threadCpuSystemMicros,
capture.threadCpuUnavailableReason,
capture.threadCpuUserMicros,
@@ -55,6 +63,7 @@ function assertClosedCapture(
]) {
assert.equal(value, null);
}
assert.deepEqual(capture.phaseEvents, []);
}
test('absent captures are missing while present non-record captures are invalid', () => {
@@ -118,6 +127,9 @@ test('malformed nested fields and cross-field invariants fail the whole capture
histogramFlushedEpochMs: null,
},
{ ...validCapture, histogramFlushedEpochMs: 1_039 },
{ ...validCapture, responsePostedEpochMs: undefined },
{ ...validCapture, responsePostedEpochMs: 1_039 },
{ ...validCapture, responsePostedEpochMs: 1_061 },
{ ...validCapture, workStartedEpochMs: 999 },
{
...validCapture,
@@ -170,3 +182,205 @@ test('coherent metric-unavailable and invalid worker results remain valid raw ca
assert.equal(legitimateInvalid.performanceCaptureUnavailableReason, null);
assert.equal(legitimateInvalid.invalidReason, invalidReason);
});
test('requires the exact operation-specific worker phase sequence', () => {
const upsertPhaseEvents = [
{
boundary: 'start',
durationMs: null,
epochMs: 1_015,
phase: 'serialize.playlist',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 5,
epochMs: 1_020,
metadata: { byteCount: 2048, itemCount: 10_000 },
phase: 'serialize.playlist',
requestId: 'request-1',
},
{
boundary: 'start',
durationMs: null,
epochMs: 1_021,
phase: 'sqlite.write',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 12,
epochMs: 1_033,
metadata: { byteCount: 2048, itemCount: 1 },
phase: 'sqlite.write',
requestId: 'request-1',
},
] as const;
const getPhaseEvents = [
{
boundary: 'start',
durationMs: null,
epochMs: 1_015,
phase: 'sqlite.read',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 5,
epochMs: 1_020,
metadata: { itemCount: 1 },
phase: 'sqlite.read',
requestId: 'request-1',
},
{
boundary: 'start',
durationMs: null,
epochMs: 1_021,
phase: 'deserialize.playlist',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 12,
epochMs: 1_033,
metadata: { itemCount: 10_000 },
phase: 'deserialize.playlist',
requestId: 'request-1',
},
] as const;
assert.deepEqual(
normalizeForOperation('DB_UPSERT_APP_PLAYLIST', {
...validCapture,
phaseEvents: upsertPhaseEvents,
}).phaseEvents,
upsertPhaseEvents
);
assert.deepEqual(
normalizeForOperation('DB_GET_APP_PLAYLIST', {
...validCapture,
phaseEvents: getPhaseEvents,
}).phaseEvents,
getPhaseEvents
);
for (const invalidPhaseEvents of [
getPhaseEvents.slice(0, 2),
getPhaseEvents.slice(1),
[...getPhaseEvents].reverse(),
getPhaseEvents.map((event) => ({
...event,
requestId: 'other-request',
})),
[
getPhaseEvents[0],
{
...getPhaseEvents[1],
metadata: { itemCount: 1, secret: 'not allowed' },
},
],
[
getPhaseEvents[0],
{
...getPhaseEvents[1],
phase: 'arbitrary.phase',
},
],
]) {
assertClosedCapture(
normalizeForOperation('DB_GET_APP_PLAYLIST', {
...validCapture,
phaseEvents: invalidPhaseEvents,
}),
'worker-performance-capture-invalid'
);
}
for (const [operation, phaseEvents] of [
['DB_GET_APP_PLAYLIST', upsertPhaseEvents],
['DB_UPSERT_APP_PLAYLIST', getPhaseEvents],
['DB_GET_CONTENT', getPhaseEvents],
] as const) {
assertClosedCapture(
normalizeForOperation(operation, {
...validCapture,
phaseEvents,
}),
'worker-performance-capture-invalid'
);
}
for (const operation of ['DB_GET_APP_PLAYLIST', 'DB_UPSERT_APP_PLAYLIST']) {
assertClosedCapture(
normalizeForOperation(operation, validCapture),
'worker-performance-capture-invalid'
);
}
assert.deepEqual(normalize(validCapture).phaseEvents, []);
});
test('routes finalized Xtream phase sequences before the legacy M3U parser', () => {
const xtreamPhaseEvents = [
{
boundary: 'start',
durationMs: null,
epochMs: 1_015,
phase: 'sqlite.content.category-map-read',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 2,
epochMs: 1_017,
metadata: { itemCount: 60 },
phase: 'sqlite.content.category-map-read',
requestId: 'request-1',
},
{
boundary: 'start',
durationMs: null,
epochMs: 1_018,
phase: 'normalize.content',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 5,
epochMs: 1_023,
metadata: { itemCount: 60_000 },
phase: 'normalize.content',
requestId: 'request-1',
},
{
boundary: 'start',
durationMs: null,
epochMs: 1_024,
phase: 'sqlite.content.write-transactions',
requestId: 'request-1',
},
{
boundary: 'end',
durationMs: 10,
epochMs: 1_034,
metadata: { itemCount: 60_000 },
phase: 'sqlite.content.write-transactions',
requestId: 'request-1',
},
] as const;
assert.deepEqual(
normalizeForOperation('DB_SAVE_CONTENT', {
...validCapture,
phaseEvents: xtreamPhaseEvents,
}).phaseEvents,
xtreamPhaseEvents
);
assertClosedCapture(
normalizeForOperation('DB_SAVE_CONTENT', {
...validCapture,
phaseEvents: xtreamPhaseEvents.slice(0, -2),
}),
'worker-performance-capture-invalid'
);
});
@@ -35,9 +35,10 @@ function requestPerformance(
eventLoopDelayUnavailableReason: null,
eventLoopUtilization: p95Ms / 100,
eventLoopUtilizationUnavailableReason: null,
histogramFlushedEpochMs: 1_050 + p95Ms,
histogramFlushedEpochMs: 1_050,
invalidReason: null,
requestReceivedEpochMs: 1_000,
responsePostedEpochMs: 1_055,
threadCpuSystemMicros: threadCpuUserMicros / 2,
threadCpuUnavailableReason: null,
threadCpuUserMicros,
@@ -53,13 +54,15 @@ function requestTransport(
requestId = 'request-1'
) {
return {
operation: 'DB_GET_APP_PLAYLIST',
ipcCallId: 7,
operation: 'DB_HAS_CONTENT',
operationId: 'operation-42',
operationIdUnavailableReason: null,
performanceCapture,
playlistId: 'playlist-1',
requestId,
responseEpochMs: 1_060,
sourceEpochMs: 990,
success: true,
} as const;
}
@@ -77,6 +80,7 @@ function assertClosedCapture(
capture.histogramFlushedEpochMs,
capture.invalidReason,
capture.requestReceivedEpochMs,
capture.responsePostedEpochMs,
capture.threadCpuSystemMicros,
capture.threadCpuUnavailableReason,
capture.threadCpuUserMicros,
@@ -220,8 +224,10 @@ test('main capture retains raw request identity, timestamps, metrics, and reason
assert.match(source, /requestPerformance: \[\]/);
assert.match(source, /record\.requestPerformance\.map/);
assert.match(source, /operationId: request\.identity\.operationId/);
assert.match(source, /ipcCallId: request\.identity\.ipcCallId/);
assert.match(source, /identity: request\.identity/);
assert.match(source, /performanceCapture:\s+message\['performance'\]/);
assert.match(source, /sourceEpochMs:\s+request\.identity\.sourceEpochMs/);
assert.match(source, /captureGeneration: state\.captureGeneration/);
assert.doesNotMatch(
source,
@@ -229,6 +235,13 @@ test('main capture retains raw request identity, timestamps, metrics, and reason
);
});
test('normalized raw request metrics retain the exact preload IPC call identity', () => {
const capture = requestPerformance('request-1', 5, 100);
assert.equal(capture.ipcCallId, 7);
assert.equal(capture.sourceEpochMs, 990);
});
test('benchmark resolves one exact BrowserWindow and main capture never scans all windows', () => {
const benchmarkSource = readFileSync(
new URL('./m3u-refresh-cancellation.benchmark.ts', import.meta.url),
@@ -6,8 +6,10 @@ import type {
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;
@@ -15,6 +17,7 @@ export interface WorkerRequestPerformanceOutcomeTransport {
readonly playlistId: string | null;
readonly requestId: string | null;
readonly responseEpochMs: number;
readonly sourceEpochMs: number | null;
readonly success: boolean;
}
@@ -39,7 +42,9 @@ type ParsedWorkerPerformanceCapture = Pick<
| 'eventLoopUtilizationUnavailableReason'
| 'histogramFlushedEpochMs'
| 'invalidReason'
| 'phaseEvents'
| 'requestReceivedEpochMs'
| 'responsePostedEpochMs'
| 'threadCpuSystemMicros'
| 'threadCpuUnavailableReason'
| 'threadCpuUserMicros'
@@ -146,7 +151,9 @@ function parseEventLoopDelay(
}
function parseWorkerPerformanceCapture(
input: unknown
input: unknown,
operation: string | null,
requestSucceeded: boolean
): ParsedWorkerPerformanceCapture | null {
if (!isRecord(input)) {
return null;
@@ -161,6 +168,7 @@ function parseWorkerPerformanceCapture(
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'];
@@ -182,6 +190,7 @@ function parseWorkerPerformanceCapture(
!isNullableFiniteNonNegativeNumber(histogramFlushedEpochMs) ||
!isNullableReason(invalidReason, INVALID_REASONS) ||
!isFiniteNonNegativeNumber(requestReceivedEpochMs) ||
!isFiniteNonNegativeNumber(responsePostedEpochMs) ||
!isNullableFiniteNonNegativeNumber(threadCpuSystemMicros) ||
!isNullableReason(threadCpuUnavailableReason, THREAD_CPU_REASONS) ||
!isNullableFiniteNonNegativeNumber(threadCpuUserMicros) ||
@@ -191,11 +200,24 @@ function parseWorkerPerformanceCapture(
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);
(workEndedEpochMs <= histogramFlushedEpochMs &&
histogramFlushedEpochMs <= responsePostedEpochMs));
const eventLoopDelayIsCoherent =
eventLoopDelay === null
? eventLoopDelayUnavailableReason !== null &&
@@ -241,7 +263,9 @@ function parseWorkerPerformanceCapture(
eventLoopUtilizationUnavailableReason,
histogramFlushedEpochMs,
invalidReason,
phaseEvents,
requestReceivedEpochMs,
responsePostedEpochMs,
threadCpuSystemMicros,
threadCpuUnavailableReason,
threadCpuUserMicros,
@@ -254,12 +278,14 @@ 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 = (
@@ -275,7 +301,9 @@ export function normalizeWorkerRequestPerformanceOutcome(
invalidReason: null,
...identity,
performanceCaptureUnavailableReason: reason,
phaseEvents: [],
requestReceivedEpochMs: null,
responsePostedEpochMs: null,
threadCpuSystemMicros: null,
threadCpuUnavailableReason: null,
threadCpuUserMicros: null,
@@ -291,11 +319,28 @@ export function normalizeWorkerRequestPerformanceOutcome(
}
const performanceCapture = parseWorkerPerformanceCapture(
input.performanceCapture
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,
@@ -0,0 +1,78 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
XTREAM_SCENARIO_ID,
type XtreamScenarioId,
} from './xtream-benchmark-contract';
import { attributeXtreamPhases } from './xtream-phase-attribution';
import { scenarioCapture } from './xtream-phase-attribution.fixtures';
import {
expectedXtreamIpc,
expectedXtreamPhases,
XTREAM_ATTRIBUTION_PHASE as PHASE,
XTREAM_PHASE_SEMANTIC_TYPE as SEMANTIC,
} from './xtream-phase-inventory';
const EXPECTED_PROVIDER_CALLS: Readonly<Record<XtreamScenarioId, number>> = {
[XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE]: 8,
[XTREAM_SCENARIO_ID.REFRESH_LARGE]: 8,
[XTREAM_SCENARIO_ID.DELETE_LARGE]: 0,
[XTREAM_SCENARIO_ID.CANCEL_IMPORT]: 6,
[XTREAM_SCENARIO_ID.BACKGROUND_UI]: 8,
};
const EXPECTED_ACCOUNT_CALLS: Readonly<Record<XtreamScenarioId, number>> = {
[XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE]: 2,
[XTREAM_SCENARIO_ID.REFRESH_LARGE]: 2,
[XTREAM_SCENARIO_ID.DELETE_LARGE]: 0,
[XTREAM_SCENARIO_ID.CANCEL_IMPORT]: 2,
[XTREAM_SCENARIO_ID.BACKGROUND_UI]: 2,
};
describe('Xtream account phase inventory', () => {
it('counts every scenario-specific provider IPC and phase lifecycle', () => {
for (const scenarioId of Object.values(XTREAM_SCENARIO_ID)) {
const providerCalls = EXPECTED_PROVIDER_CALLS[scenarioId];
assert.equal(
expectedXtreamPhases(scenarioId).get(PHASE.NETWORK_TOTAL)
?.occurrences,
providerCalls
);
assert.equal(
expectedXtreamIpc(scenarioId).get('xtreamRequest')?.success ??
0,
providerCalls
);
}
});
it('fixtures contain the exact account lifecycle count and remain valid', () => {
for (const scenarioId of Object.values(XTREAM_SCENARIO_ID)) {
const capture = scenarioCapture(scenarioId);
const accounts = capture.events.filter(
({ boundary, phase, semanticType }) =>
boundary === 'start' &&
phase === PHASE.NETWORK_TOTAL &&
semanticType === SEMANTIC.ACCOUNT
);
assert.equal(accounts.length, EXPECTED_ACCOUNT_CALLS[scenarioId]);
assert.doesNotThrow(() => attributeXtreamPhases(capture));
}
});
it('allows the two independent fresh-add account phases to overlap', () => {
const capture = scenarioCapture(
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE
);
const events = capture.events.map((event) =>
event.correlationId === 'provider-account-route'
? { ...event, epochMs: event.epochMs - 6.5 }
: event
);
assert.doesNotThrow(() =>
attributeXtreamPhases({ ...capture, events })
);
});
});
@@ -0,0 +1,196 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
XTREAM_MAIN_PERFORMANCE_PHASE,
XTREAM_PRELOAD_PERFORMANCE_METHOD,
XtreamCodeActions,
} from '@iptvnator/shared/interfaces';
import {
createXtreamIpcMarkerCapture,
type XtreamIpcMarkerCapture,
type XtreamIpcMarkerCaptureOptions,
} from './xtream-ipc-marker-events';
const OPTIONS: XtreamIpcMarkerCaptureOptions = {
actions: Object.values(XtreamCodeActions),
mainPhases: Object.values(XTREAM_MAIN_PERFORMANCE_PHASE),
methods: Object.values(XTREAM_PRELOAD_PERFORMANCE_METHOD),
schemaVersion: 1,
};
describe('Xtream account phase correlation', () => {
it('accepts overlapping null-session calls and links main phases FIFO', () => {
const capture = createXtreamIpcMarkerCapture(OPTIONS);
capture.start(42, 90);
assert.equal(
capture.acceptPreload(42, 100, accountMarker(1, 99)),
true
);
assert.equal(
capture.acceptPreload(42, 102, accountMarker(2, 101)),
true
);
acceptAccountMainLifecycle(capture, 1, 103);
assert.equal(
capture.acceptPreload(42, 113, accountMarker(1, 112, 'end')),
true
);
acceptAccountMainLifecycle(capture, 2, 114);
assert.equal(
capture.acceptPreload(42, 124, accountMarker(2, 123, 'end')),
true
);
const snapshot = capture.stop();
assert.deepEqual(snapshot.invalidReasons, []);
assert.equal(snapshot.quarantinedKeyCount, 0);
const phases = snapshot.timeline.filter(
({ type }) => type === 'xtream-main-phase'
);
assert.deepEqual(
phases.map(({ action, ipcCallId }) => ({ action, ipcCallId })),
[
...Array.from({ length: 6 }, () => ({
action: XtreamCodeActions.GetAccountInfo,
ipcCallId: 1,
})),
...Array.from({ length: 6 }, () => ({
action: XtreamCodeActions.GetAccountInfo,
ipcCallId: 2,
})),
]
);
});
it('fails closed for duplicate and malformed preload call IDs', () => {
const capture = createXtreamIpcMarkerCapture(OPTIONS);
capture.start(42, 90);
assert.equal(
capture.acceptPreload(42, 100, accountMarker(1, 99)),
true
);
assert.equal(
capture.acceptPreload(42, 102, accountMarker(1, 101)),
false
);
assert.equal(
capture.acceptPreload(42, 104, accountMarker(0, 103)),
false
);
const snapshot = capture.stop();
assert.equal(snapshot.quarantinedKeyCount, 1);
assert.ok(
snapshot.invalidReasons.includes('preload-marker-duplicate-key')
);
});
});
function acceptAccountMainLifecycle(
capture: XtreamIpcMarkerCapture,
ipcCallId: number,
offset: number
): void {
const requestId = `xtream-account-${ipcCallId}`;
acceptMainBoundary(
capture,
requestId,
'xtream.network.total',
offset + 1,
'start'
);
acceptMainPair(
capture,
requestId,
'xtream.json.transform',
offset + 2,
offset + 3
);
acceptMainBoundary(
capture,
requestId,
'xtream.network.total',
offset + 6,
'end',
5
);
acceptMainPair(
capture,
requestId,
'xtream.response.ready',
offset + 7,
offset + 8
);
}
function acceptMainBoundary(
capture: XtreamIpcMarkerCapture,
requestId: string,
phase: string,
epochMs: number,
boundary: 'end' | 'start',
durationMs: number | null = null
): void {
assert.equal(
capture.acceptMainPhase({
boundary,
durationMs,
epochMs,
phase,
requestId,
}),
true
);
}
function acceptMainPair(
capture: XtreamIpcMarkerCapture,
requestId: string,
phase: string,
startEpochMs: number,
endEpochMs: number
): void {
assert.equal(
capture.acceptMainPhase({
boundary: 'start',
durationMs: null,
epochMs: startEpochMs,
phase,
requestId,
}),
true
);
assert.equal(
capture.acceptMainPhase({
boundary: 'end',
durationMs: endEpochMs - startEpochMs,
epochMs: endEpochMs,
phase,
requestId,
}),
true
);
}
function accountMarker(
ipcCallId: number,
sourceEpochMs: number,
boundary: 'end' | 'start' = 'start'
): Record<string, unknown> {
return {
schemaVersion: 1,
sourceEpochMs,
ipcCallId,
method: 'xtreamRequest',
boundary,
outcome: boundary === 'start' ? null : 'success',
playlistId: null,
operationId: null,
sessionId: null,
action: XtreamCodeActions.GetAccountInfo,
categoryType: null,
contentType: null,
itemCount: null,
};
}
@@ -0,0 +1,15 @@
export function deriveXtreamAcquisitionDurationMs(
networkTotalDurationMs: number,
jsonTransformDurationMs: number
): number {
if (
!Number.isFinite(networkTotalDurationMs) ||
networkTotalDurationMs < 0 ||
!Number.isFinite(jsonTransformDurationMs) ||
jsonTransformDurationMs < 0 ||
jsonTransformDurationMs > networkTotalDurationMs
) {
throw new Error('xtream-acquisition-duration-invalid');
}
return networkTotalDurationMs - jsonTransformDurationMs;
}
@@ -0,0 +1,55 @@
import { join } from 'node:path';
import {
serializeXtreamArtifactForPersistence,
type XtreamDiagnosticArtifactEvidence,
} from './xtream-diagnostic-artifacts';
import { writeXtreamExclusiveArtifact } from './xtream-exclusive-artifact-writer';
import { persistThenValidateXtreamIteration } from './xtream-benchmark-report';
import { XTREAM_SYNTHETIC_CREDENTIALS } from './xtream-scenario-driver';
import type {
XtreamArtifactSafetyContext,
XtreamRawIterationResult,
XtreamValidatedIterationResult,
} from './xtream-summary-contract';
export interface PersistedXtreamIteration {
readonly diagnosticEvidence: XtreamDiagnosticArtifactEvidence | null;
readonly iteration: XtreamValidatedIterationResult;
}
export function createXtreamArtifactSafetyContext(
controlToken: string
): XtreamArtifactSafetyContext {
return Object.freeze({
controlToken,
syntheticCredentials: XTREAM_SYNTHETIC_CREDENTIALS,
});
}
export async function writeXtreamSafeJson(
absolutePath: string,
value: unknown,
safety: XtreamArtifactSafetyContext
): Promise<void> {
const serialized = serializeXtreamArtifactForPersistence(value, safety);
await writeXtreamExclusiveArtifact(absolutePath, serialized);
}
export async function persistXtreamIteration(
iterationDirectory: string,
raw: XtreamRawIterationResult,
diagnosticEvidence: XtreamDiagnosticArtifactEvidence | null,
safety: XtreamArtifactSafetyContext
): Promise<PersistedXtreamIteration> {
const iteration = await persistThenValidateXtreamIteration(
raw,
safety,
(serialized) =>
writeXtreamExclusiveArtifact(
join(iterationDirectory, 'result.json'),
serialized
)
);
return Object.freeze({ diagnosticEvidence, iteration });
}
@@ -0,0 +1,387 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { mkdir, mkdtemp, rm, stat } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { afterEach, describe, it } from 'node:test';
import { workspaceRoot } from '../electron-test-fixtures';
import { createXtreamIterationDefinitions } from './xtream-benchmark-contract';
import { BUILD_IDENTITY } from './xtream-benchmark-report.fixtures';
import {
type XtreamBenchmarkConfiguration,
assertXtreamBenchmarkBuildUnchanged,
assertXtreamBenchmarkOutputPathHasNoSymlinks,
createXtreamBenchmarkManifest,
resolveXtreamBenchmarkCdpPort,
resolveXtreamBenchmarkConfiguration,
resolveXtreamBenchmarkOutputLayout,
XTREAM_BENCHMARK_MOCK_ORIGIN,
} from './xtream-benchmark-configuration';
import { XTREAM_MEMORY_ACCOUNTING } from './xtream-summary-contract';
const SOURCE = Object.freeze({
gitCommit: '0123456789abcdef0123456789abcdef01234567',
gitDiffSha256:
'abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd',
gitDirty: false,
});
const FIXTURE = Object.freeze({
bytes: 12_345,
catalogSha256:
'1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
counts: {
categories: { live: 60, vod: 20, series: 20, total: 100 },
items: {
live: 60_000,
vod: 20_000,
series: 20_000,
total: 100_000,
},
},
epoch: 1,
scenario: 'performance-100k',
seed: 91_001,
} as const);
const temporaryDirectories: string[] = [];
afterEach(async () => {
await Promise.all(
temporaryDirectories
.splice(0)
.map((directory) => rm(directory, { force: true, recursive: true }))
);
});
describe('Xtream benchmark output configuration', () => {
it('accepts only a fresh timestamp directory directly below dist/performance', async () => {
const repositoryRoot = await createRepositoryRoot();
const performanceRoot = join(repositoryRoot, 'dist', 'performance');
const outputRoot = join(
performanceRoot,
'20260728T123456Z-xtream-smoke'
);
assert.deepEqual(
resolveXtreamBenchmarkOutputLayout(
repositoryRoot,
outputRoot,
'baseline'
),
{
outputDirectory: join(outputRoot, 'baseline'),
outputRoot,
performanceRoot,
}
);
for (const requestedRoot of [
'dist/performance/20260728T123456Z',
performanceRoot,
join(performanceRoot, 'nested', '20260728T123456Z'),
join(performanceRoot, 'not-a-timestamp'),
join(repositoryRoot, 'outside', '20260728T123456Z'),
]) {
assert.throws(
() =>
resolveXtreamBenchmarkOutputLayout(
repositoryRoot,
requestedRoot,
'baseline'
),
/absolute fresh timestamp path below dist\/performance/
);
}
assert.throws(
() =>
resolveXtreamBenchmarkOutputLayout(
repositoryRoot,
outputRoot,
'../after'
),
/safe Xtream variant/
);
});
it('rejects every existing symbolic-link component', async () => {
const repositoryRoot = await createRepositoryRoot();
const realDist = join(repositoryRoot, 'real-dist');
await mkdir(realDist);
await import('node:fs/promises').then(({ symlink }) =>
symlink(realDist, join(repositoryRoot, 'dist'))
);
await assert.rejects(
() =>
assertXtreamBenchmarkOutputPathHasNoSymlinks(
repositoryRoot,
join(
repositoryRoot,
'dist',
'performance',
'20260728T123456Z'
)
),
/contains a symbolic link/
);
});
});
describe('Xtream benchmark mode configuration', () => {
it('pins formal CDP to 9222 and bounds smoke-only alternate ports', () => {
assert.equal(resolveXtreamBenchmarkCdpPort('formal', undefined), 9222);
assert.equal(resolveXtreamBenchmarkCdpPort('smoke', '9322'), 9322);
assert.throws(
() => resolveXtreamBenchmarkCdpPort('formal', '9322'),
/Formal Xtream performance runs require CDP port 9222/
);
for (const requested of ['1023', '65536', '9322.5', '0x246a']) {
assert.throws(
() => resolveXtreamBenchmarkCdpPort('smoke', requested),
/IPTVNATOR_PERF_CDP_PORT is invalid/
);
}
});
it('creates output only after preflight and uses exclusive child mkdir calls', async () => {
const repositoryRoot = await createRepositoryRoot();
const performanceRoot = join(repositoryRoot, 'dist', 'performance');
await mkdir(performanceRoot, { recursive: true });
const outputRoot = join(
performanceRoot,
'20260728T123456Z-xtream-smoke'
);
const calls: string[] = [];
const configuration = await resolveXtreamBenchmarkConfiguration({
dependencies: {
assertCapacity: async () => {
calls.push('preflight');
return {
availableBytes: BigInt(3 * 1_024 ** 3),
checkedPath: performanceRoot,
minimumBytes: BigInt(2 * 1_024 ** 3),
};
},
captureBuildIdentity: async () => BUILD_IDENTITY,
mkdir: async (path, options) => {
calls.push(
`mkdir:${path}:${String(
options?.recursive ?? false
)}:${String(options?.mode ?? 'default')}`
);
await mkdir(path, options);
},
readElectronVersion: async () => '41.0.0',
readGitSourceState: () => SOURCE,
},
environment: {
IPTVNATOR_PERF_CDP_PORT: '9322',
IPTVNATOR_PERF_OUTPUT_DIR: outputRoot,
IPTVNATOR_PERF_SMOKE: '1',
IPTVNATOR_PERF_VARIANT: 'smoke',
IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN: 'a'.repeat(64),
},
workspaceRoot: repositoryRoot,
});
assert.equal(calls[0], 'preflight');
assert.deepEqual(calls.slice(1), [
`mkdir:${performanceRoot}:true:${0o700}`,
`mkdir:${outputRoot}:false:${0o700}`,
`mkdir:${join(outputRoot, 'smoke')}:false:${0o700}`,
]);
assert.equal((await stat(outputRoot)).mode & 0o777, 0o700);
assert.equal(
(await stat(join(outputRoot, 'smoke'))).mode & 0o777,
0o700
);
assert.equal(configuration.mode, 'smoke');
assert.equal(configuration.measuredRuns, 1);
assert.equal(configuration.outputDirectory, join(outputRoot, 'smoke'));
assert.equal(configuration.mockOrigin, XTREAM_BENCHMARK_MOCK_ORIGIN);
assert.equal(
createXtreamIterationDefinitions(configuration.smoke)
.filter((entry) => entry.kind === 'measured')
.every((entry) => !entry.eligibleForComparison),
true
);
});
it('fails formal resolution before preflight when tracked source is dirty', async () => {
const repositoryRoot = await createRepositoryRoot();
let preflightCalled = false;
await assert.rejects(
() =>
resolveXtreamBenchmarkConfiguration({
dependencies: {
assertCapacity: async () => {
preflightCalled = true;
throw new Error('unexpected preflight');
},
captureBuildIdentity: async () => BUILD_IDENTITY,
readElectronVersion: async () => '41.0.0',
readGitSourceState: () => ({
...SOURCE,
gitDirty: true,
}),
},
environment: {
IPTVNATOR_PERF_OUTPUT_DIR: join(
repositoryRoot,
'dist',
'performance',
'20260728T123456Z-xtream'
),
IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN: 'b'.repeat(64),
},
workspaceRoot: repositoryRoot,
}),
/Formal Xtream performance runs require a clean Git worktree/
);
assert.equal(preflightCalled, false);
});
});
describe('Xtream benchmark manifest provenance', () => {
it('creates an exact manifest from the canonical prepared mock fixture only', () => {
const configuration = configurationFixture();
const manifest = createXtreamBenchmarkManifest(configuration, FIXTURE);
assert.equal(manifest.fixture.categories, 100);
assert.equal(manifest.fixture.items, 100_000);
assert.deepEqual(manifest.build, BUILD_IDENTITY);
assert.deepEqual(manifest.source, SOURCE);
assert.deepEqual(manifest.memoryAccounting, XTREAM_MEMORY_ACCOUNTING);
assert.equal(
JSON.stringify(manifest).includes(configuration.controlToken),
false
);
assert.throws(
() =>
createXtreamBenchmarkManifest(configuration, {
...FIXTURE,
scenario: 'large',
}),
/invalid Xtream performance fixture/
);
});
it('recaptures and requires byte-identical build identity at suite end', async () => {
const configuration = configurationFixture();
await assert.doesNotReject(() =>
assertXtreamBenchmarkBuildUnchanged(configuration, async () =>
structuredClone(BUILD_IDENTITY)
)
);
await assert.rejects(
() =>
assertXtreamBenchmarkBuildUnchanged(
configuration,
async () => ({
...BUILD_IDENTITY,
renderer: {
...BUILD_IDENTITY.renderer,
aggregateSha256: 'f'.repeat(64),
},
})
),
/Xtream performance build changed during the suite/
);
});
});
describe('Xtream Playwright and Nx plumbing', () => {
it('uses one isolated control-enabled mock and one no-retry 30-minute test', async () => {
const configPath = join(
workspaceRoot,
'apps/electron-backend-e2e/playwright.xtream-performance.config.ts'
);
const source = await readFile(configPath, 'utf8');
assert.match(source, /randomBytes\(32\)/);
assert.match(
source,
/process\.env\['IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN'\]\s*\?\?/
);
assert.match(source, /http:\/\/127\.0\.0\.1:3221\/health/);
assert.match(source, /reuseExistingServer:\s*false/);
assert.match(source, /IPTVNATOR_XTREAM_MOCK_CONTROL:\s*'1'/);
assert.match(
source,
/IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN:\s*controlToken/
);
assert.match(source, /HOST:\s*'127\.0\.0\.1'/);
assert.match(source, /PORT:\s*'3221'/);
assert.match(source, /retries:\s*0/);
assert.match(source, /workers:\s*1/);
assert.match(source, /timeout:\s*30\s*\*\s*60\s*\*\s*1_000/);
});
it('pins the Nx target and keeps the entrypoint to one orchestrator call', async () => {
const [project, entrypoint] = await Promise.all([
readFile(
join(workspaceRoot, 'apps/electron-backend-e2e/project.json'),
'utf8'
).then(
(value) =>
JSON.parse(value) as {
targets: Record<string, Record<string, unknown>>;
}
),
readFile(
join(
workspaceRoot,
'apps/electron-backend-e2e/src/xtream.performance.ts'
),
'utf8'
),
]);
const target = project.targets['benchmark-xtream'];
assert.deepEqual(target?.['dependsOn'], [
'electron-backend:build-performance',
]);
assert.equal(target?.['cache'], false);
assert.equal(target?.['parallelism'], false);
assert.equal(
(target?.['options'] as Record<string, unknown>)?.['command'],
'pnpm exec playwright test --config=playwright.xtream-performance.config.ts src/xtream.performance.ts'
);
assert.equal(
(entrypoint.match(/runXtreamBenchmark\(\)/g) ?? []).length,
1
);
assert.equal((entrypoint.match(/\btest\(/g) ?? []).length, 1);
});
});
async function createRepositoryRoot(): Promise<string> {
const directory = await mkdtemp(join(tmpdir(), 'iptvnator-xtream-config-'));
temporaryDirectories.push(directory);
return directory;
}
function configurationFixture(): XtreamBenchmarkConfiguration {
return {
build: BUILD_IDENTITY,
cdpPort: 9222,
controlToken: 'c'.repeat(64),
electronVersion: '41.0.0',
measuredRuns: 5,
mockOrigin: XTREAM_BENCHMARK_MOCK_ORIGIN,
mode: 'formal' as const,
outputDirectory: resolve('/tmp/xtream/baseline'),
outputRoot: resolve('/tmp/xtream'),
runtime: {
arch: 'arm64',
electronVersion: '41.0.0',
harnessNodeVersion: '22.0.0',
platform: 'darwin',
},
smoke: false,
source: SOURCE,
variant: 'baseline',
workspaceRoot: resolve('/repository'),
};
}
@@ -0,0 +1,278 @@
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { isDeepStrictEqual } from 'node:util';
import { mkdir, readFile } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { workspaceRoot as defaultWorkspaceRoot } from '../electron-test-fixtures';
import type {
XtreamBenchmarkManifest,
XtreamBenchmarkRuntime,
XtreamBenchmarkSource,
XtreamPerformanceManifest,
} from './xtream-benchmark-contract';
import { parseXtreamBenchmarkManifest } from './xtream-benchmark-manifest-schema';
import {
captureXtreamBuildIdentity,
type XtreamBenchmarkBuildIdentity,
} from './xtream-build-identity';
import { parseXtreamBenchmarkBuildIdentity } from './xtream-build-identity-schema';
import { parseXtreamManifest } from './xtream-control-state-schema';
import {
assertXtreamBenchmarkOutputPathHasNoSymlinks,
createExclusiveXtreamBenchmarkOutputLayout,
resolveXtreamBenchmarkCdpPort,
resolveXtreamBenchmarkOutputLayout,
type XtreamMkdir,
} from './xtream-benchmark-output-configuration';
import {
assertPerformanceArtifactCapacity,
type PerformanceArtifactCapacity,
} from './performance-artifact-preflight';
import { XTREAM_MEMORY_ACCOUNTING } from './xtream-summary-contract';
export const XTREAM_BENCHMARK_MOCK_ORIGIN = 'http://127.0.0.1:3221';
const CONTROL_TOKEN = /^[A-Za-z0-9_-]{43,128}$/u;
export {
assertXtreamBenchmarkOutputPathHasNoSymlinks,
resolveXtreamBenchmarkCdpPort,
resolveXtreamBenchmarkOutputLayout,
XTREAM_BENCHMARK_FORMAL_CDP_PORT,
} from './xtream-benchmark-output-configuration';
export interface XtreamBenchmarkConfiguration {
readonly build: XtreamBenchmarkBuildIdentity;
readonly cdpPort: number;
/** Control-plane secret. Never serialize or log this field. */
readonly controlToken: string;
readonly electronVersion: string;
readonly measuredRuns: 1 | 5;
readonly mockOrigin: typeof XTREAM_BENCHMARK_MOCK_ORIGIN;
readonly mode: 'formal' | 'smoke';
readonly outputDirectory: string;
readonly outputRoot: string;
readonly runtime: XtreamBenchmarkRuntime;
readonly smoke: boolean;
readonly source: XtreamBenchmarkSource;
readonly variant: string;
readonly workspaceRoot: string;
}
type XtreamGitSourceState = XtreamBenchmarkSource;
interface XtreamConfigurationDependencies {
readonly assertCapacity?: (
targetPath: string
) => Promise<PerformanceArtifactCapacity>;
readonly captureBuildIdentity?: (
workspaceRoot: string
) => Promise<XtreamBenchmarkBuildIdentity>;
readonly mkdir?: XtreamMkdir;
readonly readElectronVersion?: (workspaceRoot: string) => Promise<string>;
readonly readGitSourceState?: (
workspaceRoot: string
) => XtreamGitSourceState;
}
export interface ResolveXtreamBenchmarkConfigurationOptions {
readonly dependencies?: XtreamConfigurationDependencies;
readonly environment?: NodeJS.ProcessEnv;
readonly workspaceRoot?: string;
}
export async function resolveXtreamBenchmarkConfiguration(
options: ResolveXtreamBenchmarkConfigurationOptions = {}
): Promise<XtreamBenchmarkConfiguration> {
const environment = options.environment ?? process.env;
const repositoryRoot = resolve(
options.workspaceRoot ?? defaultWorkspaceRoot
);
const requestedRoot = environment['IPTVNATOR_PERF_OUTPUT_DIR'];
if (!requestedRoot) {
throw new Error('IPTVNATOR_PERF_OUTPUT_DIR is required');
}
const smoke = resolveSmokeMode(environment['IPTVNATOR_PERF_SMOKE']);
const mode = smoke ? 'smoke' : 'formal';
const variant = environment['IPTVNATOR_PERF_VARIANT'] ?? 'baseline';
const layout = resolveXtreamBenchmarkOutputLayout(
repositoryRoot,
requestedRoot,
variant
);
const cdpPort = resolveXtreamBenchmarkCdpPort(
mode,
environment['IPTVNATOR_PERF_CDP_PORT']
);
const controlToken =
environment['IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN'] ?? '';
if (!CONTROL_TOKEN.test(controlToken)) {
throw new Error(
'Xtream benchmark requires a high-entropy mock control token'
);
}
const dependencies = options.dependencies ?? {};
const readSource = dependencies.readGitSourceState ?? readGitSourceState;
const source = Object.freeze(readSource(repositoryRoot));
assertSourceState(mode, source);
const captureBuild =
dependencies.captureBuildIdentity ?? captureXtreamBuildIdentity;
const build = parseXtreamBenchmarkBuildIdentity(
await captureBuild(repositoryRoot)
);
const electronVersion = await (
dependencies.readElectronVersion ?? readElectronRuntimeVersion
)(repositoryRoot);
if (!electronVersion.trim()) {
throw new Error('Unable to resolve the Electron runtime version');
}
await assertXtreamBenchmarkOutputPathHasNoSymlinks(
repositoryRoot,
layout.outputRoot
);
await (dependencies.assertCapacity ?? assertPerformanceArtifactCapacity)(
layout.outputDirectory
);
await createExclusiveXtreamBenchmarkOutputLayout(
repositoryRoot,
layout,
dependencies.mkdir ?? mkdir
);
const runtime = Object.freeze({
arch: process.arch,
electronVersion,
harnessNodeVersion: process.versions.node,
platform: process.platform,
});
return Object.freeze({
build,
cdpPort,
controlToken,
electronVersion,
measuredRuns: smoke ? 1 : 5,
mockOrigin: XTREAM_BENCHMARK_MOCK_ORIGIN,
mode,
outputDirectory: layout.outputDirectory,
outputRoot: layout.outputRoot,
runtime,
smoke,
source,
variant,
workspaceRoot: repositoryRoot,
});
}
export function createXtreamBenchmarkManifest(
configuration: XtreamBenchmarkConfiguration,
preparedFixture: unknown
): XtreamBenchmarkManifest {
let fixture: XtreamPerformanceManifest;
try {
fixture = parseXtreamManifest(preparedFixture);
} catch {
throw new Error('invalid Xtream performance fixture');
}
return parseXtreamBenchmarkManifest({
build: configuration.build,
environment: {
cdpAddress: '127.0.0.1',
cdpPort: configuration.cdpPort,
databaseWorkerBatchDelayMs: 0,
exposeGc: true,
freshUserDataPerIteration: true,
perfCapture: '1',
perfWorkerProfiling: '1',
traceRendererConsole: '0',
},
fixture: {
bytes: fixture.bytes,
catalogSha256: fixture.catalogSha256,
categories: fixture.counts.categories.total,
items: fixture.counts.items.total,
scenario: fixture.scenario,
seed: fixture.seed,
},
memoryAccounting: XTREAM_MEMORY_ACCOUNTING,
mode: configuration.mode,
runtime: configuration.runtime,
source: configuration.source,
suite: 'xtream-performance-100k',
variant: configuration.variant,
});
}
export async function assertXtreamBenchmarkBuildUnchanged(
configuration: XtreamBenchmarkConfiguration,
capture: (
workspaceRoot: string
) => Promise<XtreamBenchmarkBuildIdentity> = captureXtreamBuildIdentity
): Promise<void> {
let current: XtreamBenchmarkBuildIdentity;
try {
current = parseXtreamBenchmarkBuildIdentity(
await capture(configuration.workspaceRoot)
);
} catch {
throw new Error('Xtream performance build changed during the suite');
}
if (!isDeepStrictEqual(configuration.build, current)) {
throw new Error('Xtream performance build changed during the suite');
}
}
function resolveSmokeMode(value: string | undefined): boolean {
if (value === undefined) return false;
if (value === '1') return true;
throw new Error('IPTVNATOR_PERF_SMOKE is invalid');
}
function assertSourceState(
mode: 'formal' | 'smoke',
source: XtreamGitSourceState
): void {
if (mode === 'formal' && source.gitDirty) {
throw new Error(
'Formal Xtream performance runs require a clean Git worktree'
);
}
}
function readGitSourceState(repositoryRoot: string): XtreamGitSourceState {
const git = (arguments_: readonly string[]): string =>
execFileSync('git', [...arguments_], {
cwd: repositoryRoot,
encoding: 'utf8',
});
const gitCommit = git(['rev-parse', 'HEAD']).trim();
const status = git(['status', '--porcelain=v1', '--untracked-files=all']);
const gitDiffSha256 = createHash('sha256')
.update(status)
.update('\0')
.update(git(['diff', '--binary', 'HEAD']))
.update('\0')
.update(git(['diff', '--binary', '--cached', 'HEAD']))
.digest('hex');
return Object.freeze({
gitCommit,
gitDiffSha256,
gitDirty: status.trim().length > 0,
});
}
async function readElectronRuntimeVersion(
repositoryRoot: string
): Promise<string> {
const value = JSON.parse(
await readFile(
join(repositoryRoot, 'node_modules', 'electron', 'package.json'),
'utf8'
)
) as { version?: unknown };
if (typeof value.version !== 'string' || !value.version.trim()) {
throw new Error('Unable to resolve the Electron runtime version');
}
return value.version;
}
@@ -0,0 +1,361 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
assertXtreamArtifactRedacted,
assertXtreamControlReadyAfterObservationReset,
assertXtreamControlStateForScenario,
assertXtreamManifest,
assertXtreamManifestIdentity,
assertXtreamMockOrigin,
createXtreamIterationDefinitions,
listXtreamBenchmarkScenarios,
XTREAM_ITERATION_KIND,
XTREAM_SCENARIO_ID,
type XtreamControlState,
type XtreamPerformanceManifest,
} from './xtream-benchmark-contract';
const MANIFEST: XtreamPerformanceManifest = {
epoch: 1,
scenario: 'performance-100k',
seed: 91_001,
counts: {
categories: { live: 60, vod: 20, series: 20, total: 100 },
items: {
live: 60_000,
vod: 20_000,
series: 20_000,
total: 100_000,
},
},
bytes: 12_345_678,
catalogSha256:
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
};
describe('Xtream benchmark contract', () => {
it('defines exactly the five requested scenarios', () => {
assert.deepEqual(
listXtreamBenchmarkScenarios().map(({ id }) => id),
[
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE,
XTREAM_SCENARIO_ID.REFRESH_LARGE,
XTREAM_SCENARIO_ID.DELETE_LARGE,
XTREAM_SCENARIO_ID.CANCEL_IMPORT,
XTREAM_SCENARIO_ID.BACKGROUND_UI,
]
);
});
it('schedules warm-up, five comparable measurements, and one diagnostic', () => {
const definitions = createXtreamIterationDefinitions(false);
assert.equal(definitions.length, 35);
for (const { id } of listXtreamBenchmarkScenarios()) {
const runs = definitions.filter(
(definition) => definition.scenarioId === id
);
assert.deepEqual(
runs.map(({ eligibleForComparison, kind, runId }) => ({
eligibleForComparison,
kind,
runId,
})),
[
{
eligibleForComparison: false,
kind: XTREAM_ITERATION_KIND.WARMUP,
runId: 'warmup-01',
},
...Array.from({ length: 5 }, (_, index) => ({
eligibleForComparison: true,
kind: XTREAM_ITERATION_KIND.MEASURED,
runId: `run-${String(index + 1).padStart(2, '0')}`,
})),
{
eligibleForComparison: false,
kind: XTREAM_ITERATION_KIND.DIAGNOSTIC,
runId: 'diagnostic',
},
]
);
}
});
it('keeps smoke measurements ineligible for formal comparison', () => {
const definitions = createXtreamIterationDefinitions(true);
assert.equal(definitions.length, 15);
assert.ok(
definitions.every((definition) => !definition.eligibleForComparison)
);
for (const { id } of listXtreamBenchmarkScenarios()) {
assert.deepEqual(
definitions
.filter((definition) => definition.scenarioId === id)
.map(({ kind, runId }) => ({ kind, runId })),
[
{
kind: XTREAM_ITERATION_KIND.WARMUP,
runId: 'warmup-01',
},
{
kind: XTREAM_ITERATION_KIND.MEASURED,
runId: 'run-01',
},
{
kind: XTREAM_ITERATION_KIND.DIAGNOSTIC,
runId: 'diagnostic',
},
]
);
}
});
it('accepts only an exact credential-free HTTP loopback origin', () => {
assert.doesNotThrow(() =>
assertXtreamMockOrigin('http://127.0.0.1:43123')
);
for (const unsafe of [
'https://127.0.0.1:43123',
'http://localhost:43123',
'http://127.0.0.1',
'http://127.0.0.1:43123/',
'http://user:pass@127.0.0.1:43123',
'http://127.0.0.1:43123/player_api.php',
'http://127.0.0.1:43123?token=secret',
'http://127.0.0.1:43123#fragment',
]) {
assert.throws(
() => assertXtreamMockOrigin(unsafe),
/exact Xtream HTTP loopback origin/
);
}
});
it('rejects unexpected fixture names, counts, hashes, and identity drift', () => {
assert.doesNotThrow(() => assertXtreamManifest(MANIFEST));
for (const invalid of [
{ ...MANIFEST, scenario: 'provider-data' },
{ ...MANIFEST, seed: 42 },
{
...MANIFEST,
counts: {
...MANIFEST.counts,
items: { ...MANIFEST.counts.items, total: 99_999 },
},
},
{ ...MANIFEST, catalogSha256: 'not-a-hash' },
{ ...MANIFEST, username: 'performance' },
]) {
assert.throws(
() => assertXtreamManifest(invalid),
/invalid Xtream performance manifest/
);
}
assert.doesNotThrow(() =>
assertXtreamManifestIdentity(MANIFEST, {
...MANIFEST,
epoch: 2,
})
);
assert.throws(
() =>
assertXtreamManifestIdentity(MANIFEST, {
...MANIFEST,
epoch: 2,
bytes: MANIFEST.bytes + 1,
}),
/fixture identity changed/
);
});
it('requires a clean, same-epoch pre-trigger state after observation reset', () => {
const ready = controlState([]);
assert.doesNotThrow(() =>
assertXtreamControlReadyAfterObservationReset(ready, MANIFEST)
);
for (const invalid of [
{ ...ready, epoch: 2 },
{ ...ready, rules: [{ kind: 'barrier' }] },
{ ...ready, heldCount: 1, heldIds: ['leftover'] },
controlState([['get_account_info', 'all', 1]]),
]) {
assert.throws(
() =>
assertXtreamControlReadyAfterObservationReset(
invalid,
MANIFEST
),
/invalid Xtream mock readiness/
);
}
});
it('allows category requests to arrive in any parallel order', () => {
assert.doesNotThrow(() =>
assertXtreamControlStateForScenario(
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE,
controlState(
[
['get_account_info', 'all', 1],
['get_series_categories', 'all', 2],
['get_vod_categories', 'all', 3],
['get_live_categories', 'all', 4],
['get_live_streams', 'all', 5],
['get_vod_streams', 'all', 6],
['get_series', 'all', 7],
],
2
),
MANIFEST
)
);
});
it('fails closed on action drift, proxy traffic, residue, and ordering drift', () => {
const cases: XtreamControlState[] = [
controlState(fullActions().slice(0, -1), 2),
controlState([...fullActions(), ['get_vod_info', 'all', 8]], 2),
{
...controlState(fullActions(), 2),
occurrences: controlState(fullActions(), 2).occurrences.map(
(entry, index) =>
index === 0 ? { ...entry, transport: 'proxy' } : entry
),
},
{
...controlState(fullActions(), 2),
rules: [{ kind: 'delay' }],
},
{
...controlState(fullActions(), 2),
heldCount: 1,
heldIds: ['leftover'],
},
controlState(
[
['get_account_info', 'all', 1],
['get_live_streams', 'all', 2],
['get_live_categories', 'all', 3],
['get_vod_categories', 'all', 4],
['get_series_categories', 'all', 5],
['get_vod_streams', 'all', 6],
['get_series', 'all', 7],
],
2
),
];
for (const invalid of cases) {
assert.throws(
() =>
assertXtreamControlStateForScenario(
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE,
invalid,
MANIFEST
),
/invalid Xtream mock observations/
);
}
});
it('rejects credential keys and secret values from persisted artifacts', () => {
assert.doesNotThrow(() =>
assertXtreamArtifactRedacted({
fixture: {
bytes: MANIFEST.bytes,
catalogSha256: MANIFEST.catalogSha256,
},
})
);
for (const unsafe of [
{ username: 'performance' },
{ nested: { password: 'performance' } },
{ serverUrl: 'http://127.0.0.1:43123' },
{ note: 'leaked benchmark-control-token' },
]) {
assert.throws(
() =>
assertXtreamArtifactRedacted(unsafe, [
'benchmark-control-token',
]),
/credential-safe/
);
}
});
});
type Action = readonly [string, string, number];
function fullActions(): readonly Action[] {
return [
['get_account_info', 'all', 1],
['get_live_categories', 'all', 2],
['get_vod_categories', 'all', 3],
['get_series_categories', 'all', 4],
['get_live_streams', 'all', 5],
['get_vod_streams', 'all', 6],
['get_series', 'all', 7],
];
}
function controlState(
actions: readonly Action[],
accountCount = 1
): XtreamControlState {
return {
epoch: MANIFEST.epoch,
prepared: MANIFEST,
rules: [],
heldIds: [],
heldCount: 0,
occurrences: actions.map(([action, categoryId]) => ({
scenario: 'performance-100k',
transport: 'direct',
action,
categoryId,
count: action === 'get_account_info' ? accountCount : 1,
})),
ledger: actions.flatMap(([action, categoryId, order]) =>
Array.from(
{
length: action === 'get_account_info' ? accountCount : 1,
},
(_, index) => {
const shiftedOrder =
order +
index +
(action === 'get_account_info' ? 0 : accountCount - 1);
return [
{
epoch: MANIFEST.epoch,
scenario: 'performance-100k',
transport: 'direct' as const,
action,
categoryId,
occurrence: index + 1,
status: 'arrived' as const,
atMs: shiftedOrder * 10,
},
{
epoch: MANIFEST.epoch,
scenario: 'performance-100k',
transport: 'direct' as const,
action,
categoryId,
occurrence: index + 1,
status: 'responded' as const,
atMs: shiftedOrder * 10 + 1,
},
];
}
).flat()
),
};
}
@@ -0,0 +1,399 @@
import type { XTREAM_MEMORY_ACCOUNTING } from './xtream-summary-contract';
import type { XtreamBenchmarkBuildIdentity } from './xtream-build-identity';
export const XTREAM_SCENARIO_ID = {
INITIAL_IMPORT_LARGE: 'xtream-initial-import-large',
REFRESH_LARGE: 'xtream-refresh-large',
DELETE_LARGE: 'xtream-delete-large',
CANCEL_IMPORT: 'xtream-cancel-import',
BACKGROUND_UI: 'xtream-background-ui',
} as const;
export type XtreamScenarioId =
(typeof XTREAM_SCENARIO_ID)[keyof typeof XTREAM_SCENARIO_ID];
export const XTREAM_ITERATION_KIND = {
DIAGNOSTIC: 'diagnostic',
MEASURED: 'measured',
WARMUP: 'warmup',
} as const;
export type XtreamIterationKind =
(typeof XTREAM_ITERATION_KIND)[keyof typeof XTREAM_ITERATION_KIND];
export interface XtreamScenario {
readonly id: XtreamScenarioId;
}
export interface XtreamWorkerRequestInventoryEntry {
readonly count: number;
readonly operation: string;
readonly phases: readonly string[];
readonly success: boolean;
}
export interface XtreamIterationDefinition {
readonly eligibleForComparison: boolean;
readonly kind: XtreamIterationKind;
readonly runId: string;
readonly scenarioId: XtreamScenarioId;
}
export interface XtreamPerformanceManifest {
readonly bytes: number;
readonly catalogSha256: string;
readonly counts: {
readonly categories: XtreamCatalogCounts;
readonly items: XtreamCatalogCounts;
};
readonly epoch: number;
readonly scenario: 'performance-100k';
readonly seed: 91_001;
}
export interface XtreamCatalogCounts {
readonly live: number;
readonly series: number;
readonly total: number;
readonly vod: number;
}
export interface XtreamOccurrence {
readonly action: string;
readonly categoryId: string;
readonly count: number;
readonly scenario: string;
readonly transport: string;
}
export interface XtreamLedgerEntry {
readonly action: string;
readonly atMs: number;
readonly categoryId: string;
readonly epoch: number;
readonly occurrence: number;
readonly scenario: string;
readonly status: string;
readonly transport: string;
}
export interface XtreamControlState {
readonly epoch: number;
readonly heldCount: number;
readonly heldIds: readonly string[];
readonly ledger: readonly XtreamLedgerEntry[];
readonly occurrences: readonly XtreamOccurrence[];
readonly prepared: XtreamPerformanceManifest | null;
readonly rules: readonly unknown[];
}
export interface XtreamBenchmarkManifest {
readonly build: XtreamBenchmarkBuildIdentity;
readonly environment: XtreamBenchmarkEnvironment;
readonly fixture: XtreamBenchmarkFixture;
readonly memoryAccounting: typeof XTREAM_MEMORY_ACCOUNTING;
readonly mode: 'formal' | 'smoke';
readonly runtime: XtreamBenchmarkRuntime;
readonly source: XtreamBenchmarkSource;
readonly suite: 'xtream-performance-100k';
readonly variant: string;
}
export interface XtreamBenchmarkEnvironment {
readonly cdpAddress: '127.0.0.1';
readonly cdpPort: number;
readonly databaseWorkerBatchDelayMs: 0;
readonly exposeGc: true;
readonly freshUserDataPerIteration: true;
readonly perfCapture: '1';
readonly perfWorkerProfiling: '1';
readonly traceRendererConsole: '0';
}
export interface XtreamBenchmarkFixture {
readonly bytes: number;
readonly catalogSha256: string;
readonly categories: 100;
readonly items: 100_000;
readonly scenario: 'performance-100k';
readonly seed: 91_001;
}
export interface XtreamBenchmarkRuntime {
readonly arch: string;
readonly electronVersion: string;
readonly harnessNodeVersion: string;
readonly platform: string;
}
export interface XtreamBenchmarkSource {
readonly gitCommit: string;
readonly gitDiffSha256: string;
readonly gitDirty: boolean;
}
export interface NumericDistribution {
readonly count: number;
readonly max: number | null;
readonly mean: number | null;
readonly median: number | null;
readonly min: number | null;
readonly p95: number | null;
readonly p99: number | null;
}
export function summarizeXtreamNumbers(
values: readonly number[]
): NumericDistribution {
const samples = [...values].sort((left, right) => left - right);
const total = samples.reduce((sum, value) => sum + value, 0);
const percentile = (rank: number): number => {
if (samples.length === 1) return samples[0] ?? 0;
const position = ((samples.length - 1) * rank) / 100;
const lowerIndex = Math.floor(position);
const upperIndex = Math.ceil(position);
const lower = samples[lowerIndex] ?? 0;
const upper = samples[upperIndex] ?? lower;
return lower + (upper - lower) * (position - lowerIndex);
};
return {
count: samples.length,
max: samples.at(-1) ?? null,
mean: total / samples.length,
median: percentile(50),
min: samples[0] ?? null,
p95: percentile(95),
p99: percentile(99),
};
}
export interface XtreamScenarioMetrics {
readonly angularRenderingMs: NumericDistribution;
readonly cancelAcknowledgementLatencyMs: NumericDistribution | null;
readonly dataAcquisitionMs: NumericDistribution;
readonly deserializationMs: NumericDistribution;
readonly databaseWorkerCpuSystemMicros: NumericDistribution;
readonly databaseWorkerCpuUserMicros: NumericDistribution;
readonly databaseWorkerEventLoopDelayMaxMs: NumericDistribution;
readonly databaseWorkerEventLoopDelayP95Ms: NumericDistribution;
readonly databaseWorkerEventLoopDelayP99Ms: NumericDistribution;
readonly databaseWorkerEventLoopUtilization: NumericDistribution;
readonly databaseWorkerPeakExternalBytes: NumericDistribution;
readonly databaseWorkerPeakHeapBytes: NumericDistribution;
readonly databaseWorkerPostGcHeapBytes: NumericDistribution;
readonly databaseWorkerRequestInvalidSampleCount: NumericDistribution;
readonly databaseWorkerRequestEventLoopDelayMaxMs: NumericDistribution;
readonly databaseWorkerRequestEventLoopDelayP95Ms: NumericDistribution;
readonly databaseWorkerRequestEventLoopDelayP99Ms: NumericDistribution;
readonly databaseWorkerRequestEventLoopUtilization: NumericDistribution;
readonly databaseWorkerRequestTotalCount: NumericDistribution;
readonly databaseWorkerRequestValidSampleCount: NumericDistribution;
readonly jsonParsingMs: NumericDistribution;
readonly mainCpuSystemMicros: NumericDistribution;
readonly mainCpuUserMicros: NumericDistribution;
readonly mainEventLoopDelayMaxMs: NumericDistribution;
readonly mainEventLoopDelayP95Ms: NumericDistribution;
readonly mainEventLoopDelayP99Ms: NumericDistribution;
readonly mainPeakHeapBytes: NumericDistribution;
readonly mainPeakRssBytes: NumericDistribution;
readonly mainPostGcHeapBytes: NumericDistribution;
readonly normalizationMs: NumericDistribution;
readonly rendererActionLatencyMs: NumericDistribution | null;
readonly rendererFrameGapMaxMs: NumericDistribution;
readonly rendererFrameGapP95Ms: NumericDistribution;
readonly rendererHeartbeatMaxMs: NumericDistribution;
readonly rendererHeartbeatP95Ms: NumericDistribution;
readonly rendererLongTaskCount: NumericDistribution;
readonly rendererLongTaskMaxMs: NumericDistribution;
readonly rendererNavigationLatencyMs: NumericDistribution | null;
readonly rendererPeakHeapBytes: NumericDistribution;
readonly rendererPeakRssBytes: NumericDistribution;
readonly rendererPostGcHeapBytes: NumericDistribution;
readonly rendererSearchLatencyMs: NumericDistribution | null;
readonly serializationMs: NumericDistribution;
readonly sqliteReadMs: NumericDistribution;
readonly sqliteWriteMs: NumericDistribution;
readonly storeUpdateMs: NumericDistribution;
readonly structuredCloneProxyMs: NumericDistribution;
readonly totalMs: NumericDistribution;
}
const SCENARIOS: readonly XtreamScenario[] = Object.freeze([
Object.freeze({ id: XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE }),
Object.freeze({ id: XTREAM_SCENARIO_ID.REFRESH_LARGE }),
Object.freeze({ id: XTREAM_SCENARIO_ID.DELETE_LARGE }),
Object.freeze({ id: XTREAM_SCENARIO_ID.CANCEL_IMPORT }),
Object.freeze({ id: XTREAM_SCENARIO_ID.BACKGROUND_UI }),
]);
export function listXtreamBenchmarkScenarios(): readonly XtreamScenario[] {
return SCENARIOS;
}
export function createXtreamIterationDefinitions(
smoke: boolean
): readonly XtreamIterationDefinition[] {
const definitions: XtreamIterationDefinition[] = [];
for (const scenario of SCENARIOS) {
definitions.push(iteration(scenario.id, 'warmup-01', 'warmup', false));
for (let index = 1; index <= (smoke ? 1 : 5); index += 1) {
definitions.push(
iteration(
scenario.id,
`run-${String(index).padStart(2, '0')}`,
'measured',
!smoke
)
);
}
definitions.push(
iteration(scenario.id, 'diagnostic', 'diagnostic', false)
);
}
return Object.freeze(definitions);
}
export function expectedXtreamWorkerRequestInventory(
scenarioId: XtreamScenarioId
): readonly XtreamWorkerRequestInventoryEntry[] {
return WORKER_REQUEST_INVENTORY[scenarioId];
}
const PHASES = Object.freeze({
categoriesRead: ['sqlite.categories.read'],
categoriesWrite: [
'normalize.categories',
'sqlite.categories.write-transactions',
],
contentRead: ['sqlite.content.read'],
contentWrite: [
'sqlite.content.category-map-read',
'normalize.content',
'sqlite.content.write-transactions',
],
playlistDelete: [
'sqlite.playlist-delete.collect-ids',
'sqlite.playlist-delete.write-transactions',
],
playlistGet: ['sqlite.read', 'deserialize.playlist'],
playlistUpsert: ['serialize.playlist', 'sqlite.write'],
xtreamCacheClear: ['sqlite.xtream-cache-clear.write-transactions'],
xtreamDelete: [
'sqlite.xtream-delete.collect-user-data',
'sqlite.xtream-delete.write-transactions',
],
});
const WORKER_REQUEST_INVENTORY: Readonly<
Record<XtreamScenarioId, readonly XtreamWorkerRequestInventoryEntry[]>
> = Object.freeze({
[XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE]: inventory(
request('DB_UPSERT_APP_PLAYLIST', 1, PHASES.playlistUpsert),
request('DB_GET_PLAYLIST', 1),
request('DB_GET_ALL_PLAYBACK_POSITIONS', 1),
...fullImportRequests()
),
[XTREAM_SCENARIO_ID.REFRESH_LARGE]: refreshRequests(),
[XTREAM_SCENARIO_ID.DELETE_LARGE]: inventory(
request('DB_DELETE_PLAYLIST', 2, PHASES.playlistDelete)
),
[XTREAM_SCENARIO_ID.CANCEL_IMPORT]: inventory(
request('DB_UPSERT_APP_PLAYLIST', 1, PHASES.playlistUpsert),
request('DB_GET_PLAYLIST', 1),
request('DB_GET_ALL_PLAYBACK_POSITIONS', 1),
request('DB_GET_APP_STATE', 4),
request('DB_GET_CATEGORIES', 6, PHASES.categoriesRead),
request('DB_SAVE_CATEGORIES', 3, PHASES.categoriesWrite),
request('DB_GET_CONTENT', 1, PHASES.contentRead),
request('DB_SAVE_CONTENT', 1, PHASES.contentWrite, false),
request('DB_SET_APP_STATE', 3),
request('DB_CLEAR_XTREAM_IMPORT_CACHE', 3, PHASES.xtreamCacheClear)
),
[XTREAM_SCENARIO_ID.BACKGROUND_UI]: inventory(
...refreshRequests(),
request('DB_GET_RECENTLY_VIEWED', 1),
request('DB_GET_ALL_GLOBAL_FAVORITES', 1),
request('DB_GET_GLOBAL_RECENTLY_ADDED', 1)
),
});
function fullImportRequests(
reads: Readonly<{
appState: number;
categories: number;
content: number;
setAppState: number;
}> = { appState: 6, categories: 6, content: 6, setAppState: 3 }
): readonly XtreamWorkerRequestInventoryEntry[] {
return inventory(
request('DB_GET_APP_STATE', reads.appState),
request('DB_GET_CATEGORIES', reads.categories, PHASES.categoriesRead),
request('DB_SAVE_CATEGORIES', 3, PHASES.categoriesWrite),
request('DB_GET_CONTENT', reads.content, PHASES.contentRead),
request('DB_SAVE_CONTENT', 3, PHASES.contentWrite),
request('DB_SET_APP_STATE', reads.setAppState)
);
}
function refreshRequests(): readonly XtreamWorkerRequestInventoryEntry[] {
return inventory(
request('DB_DELETE_XTREAM_CONTENT', 1, PHASES.xtreamDelete),
request('DB_GET_ALL_PLAYBACK_POSITIONS', 1),
request('DB_UPDATE_PLAYLIST', 1),
request('DB_GET_APP_PLAYLIST', 1, PHASES.playlistGet),
request('DB_GET_PLAYLIST', 2),
request('DB_UPSERT_APP_PLAYLIST', 1, PHASES.playlistUpsert),
...fullImportRequests({
appState: 12,
categories: 9,
content: 9,
setAppState: 6,
}),
request('DB_RESTORE_XTREAM_USER_DATA', 1),
request('DB_CLEAR_ALL_PLAYBACK_POSITIONS', 1)
);
}
function inventory(
...entries: readonly XtreamWorkerRequestInventoryEntry[]
): readonly XtreamWorkerRequestInventoryEntry[] {
return Object.freeze(entries);
}
function request(
operation: string,
count: number,
phases: readonly string[] = [],
success = true
): XtreamWorkerRequestInventoryEntry {
return Object.freeze({
count,
operation,
phases: Object.freeze([...phases]),
success,
});
}
function iteration(
scenarioId: XtreamScenarioId,
runId: string,
kind: XtreamIterationKind,
eligibleForComparison: boolean
): XtreamIterationDefinition {
return Object.freeze({
eligibleForComparison,
kind,
runId,
scenarioId,
});
}
export {
assertXtreamArtifactRedacted,
assertXtreamControlReadyAfterObservationReset,
assertXtreamControlStateForScenario,
assertXtreamManifest,
assertXtreamManifestIdentity,
assertXtreamMockOrigin,
parseXtreamControlState,
} from './xtream-control-client';
@@ -0,0 +1,62 @@
import assert from 'node:assert/strict';
import { afterEach, describe, it } from 'node:test';
import type { XtreamBenchmarkManifest } from './xtream-benchmark-contract';
import { createXtreamBenchmarkSummary } from './xtream-benchmark-report';
import {
MANIFEST,
clearXtreamReportFixtureDirectories,
} from './xtream-benchmark-report.fixtures';
import {
clearXtreamProvenanceFixtureDirectories,
smokeReportFixture,
} from './xtream-benchmark-report-provenance.fixtures';
import { assertXtreamBenchmarkManifest } from './xtream-exact-schema';
afterEach(async () => {
await Promise.all([
clearXtreamReportFixtureDirectories(),
clearXtreamProvenanceFixtureDirectories(),
]);
});
describe('Xtream benchmark CDP environment', () => {
it('allows a bounded alternate smoke port but keeps formal capture on 9222', async () => {
const smokeManifest: XtreamBenchmarkManifest = {
...MANIFEST,
environment: { ...MANIFEST.environment, cdpPort: 9322 },
mode: 'smoke',
};
assert.doesNotThrow(() => assertXtreamBenchmarkManifest(smokeManifest));
assert.throws(
() =>
assertXtreamBenchmarkManifest({
...smokeManifest,
mode: 'formal',
}),
/invalid Xtream benchmark manifest/
);
const { diagnosticEvidence, iterations } = await smokeReportFixture();
const summary = createXtreamBenchmarkSummary(
smokeManifest,
iterations,
diagnosticEvidence
);
assert.equal(summary.validForComparison, false);
});
it('requires an integer CDP port from 1024 through 65535', () => {
for (const cdpPort of [1023, 65536, 9322.5, Number.NaN]) {
assert.throws(
() =>
assertXtreamBenchmarkManifest({
...MANIFEST,
environment: { ...MANIFEST.environment, cdpPort },
mode: 'smoke',
}),
/invalid Xtream benchmark manifest/
);
}
});
});
@@ -0,0 +1,274 @@
import assert from 'node:assert/strict';
import { mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { describe, it } from 'node:test';
import { createXtreamIterationDefinitions } from './xtream-benchmark-contract';
import { createXtreamBenchmarkSummary } from './xtream-benchmark-report';
import {
MANIFEST,
clearXtreamReportFixtureDirectories,
iteration,
persist,
persistWith,
sha256,
} from './xtream-benchmark-report.fixtures';
import { toXtreamRawIteration } from './xtream-exact-schema';
import {
clearXtreamProvenanceFixtureDirectories,
formalReportFixture,
} from './xtream-benchmark-report-provenance.fixtures';
import type {
XtreamBenchmarkManifest,
XtreamPersistenceReceipt,
} from './xtream-summary-contract';
describe('Xtream benchmark integrity boundaries', () => {
it('uses one intrinsic snapshot instead of caller-owned array methods', async () => {
const { diagnosticEvidence, iterations } = await formalReportFixture();
const adversarial = [...iterations];
const invoked: string[] = [];
for (const method of ['filter', 'find', 'map'] as const) {
Object.defineProperty(adversarial, method, {
value() {
invoked.push(method);
throw new Error(`caller-owned-${method}`);
},
});
}
Object.defineProperty(adversarial, Symbol.iterator, {
value() {
invoked.push('iterator');
throw new Error('caller-owned-iterator');
},
});
try {
assert.doesNotThrow(() =>
createXtreamBenchmarkSummary(
MANIFEST,
adversarial,
diagnosticEvidence
)
);
assert.deepEqual(invoked, []);
} finally {
await clearXtreamProvenanceFixtureDirectories();
}
});
it('cannot hide a reused process identity behind caller-owned map', async () => {
const fixture = await formalReportFixture();
const definitions = createXtreamIterationDefinitions(false);
const iterations = [...fixture.iterations];
const sourceDuplicate = iteration(required(definitions, 1), 9_999);
const duplicate = {
...sourceDuplicate,
processIdentity: {
...sourceDuplicate.processIdentity,
electronPid: required(iterations, 0).processIdentity
.electronPid,
},
};
iterations[1] = await persist(duplicate, 9_999);
Object.defineProperty(iterations, 'map', {
value() {
return Array.from(
{ length: iterations.length },
(_, index) => index
);
},
});
try {
assert.throws(
() =>
createXtreamBenchmarkSummary(
MANIFEST,
iterations,
fixture.diagnosticEvidence
),
/fresh process identity/
);
} finally {
await Promise.all([
clearXtreamReportFixtureDirectories(),
clearXtreamProvenanceFixtureDirectories(),
]);
}
});
it('uses native Map entries after validating diagnostic evidence', async () => {
const { diagnosticEvidence, iterations } = await formalReportFixture();
const entries = [...diagnosticEvidence.entries()];
const [firstIteration, firstEvidence] = required(entries, 0);
const [, secondEvidence] = required(entries, 1);
const adversarial = new Map(entries);
Object.defineProperty(adversarial, 'get', {
value(key: (typeof entries)[number][0]) {
return key === firstIteration
? secondEvidence
: Map.prototype.get.call(this, key);
},
});
try {
const summary = createXtreamBenchmarkSummary(
MANIFEST,
iterations,
adversarial
);
const scenario = summary.scenarios.find(
({ scenarioId }) => scenarioId === firstIteration.scenarioId
);
assert.equal(
scenario?.diagnosticProfiles.artifactProvenance.directorySha256,
firstEvidence.directorySha256
);
} finally {
await clearXtreamProvenanceFixtureDirectories();
}
});
it('snapshots every persistence receipt accessor exactly once', async () => {
const definition = required(createXtreamIterationDefinitions(false), 1);
const raw = iteration(definition, 1);
const directory = await mkdtemp(
join(tmpdir(), 'iptvnator-xtream-receipt-accessor-')
);
const regularPath = join(directory, 'raw.json');
const symlinkPath = join(directory, 'raw-link.json');
try {
await assert.rejects(
() =>
persistWith(raw, async (serialized) => {
await writeFile(regularPath, serialized);
await symlink(regularPath, symlinkPath);
let pathReads = 0;
return {
get absolutePath() {
pathReads += 1;
return pathReads === 1
? symlinkPath
: regularPath;
},
bytes: Buffer.byteLength(serialized),
sha256: sha256(serialized),
} as XtreamPersistenceReceipt;
}),
/persistence receipt/
);
let stablePathReads = 0;
await persistWith(raw, async (serialized) => ({
get absolutePath() {
stablePathReads += 1;
return regularPath;
},
bytes: Buffer.byteLength(serialized),
sha256: sha256(serialized),
}));
assert.equal(stablePathReads, 1);
} finally {
await rm(directory, { force: true, recursive: true });
}
});
it('uses one canonical manifest snapshot for validation and output', async () => {
const { diagnosticEvidence, iterations } = await formalReportFixture();
let variantReads = 0;
const manifest = {
...MANIFEST,
get variant() {
variantReads += 1;
return variantReads <= 3 ? 'baseline' : 'CON.json';
},
} as XtreamBenchmarkManifest;
try {
const summary = createXtreamBenchmarkSummary(
manifest,
iterations,
diagnosticEvidence
);
assert.equal(summary.manifest.variant, 'baseline');
assert.equal(variantReads, 1);
} finally {
await clearXtreamProvenanceFixtureDirectories();
}
});
it('binds diagnostic profile durations and worker ordinal to artifact evidence', async () => {
const fixture = await formalReportFixture();
const [diagnostic, evidence] = required(
[...fixture.diagnosticEvidence.entries()],
0
);
const diagnosticIndex = fixture.iterations.indexOf(diagnostic);
assert.notEqual(diagnosticIndex, -1);
const raw = toXtreamRawIteration(diagnostic);
const mismatches = [
{
...raw,
renderer: {
...raw.renderer,
cpuProfileDurationMicros: 2_001,
},
},
{
...raw,
main: {
...raw.main,
cpuProfileDurationMicros: 2_001,
},
},
{
...raw,
databaseWorker: {
...raw.databaseWorker,
cpuProfileDurationMicros: 2_001,
},
},
{
...raw,
databaseWorker: {
...raw.databaseWorker,
ordinal: raw.databaseWorker.ordinal + 1,
},
},
];
try {
for (const [index, mismatch] of mismatches.entries()) {
const replacement = await persist(mismatch, 20_000 + index);
const iterations = [...fixture.iterations];
iterations[diagnosticIndex] = replacement;
const diagnosticEvidence = new Map(fixture.diagnosticEvidence);
diagnosticEvidence.delete(diagnostic);
diagnosticEvidence.set(replacement, evidence);
assert.throws(
() =>
createXtreamBenchmarkSummary(
MANIFEST,
iterations,
diagnosticEvidence
),
/diagnostic artifact evidence binding/
);
}
} finally {
await Promise.all([
clearXtreamReportFixtureDirectories(),
clearXtreamProvenanceFixtureDirectories(),
]);
}
});
});
function required<T>(values: readonly T[], index: number): T {
const value = values[index];
assert.ok(value);
return value;
}
@@ -0,0 +1,262 @@
import { join } from 'node:path';
import {
ELECTRON_RUNTIME_ENVIRONMENT_KEYS,
type LaunchedElectronApp,
} from '../electron-test-fixtures';
import {
XTREAM_SCENARIO_ID,
type XtreamIterationDefinition,
type XtreamPerformanceManifest,
type XtreamScenarioId,
} from './xtream-benchmark-contract';
import { writeXtreamSafeJson } from './xtream-benchmark-artifacts';
import type { XtreamControlClient } from './xtream-control-client';
import { assertXtreamControlStateForScenario } from './xtream-control-client';
import {
validateXtreamDiagnosticArtifacts,
type XtreamDiagnosticArtifactEvidence,
} from './xtream-diagnostic-artifacts';
import {
readXtreamMainCaptureStatus,
startMainCapture,
type MainCaptureStartOptions,
type XtreamMainCaptureResult,
type XtreamMainCaptureStatus,
} from './m3u-refresh-main-capture';
import { isXtreamMainCaptureSettled } from './xtream-main-settlement';
import type { XtreamRendererCaptureMetrics } from './xtream-renderer-capture';
import {
assertCompleteXtreamRendererProbe,
beginXtreamRendererOperation,
stopXtreamRendererProbe,
waitForXtreamRendererTerminal,
} from './xtream-renderer-probe-control';
import { installXtreamRendererProbe } from './xtream-renderer-probe';
import { prepareXtreamScenario } from './xtream-scenario-driver';
import type { XtreamArtifactSafetyContext } from './xtream-summary-contract';
export interface SeedXtreamExistingPortalOptions {
readonly app: LaunchedElectronApp;
readonly captureOptions: MainCaptureStartOptions;
readonly control: XtreamControlClient;
readonly fixture: XtreamPerformanceManifest;
readonly iterationDirectory: string;
readonly safety: XtreamArtifactSafetyContext;
readonly serverUrl: string;
readonly sourceTitle: string;
}
export async function prearmXtreamDatabaseWorker(
app: LaunchedElectronApp
): Promise<void> {
const playlists = await app.mainWindow.evaluate(() =>
window.electron.dbGetAppPlaylists()
);
if (!Array.isArray(playlists)) {
throw new Error('xtream-database-worker-prearm-invalid');
}
}
export async function seedXtreamExistingPortal(
options: SeedXtreamExistingPortalOptions
): Promise<void> {
const seed = await prepareXtreamScenario(options.app.mainWindow, {
scenarioId: XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE,
serverUrl: options.serverUrl,
sourceTitle: options.sourceTitle,
});
let rendererProbeInstalled = false;
try {
await installXtreamRendererProbe(options.app.mainWindow, {
scenarioId: XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE,
sourceTitle: options.sourceTitle,
});
rendererProbeInstalled = true;
await startMainCapture(options.app.electronApp, {
...options.captureOptions,
deferMeasurement: false,
diagnostic: false,
});
const captureGeneration = (
await readXtreamMainCaptureStatus(options.app.electronApp)
).captureGeneration;
await beginXtreamRendererOperation(options.app.mainWindow);
await seed.trigger();
const driverTerminal = seed.waitForTerminal();
await waitForXtreamRendererTerminal(options.app.mainWindow);
const rendererProbe = await stopXtreamRendererProbe(
options.app.mainWindow
);
rendererProbeInstalled = false;
assertCompleteXtreamRendererProbe(rendererProbe);
await assertXtreamSettlementAfterTerminal({
captureGeneration,
readStatus: () =>
readXtreamMainCaptureStatus(options.app.electronApp),
scenarioId: XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE,
terminal: driverTerminal,
});
const state = await options.control.state();
await writeXtreamSafeJson(
join(options.iterationDirectory, 'seed-control-state.json'),
state,
options.safety
);
assertXtreamControlStateForScenario(
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE,
state,
options.fixture
);
} finally {
await Promise.allSettled([
rendererProbeInstalled
? stopXtreamRendererProbe(options.app.mainWindow)
: Promise.resolve(),
seed.dispose(),
]);
}
}
export interface AssertXtreamSettlementAfterTerminalOptions {
readonly captureGeneration: number;
readonly readStatus: () => Promise<XtreamMainCaptureStatus>;
readonly scenarioId: XtreamScenarioId;
readonly terminal: Promise<unknown>;
}
export async function assertXtreamSettlementAfterTerminal(
options: AssertXtreamSettlementAfterTerminalOptions
): Promise<void> {
await options.terminal;
const status = await options.readStatus();
if (status.captureGeneration !== options.captureGeneration) {
throw new Error('xtream-measured-capture-generation-changed');
}
if (!isXtreamMainCaptureSettled(status, options.scenarioId)) {
throw new Error('xtream-main-not-settled-after-terminal');
}
}
export interface FinalizeXtreamCaptureAfterRendererTerminalOptions<
DriverTerminal,
MainCapture,
RendererCapture,
> extends Omit<AssertXtreamSettlementAfterTerminalOptions, 'terminal'> {
readonly driverTerminal: Promise<DriverTerminal>;
readonly rendererTerminal: Promise<unknown>;
readonly stopMain: () => Promise<MainCapture>;
readonly stopRenderer: () => Promise<RendererCapture>;
}
export interface FinalizedXtreamCapture<
DriverTerminal,
MainCapture,
RendererCapture,
> {
readonly driverTerminal: DriverTerminal;
readonly mainCapture: MainCapture;
readonly rendererCapture: RendererCapture;
}
export async function finalizeXtreamCaptureAfterRendererTerminal<
DriverTerminal,
MainCapture,
RendererCapture,
>(
options: FinalizeXtreamCaptureAfterRendererTerminalOptions<
DriverTerminal,
MainCapture,
RendererCapture
>
): Promise<
FinalizedXtreamCapture<DriverTerminal, MainCapture, RendererCapture>
> {
await assertXtreamSettlementAfterTerminal({
captureGeneration: options.captureGeneration,
readStatus: options.readStatus,
scenarioId: options.scenarioId,
terminal: options.rendererTerminal,
});
const rendererCapture = options.stopRenderer();
const mainCapture = options.stopMain();
const [resolvedRenderer, resolvedMain, driverTerminal] = await Promise.all([
rendererCapture,
mainCapture,
options.driverTerminal,
]);
return Object.freeze({
driverTerminal,
mainCapture: resolvedMain,
rendererCapture: resolvedRenderer,
});
}
export async function assertXtreamBenchmarkEnvironmentIsolated(
app: LaunchedElectronApp
): Promise<void> {
const keys = await app.electronApp.evaluate(() => Object.keys(process.env));
const allowed = new Set(
[
...ELECTRON_RUNTIME_ENVIRONMENT_KEYS,
'ELECTRON_IS_DEV',
'IPTVNATOR_ALLOW_PRIVATE_NETWORK_URLS',
'IPTVNATOR_DB_WORKER_BATCH_DELAY_MS',
'IPTVNATOR_E2E_DATA_DIR',
'IPTVNATOR_PERF_CAPTURE',
'IPTVNATOR_PERF_WORKER_PROFILING',
'IPTVNATOR_TRACE_RENDERER_CONSOLE',
'NODE_ENV',
].map((key) => key.toUpperCase())
);
if (
!Array.isArray(keys) ||
keys.some(
(key) => typeof key !== 'string' || !allowed.has(key.toUpperCase())
)
) {
throw new Error('xtream-benchmark-environment-reached-electron');
}
}
export function createXtreamSourceTitle(
definition: XtreamIterationDefinition
): string {
return `Synthetic Xtream 100k ${definition.scenarioId} ${definition.runId}`;
}
export async function createXtreamDiagnosticEvidence(
kind: XtreamIterationDefinition['kind'],
main: XtreamMainCaptureResult,
renderer: XtreamRendererCaptureMetrics,
iterationDirectory: string
): Promise<XtreamDiagnosticArtifactEvidence | null> {
if (kind !== 'diagnostic') return null;
return validateXtreamDiagnosticArtifacts(
{
main: {
cpuProfilePath: main.capture.cpuProfilePath,
heapSnapshotPath: main.capture.heapSnapshotPath,
workers: main.workers.map(
({
kind: workerKind,
ordinal,
profilePath,
snapshotPath,
}) => ({
kind: workerKind,
ordinal,
profilePath,
snapshotPath,
})
),
},
renderer: {
cpuProfilePath: renderer.cpuProfilePath,
heapSnapshotPath: renderer.heapSnapshotPath,
tracePath: renderer.tracePath,
},
},
iterationDirectory
);
}
@@ -0,0 +1,399 @@
import { mkdir, mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
closeElectronApp,
launchElectronApp,
type LaunchedElectronApp,
} from '../electron-test-fixtures';
import {
type XtreamIterationDefinition,
type XtreamPerformanceManifest,
XTREAM_SCENARIO_ID,
} from './xtream-benchmark-contract';
import type { XtreamBenchmarkConfiguration } from './xtream-benchmark-configuration';
import type { XtreamControlClient } from './xtream-control-client';
import {
assertXtreamControlReadyAfterObservationReset,
assertXtreamControlStateForScenario,
assertXtreamManifestIdentity,
} from './xtream-control-client';
import { createXtreamCatalogVerification } from './xtream-catalog-database-verification';
import { readXtreamCatalogDatabaseSnapshot } from './xtream-catalog-database-reader';
import { assembleXtreamRawIteration } from './xtream-iteration-assembler';
import {
beginXtreamMainMeasurement,
installMainCapture,
readXtreamMainCaptureStatus,
rolloverXtreamMainCapture,
startMainCapture,
stopXtreamMainCapture,
type MainCaptureStartOptions,
} from './m3u-refresh-main-capture';
import {
assertRendererCdpTarget,
assertTcpPortAvailable,
resolveRendererWindowIdentity,
} from './m3u-import-benchmark-support';
import { assertPerformanceArtifactCapacity } from './performance-artifact-preflight';
import {
createXtreamArtifactSafetyContext,
persistXtreamIteration,
writeXtreamSafeJson,
type PersistedXtreamIteration,
} from './xtream-benchmark-artifacts';
import {
createXtreamSingleAttemptCapture,
persistXtreamIterationFailureEvidenceAndThrow,
type XtreamBenchmarkFailureStage,
} from './xtream-benchmark-lifecycle';
import {
assertXtreamBenchmarkEnvironmentIsolated,
createXtreamDiagnosticEvidence,
createXtreamSourceTitle,
finalizeXtreamCaptureAfterRendererTerminal,
prearmXtreamDatabaseWorker,
seedXtreamExistingPortal,
} from './xtream-benchmark-iteration-support';
import {
deriveXtreamMeasuredPlaylistId,
disableGenericElectronFixtureProbes,
xtreamScenarioRequiresSeed,
} from './xtream-benchmark-runtime';
import {
captureXtreamLaunchIdentity,
createXtreamIterationProcessEvidence,
} from './xtream-process-evidence';
import {
startXtreamRendererCapture,
type RunningXtreamRendererCapture,
} from './xtream-renderer-capture';
import {
prepareXtreamScenario,
type PreparedXtreamScenario,
type XtreamScenarioTerminalEvidence,
type XtreamScenarioTriggerEvidence,
} from './xtream-scenario-driver';
import { runXtreamBackgroundUiActions } from './xtream-ui-action-probe';
export interface XtreamBenchmarkIterationContext {
readonly configuration: XtreamBenchmarkConfiguration;
readonly control: XtreamControlClient;
readonly definition: XtreamIterationDefinition;
readonly expectedFixture: XtreamPerformanceManifest;
readonly scenarioDirectory: string;
readonly seenElectronPids: Set<number>;
}
export async function runXtreamBenchmarkIteration(
context: XtreamBenchmarkIterationContext
): Promise<PersistedXtreamIteration> {
const { configuration, control, definition, scenarioDirectory } = context;
const iterationDirectory = join(scenarioDirectory, definition.runId);
await assertPerformanceArtifactCapacity(iterationDirectory);
await mkdir(iterationDirectory, {
mode: 0o700,
recursive: false,
});
const dataDirectory = await mkdtemp(
join(tmpdir(), 'iptvnator-xtream-performance-')
);
const safety = createXtreamArtifactSafetyContext(
configuration.controlToken
);
let app: LaunchedElectronApp | null = null;
let renderer: RunningXtreamRendererCapture | null = null;
let prepared: PreparedXtreamScenario | null = null;
let controlState: Awaited<ReturnType<XtreamControlClient['state']>> | null =
null;
let failureStage: XtreamBenchmarkFailureStage = 'capture-setup';
let stopMainCaptureOnce:
(() => ReturnType<typeof stopXtreamMainCapture>) | null = null;
let stopRendererCaptureOnce: RunningXtreamRendererCapture['stop'] | null =
null;
let terminal: XtreamScenarioTerminalEvidence | null = null;
let trigger: XtreamScenarioTriggerEvidence | null = null;
try {
await control.resetAll();
const fixture = await control.prepare();
assertXtreamManifestIdentity(context.expectedFixture, fixture);
await assertTcpPortAvailable(configuration.cdpPort);
app = await launchElectronApp(dataDirectory, {
args: [
'--js-flags=--expose-gc',
'--remote-debugging-address=127.0.0.1',
`--remote-debugging-port=${configuration.cdpPort}`,
`--user-data-dir=${join(dataDirectory, 'user-data')}`,
],
environmentInheritance: 'runtime-only',
env: {
IPTVNATOR_DB_WORKER_BATCH_DELAY_MS: '0',
IPTVNATOR_PERF_CAPTURE: '1',
IPTVNATOR_PERF_WORKER_PROFILING: '1',
IPTVNATOR_TRACE_RENDERER_CONSOLE: '0',
},
omitEnvKeys: [
'IPTVNATOR_XTREAM_MOCK_CONTROL',
'IPTVNATOR_XTREAM_MOCK_CONTROL_TOKEN',
],
});
app.mainWindow.setDefaultTimeout(10 * 60 * 1_000);
await assertXtreamBenchmarkEnvironmentIsolated(app);
await disableGenericElectronFixtureProbes(app.mainWindow);
await assertRendererCdpTarget(app.mainWindow, configuration.cdpPort);
await installMainCapture(app.electronApp);
await prearmXtreamDatabaseWorker(app);
const electronApp = app.electronApp;
const launch = await captureXtreamLaunchIdentity(
app.electronApp,
app.electronApp.process().pid,
context.seenElectronPids
);
const rendererWindowIdentity = await resolveRendererWindowIdentity(app);
const captureOptions: MainCaptureStartOptions = {
deferMeasurement: true,
diagnostic: definition.kind === 'diagnostic',
outputDirectory: iterationDirectory,
rendererWindowIdentity,
};
const sourceTitle = createXtreamSourceTitle(definition);
const seeded = xtreamScenarioRequiresSeed(definition.scenarioId);
if (seeded) {
failureStage = 'seed';
stopMainCaptureOnce = createXtreamSingleAttemptCapture(() =>
stopXtreamMainCapture(electronApp)
);
await seedXtreamExistingPortal({
app,
captureOptions,
control,
fixture,
iterationDirectory,
safety,
serverUrl: configuration.mockOrigin,
sourceTitle,
});
}
failureStage = 'scenario-preparation';
prepared = await prepareXtreamScenario(app.mainWindow, {
scenarioId: definition.scenarioId,
serverUrl: configuration.mockOrigin,
sourceTitle,
});
await control.resetObservations();
assertXtreamControlReadyAfterObservationReset(
await control.state(),
fixture
);
let captureGeneration: number;
failureStage = 'measured-capture-start';
if (seeded) {
const rolloverOnce = createXtreamSingleAttemptCapture(() =>
rolloverXtreamMainCapture(electronApp, captureOptions)
);
stopMainCaptureOnce = rolloverOnce;
const rollover = await rolloverOnce();
stopMainCaptureOnce = createXtreamSingleAttemptCapture(() =>
stopXtreamMainCapture(electronApp)
);
if (
!rollover.rollover?.nextCaptureStarted ||
rollover.rollover.nextCaptureUnavailableReason !== null
) {
throw new Error('xtream-measured-capture-rollover-failed');
}
await writeXtreamSafeJson(
join(iterationDirectory, 'seed-main-capture.json'),
rollover,
safety
);
captureGeneration = rollover.captureGeneration + 1;
if (
(await readXtreamMainCaptureStatus(app.electronApp))
.captureGeneration !== captureGeneration
) {
throw new Error('xtream-measured-capture-generation-changed');
}
} else {
stopMainCaptureOnce = createXtreamSingleAttemptCapture(() =>
stopXtreamMainCapture(electronApp)
);
await startMainCapture(app.electronApp, captureOptions);
captureGeneration = (
await readXtreamMainCaptureStatus(app.electronApp)
).captureGeneration;
}
failureStage = 'renderer-capture-start';
renderer = await startXtreamRendererCapture(app.mainWindow, {
diagnostic: definition.kind === 'diagnostic',
outputDirectory: iterationDirectory,
scenarioId: definition.scenarioId,
sourceTitle,
});
const runningRenderer = renderer;
stopRendererCaptureOnce = createXtreamSingleAttemptCapture(() =>
runningRenderer.stop()
);
await beginXtreamMainMeasurement(app.electronApp);
await renderer.beginOperation();
failureStage = 'operation-trigger';
trigger = await prepared.trigger();
if (definition.scenarioId === XTREAM_SCENARIO_ID.BACKGROUND_UI) {
failureStage = 'background-ui';
await prepared.runBackgroundProbe(
({ operationId, page, playlistId }) =>
runXtreamBackgroundUiActions(page, {
operationId,
playlistId,
searchTerm: 'Performance',
})
);
}
failureStage = 'terminal-settlement';
const terminalPromise = prepared.waitForTerminal().then((value) => {
terminal = value;
return value;
});
if (!stopRendererCaptureOnce || !stopMainCaptureOnce) {
throw new Error('xtream-measured-capture-lifecycle-invalid');
}
const finalized = await finalizeXtreamCaptureAfterRendererTerminal({
captureGeneration,
driverTerminal: terminalPromise,
readStatus: () => readXtreamMainCaptureStatus(app.electronApp),
rendererTerminal: renderer.waitForTerminal(),
scenarioId: definition.scenarioId,
stopMain: stopMainCaptureOnce,
stopRenderer: stopRendererCaptureOnce,
});
terminal = finalized.driverTerminal;
const { mainCapture, rendererCapture } = finalized;
if (!terminal) {
throw new Error('xtream-measured-capture-lifecycle-invalid');
}
if (mainCapture.captureGeneration !== captureGeneration) {
throw new Error('xtream-measured-capture-generation-changed');
}
controlState = await control.state();
failureStage = 'evidence-validation';
await Promise.all([
writeXtreamSafeJson(
join(iterationDirectory, 'main-capture.json'),
mainCapture,
safety
),
writeXtreamSafeJson(
join(iterationDirectory, 'renderer-capture.json'),
rendererCapture,
safety
),
writeXtreamSafeJson(
join(iterationDirectory, 'terminal-evidence.json'),
terminal,
safety
),
writeXtreamSafeJson(
join(iterationDirectory, 'trigger-evidence.json'),
trigger,
safety
),
writeXtreamSafeJson(
join(iterationDirectory, 'control-state.json'),
controlState,
safety
),
]);
assertXtreamControlStateForScenario(
definition.scenarioId,
controlState,
fixture
);
const playlistId = deriveXtreamMeasuredPlaylistId(mainCapture.requests);
const catalogSnapshot = await readXtreamCatalogDatabaseSnapshot(
app.electronApp,
{
captureStoppedEpochMs: mainCapture.captureStoppedEpochMs,
dataDirectory,
playlistId,
requireFromPath: join(
configuration.workspaceRoot,
'dist/apps/electron-backend/main.js'
),
}
);
await writeXtreamSafeJson(
join(iterationDirectory, 'catalog-snapshot.json'),
catalogSnapshot,
safety
);
const catalogVerification = createXtreamCatalogVerification(
catalogSnapshot,
fixture,
definition.scenarioId
);
const diagnosticEvidence = await createXtreamDiagnosticEvidence(
definition.kind,
mainCapture,
rendererCapture,
iterationDirectory
);
const processEvidence = await createXtreamIterationProcessEvidence({
...launch,
diagnosticDirectorySha256:
diagnosticEvidence?.directorySha256 ?? null,
iterationDirectory,
});
const raw = assembleXtreamRawIteration({
catalogVerification,
definition,
diagnosticArtifacts: diagnosticEvidence,
mainCapture,
process: processEvidence,
rendererCapture,
terminal,
trigger,
});
failureStage = 'result-persistence';
return persistXtreamIteration(
iterationDirectory,
raw,
diagnosticEvidence,
safety
);
} catch (failure) {
const electronApp = app?.electronApp;
await persistXtreamIterationFailureEvidenceAndThrow(failure, {
evidence: {
control: async () => controlState ?? control.state(),
...(electronApp
? {
mainStatus: () =>
readXtreamMainCaptureStatus(electronApp),
}
: {}),
...(stopMainCaptureOnce
? { mainCapture: stopMainCaptureOnce }
: {}),
...(stopRendererCaptureOnce
? { rendererCapture: stopRendererCaptureOnce }
: {}),
...(terminal ? { terminal: async () => terminal } : {}),
...(trigger ? { trigger: async () => trigger } : {}),
},
persist: (filename, value) =>
writeXtreamSafeJson(
join(iterationDirectory, filename),
value,
safety
),
stage: failureStage,
});
} finally {
await prepared?.dispose().catch(() => undefined);
await renderer?.dispose().catch(() => undefined);
if (app) await closeElectronApp(app);
await rm(dataDirectory, { force: true, recursive: true }).catch(
() => undefined
);
}
}
@@ -0,0 +1,250 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { describe, it } from 'node:test';
import {
createXtreamSingleAttemptCapture,
persistXtreamIterationFailureEvidence,
persistXtreamIterationFailureEvidenceAndThrow,
} from './xtream-benchmark-lifecycle';
const performanceDirectory = resolve(process.cwd(), 'src/performance');
describe('Xtream benchmark failure lifecycle', () => {
it('memoizes capture finalization across success and failure paths', async () => {
let attempts = 0;
const capture = createXtreamSingleAttemptCapture(async () => {
attempts += 1;
return Object.freeze({ capture: 'complete' });
});
const [first, second] = await Promise.all([capture(), capture()]);
const third = await capture();
assert.equal(attempts, 1);
assert.strictEqual(first, second);
assert.strictEqual(second, third);
let failedAttempts = 0;
const failedCapture = createXtreamSingleAttemptCapture(async () => {
failedAttempts += 1;
throw new Error('fixed-test-failure');
});
await assert.rejects(failedCapture, /fixed-test-failure/);
await assert.rejects(failedCapture, /fixed-test-failure/);
assert.equal(failedAttempts, 1);
});
it('persists all available evidence and only fixed failure metadata', async () => {
const persisted = new Map<string, unknown>();
const attempts: string[] = [];
await persistXtreamIterationFailureEvidence({
evidence: {
control: async () => {
attempts.push('control');
return { epoch: 11 };
},
mainCapture: async () => {
attempts.push('mainCapture');
return { captureGeneration: 3 };
},
mainStatus: async () => {
attempts.push('mainStatus');
return { captureGeneration: 3, pending: 1 };
},
rendererCapture: async () => {
attempts.push('rendererCapture');
throw new Error(
'https://performance:performance@example.invalid?token=secret'
);
},
terminal: async () => {
attempts.push('terminal');
return { terminalEpochMs: 42 };
},
},
persist: async (filename, value) => {
persisted.set(filename, value);
},
stage: 'terminal-settlement',
});
assert.deepEqual(attempts.sort(), [
'control',
'mainCapture',
'mainStatus',
'rendererCapture',
'terminal',
]);
assert.deepEqual([...persisted.keys()].sort(), [
'failure-control-state.json',
'failure-main-capture.json',
'failure-main-status.json',
'failure-terminal-evidence.json',
'failure.json',
]);
const metadata = persisted.get('failure.json');
assert.deepEqual(metadata, {
evidence: {
control: 'captured',
mainCapture: 'captured',
mainStatus: 'captured',
rendererCapture: 'capture-failed',
terminal: 'captured',
trigger: 'not-available',
},
reason: 'xtream-benchmark-iteration-failed',
stage: 'terminal-settlement',
status: 'failed',
validForComparison: false,
});
const serialized = JSON.stringify([...persisted.values()]);
assert.doesNotMatch(serialized, /performance:performance|secret/iu);
assert.doesNotMatch(serialized, /message|stack/iu);
});
it('persists fixed failure metadata when capture setup never completed', async () => {
const persisted = new Map<string, unknown>();
await persistXtreamIterationFailureEvidence({
evidence: {},
persist: async (filename, value) => {
persisted.set(filename, value);
},
stage: 'capture-setup',
});
assert.deepEqual([...persisted.keys()], ['failure.json']);
assert.deepEqual(persisted.get('failure.json'), {
evidence: {
control: 'not-available',
mainCapture: 'not-available',
mainStatus: 'not-available',
rendererCapture: 'not-available',
terminal: 'not-available',
trigger: 'not-available',
},
reason: 'xtream-benchmark-iteration-failed',
stage: 'capture-setup',
status: 'failed',
validForComparison: false,
});
});
it('fails when aggregate failure evidence cannot be persisted', async () => {
await assert.rejects(
persistXtreamIterationFailureEvidence({
evidence: {},
persist: async (filename) => {
if (filename === 'failure.json') {
throw new Error('fixed-persistence-failure');
}
},
stage: 'capture-setup',
}),
/fixed-persistence-failure/
);
});
it('rethrows the original iteration failure after evidence persistence succeeds', async () => {
const originalFailure = new Error('fixed-original-failure');
await assert.rejects(
persistXtreamIterationFailureEvidenceAndThrow(originalFailure, {
evidence: {},
persist: async () => undefined,
stage: 'capture-setup',
}),
(failure: unknown) => {
assert.strictEqual(failure, originalFailure);
return true;
}
);
});
it('preserves both the original and evidence persistence failures', async () => {
const originalFailure = new Error('fixed-original-failure');
const persistenceFailure = new Error('fixed-persistence-failure');
await assert.rejects(
persistXtreamIterationFailureEvidenceAndThrow(originalFailure, {
evidence: {},
persist: async (filename) => {
if (filename === 'failure.json') {
throw persistenceFailure;
}
},
stage: 'capture-setup',
}),
(failure: unknown) => {
assert.ok(failure instanceof AggregateError);
assert.deepEqual(failure.errors, [
originalFailure,
persistenceFailure,
]);
assert.strictEqual(failure.cause, originalFailure);
return true;
}
);
});
it('collects failure evidence before teardown and writes summary before its guard', () => {
const iteration = readFileSync(
resolve(performanceDirectory, 'xtream-benchmark-iteration.ts'),
'utf8'
);
const failureCatch = iteration.indexOf('} catch (failure) {');
const failurePersistence = iteration.indexOf(
'persistXtreamIterationFailureEvidenceAndThrow(',
failureCatch
);
const teardown = iteration.indexOf('} finally {', failureCatch);
assert.ok(failureCatch >= 0, 'iteration failure catch is missing');
assert.ok(
failurePersistence > failureCatch,
'failure evidence persistence is missing'
);
assert.ok(
teardown > failurePersistence,
'teardown occurs before failure evidence persistence'
);
assert.doesNotMatch(
iteration.slice(failureCatch, teardown),
/if \(captureInstalled\)/u,
'early capture-setup failure metadata is incorrectly gated'
);
assert.match(iteration, /createXtreamSingleAttemptCapture/);
const seededBranch = iteration.indexOf('if (seeded) {');
const seedStop = iteration.indexOf(
'stopMainCaptureOnce = createXtreamSingleAttemptCapture',
seededBranch
);
const seedRun = iteration.indexOf(
'await seedXtreamExistingPortal',
seededBranch
);
assert.ok(
seedStop > seededBranch && seedRun > seedStop,
'seed capture does not have failure finalization armed'
);
const orchestrator = readFileSync(
resolve(performanceDirectory, 'xtream-benchmark-orchestrator.ts'),
'utf8'
);
const summaryWrite = orchestrator.indexOf(
"join(configuration.outputDirectory, 'summary.json')"
);
const validityGuard = orchestrator.indexOf(
'summary.validForComparison !=='
);
assert.ok(summaryWrite >= 0, 'summary persistence is missing');
assert.ok(
validityGuard > summaryWrite,
'comparison-validity guard runs before summary persistence'
);
});
});
@@ -0,0 +1,93 @@
export type XtreamBenchmarkFailureStage =
| 'background-ui'
| 'capture-setup'
| 'capture-stop'
| 'evidence-validation'
| 'measured-capture-start'
| 'operation-trigger'
| 'renderer-capture-start'
| 'result-persistence'
| 'scenario-preparation'
| 'seed'
| 'terminal-settlement';
type FailureEvidenceName =
| 'control'
| 'mainCapture'
| 'mainStatus'
| 'rendererCapture'
| 'terminal'
| 'trigger';
type FailureEvidenceStatus = 'capture-failed' | 'captured' | 'not-available';
interface PersistXtreamIterationFailureEvidenceOptions {
readonly evidence: Partial<
Record<FailureEvidenceName, () => Promise<unknown>>
>;
readonly persist: (filename: string, value: unknown) => Promise<void>;
readonly stage: XtreamBenchmarkFailureStage;
}
const EVIDENCE_ARTIFACTS: readonly [FailureEvidenceName, string][] =
Object.freeze([
['mainStatus', 'failure-main-status.json'],
['mainCapture', 'failure-main-capture.json'],
['rendererCapture', 'failure-renderer-capture.json'],
['trigger', 'failure-trigger-evidence.json'],
['terminal', 'failure-terminal-evidence.json'],
['control', 'failure-control-state.json'],
]);
export function createXtreamSingleAttemptCapture<T>(
capture: () => Promise<T>
): () => Promise<T> {
let attempt: Promise<T> | null = null;
return () => {
attempt ??= Promise.resolve().then(capture);
return attempt;
};
}
export async function persistXtreamIterationFailureEvidence(
options: PersistXtreamIterationFailureEvidenceOptions
): Promise<void> {
const status = {} as Record<FailureEvidenceName, FailureEvidenceStatus>;
for (const [name, filename] of EVIDENCE_ARTIFACTS) {
const capture = options.evidence[name];
if (!capture) {
status[name] = 'not-available';
continue;
}
try {
const value = await capture();
await options.persist(filename, value);
status[name] = 'captured';
} catch {
status[name] = 'capture-failed';
}
}
await options.persist('failure.json', {
evidence: status,
reason: 'xtream-benchmark-iteration-failed',
stage: options.stage,
status: 'failed',
validForComparison: false,
});
}
export async function persistXtreamIterationFailureEvidenceAndThrow(
failure: unknown,
options: PersistXtreamIterationFailureEvidenceOptions
): Promise<never> {
try {
await persistXtreamIterationFailureEvidence(options);
} catch (persistenceFailure) {
throw new AggregateError(
[failure, persistenceFailure],
'Xtream benchmark iteration and failure evidence persistence failed',
{ cause: failure }
);
}
throw failure;
}
@@ -0,0 +1,186 @@
import type { XtreamBenchmarkManifest } from './xtream-benchmark-contract';
import { parseXtreamBenchmarkBuildIdentity } from './xtream-build-identity-schema';
import { assertXtreamArtifactRedacted } from './xtream-diagnostic-artifacts';
import { XTREAM_MEMORY_ACCOUNTING } from './xtream-summary-contract';
export const XTREAM_SHA256 = /^[a-f0-9]{64}$/u;
export const XTREAM_IDENTIFIER =
/^(?!(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$))[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9_-])?$/iu;
const ROOT_KEYS = [
'build',
'environment',
'fixture',
'memoryAccounting',
'mode',
'runtime',
'source',
'suite',
'variant',
] as const;
const ENVIRONMENT_KEYS = [
'cdpAddress',
'cdpPort',
'databaseWorkerBatchDelayMs',
'exposeGc',
'freshUserDataPerIteration',
'perfCapture',
'perfWorkerProfiling',
'traceRendererConsole',
] as const;
const FIXTURE_KEYS = [
'bytes',
'catalogSha256',
'categories',
'items',
'scenario',
'seed',
] as const;
const RUNTIME_KEYS = [
'arch',
'electronVersion',
'harnessNodeVersion',
'platform',
] as const;
const SOURCE_KEYS = ['gitCommit', 'gitDiffSha256', 'gitDirty'] as const;
const MEMORY_KEYS = Object.keys(XTREAM_MEMORY_ACCOUNTING);
export function parseXtreamBenchmarkManifest(
value: unknown
): XtreamBenchmarkManifest {
try {
const input = snapshotExactRecord(value, ROOT_KEYS);
const build = parseXtreamBenchmarkBuildIdentity(input['build']);
const environment = snapshotExactRecord(
input['environment'],
ENVIRONMENT_KEYS
);
const fixture = snapshotExactRecord(input['fixture'], FIXTURE_KEYS);
const memoryAccounting = snapshotExactRecord(
input['memoryAccounting'],
MEMORY_KEYS
);
const runtime = snapshotExactRecord(input['runtime'], RUNTIME_KEYS);
const source = snapshotExactRecord(input['source'], SOURCE_KEYS);
const snapshot = {
build,
environment: { ...environment },
fixture: { ...fixture },
memoryAccounting: { ...memoryAccounting },
mode: input['mode'],
runtime: { ...runtime },
source: { ...source },
suite: input['suite'],
variant: input['variant'],
};
assertXtreamArtifactRedacted(snapshot);
if (!isValidManifest(snapshot)) invalid();
return freezeDeep(snapshot) as unknown as XtreamBenchmarkManifest;
} catch {
throw new Error('invalid Xtream benchmark manifest');
}
}
export function assertXtreamBenchmarkManifest(
value: unknown
): asserts value is XtreamBenchmarkManifest {
parseXtreamBenchmarkManifest(value);
}
function isValidManifest(
manifest: Record<string, unknown> & {
environment: Record<string, unknown>;
fixture: Record<string, unknown>;
memoryAccounting: Record<string, unknown>;
runtime: Record<string, unknown>;
source: Record<string, unknown>;
}
): boolean {
const { environment, fixture, memoryAccounting, runtime, source } =
manifest;
return (
environment['cdpAddress'] === '127.0.0.1' &&
isValidXtreamCdpPort(manifest['mode'], environment['cdpPort']) &&
environment['databaseWorkerBatchDelayMs'] === 0 &&
environment['exposeGc'] === true &&
environment['freshUserDataPerIteration'] === true &&
environment['perfCapture'] === '1' &&
environment['perfWorkerProfiling'] === '1' &&
environment['traceRendererConsole'] === '0' &&
fixture['scenario'] === 'performance-100k' &&
fixture['seed'] === 91_001 &&
fixture['categories'] === 100 &&
fixture['items'] === 100_000 &&
isPositiveSafeInteger(fixture['bytes']) &&
typeof fixture['catalogSha256'] === 'string' &&
XTREAM_SHA256.test(fixture['catalogSha256']) &&
Object.values(runtime).every(
(entry) => typeof entry === 'string' && entry.length > 0
) &&
typeof source['gitCommit'] === 'string' &&
/^[a-f0-9]{40}$/u.test(source['gitCommit']) &&
typeof source['gitDiffSha256'] === 'string' &&
XTREAM_SHA256.test(source['gitDiffSha256']) &&
typeof source['gitDirty'] === 'boolean' &&
(manifest['mode'] !== 'formal' || !source['gitDirty']) &&
(manifest['mode'] === 'formal' || manifest['mode'] === 'smoke') &&
manifest['suite'] === 'xtream-performance-100k' &&
typeof manifest['variant'] === 'string' &&
XTREAM_IDENTIFIER.test(manifest['variant']) &&
MEMORY_KEYS.every(
(key) =>
memoryAccounting[key] ===
XTREAM_MEMORY_ACCOUNTING[
key as keyof typeof XTREAM_MEMORY_ACCOUNTING
]
)
);
}
function isValidXtreamCdpPort(mode: unknown, cdpPort: unknown): boolean {
return (
(mode === 'formal' || mode === 'smoke') &&
typeof cdpPort === 'number' &&
Number.isInteger(cdpPort) &&
cdpPort >= 1024 &&
cdpPort <= 65_535 &&
(mode === 'smoke' || cdpPort === 9222)
);
}
function snapshotExactRecord(
value: unknown,
expectedKeys: readonly string[]
): Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
invalid();
}
const keys = Reflect.ownKeys(value);
if (
keys.length !== expectedKeys.length ||
keys.some(
(key) => typeof key !== 'string' || !expectedKeys.includes(key)
)
) {
invalid();
}
return Object.fromEntries(
expectedKeys.map((key) => [key, Reflect.get(value, key)])
);
}
function freezeDeep<T>(value: T): T {
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
Object.values(value).forEach(freezeDeep);
Object.freeze(value);
}
return value;
}
function isPositiveSafeInteger(value: unknown): boolean {
return Number.isSafeInteger(value) && Number(value) > 0;
}
function invalid(): never {
throw new Error('invalid manifest');
}
@@ -0,0 +1,119 @@
import { mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import {
createXtreamIterationDefinitions,
listXtreamBenchmarkScenarios,
} from './xtream-benchmark-contract';
import {
assertXtreamBenchmarkBuildUnchanged,
createXtreamBenchmarkManifest,
resolveXtreamBenchmarkConfiguration,
} from './xtream-benchmark-configuration';
import {
createXtreamArtifactSafetyContext,
writeXtreamSafeJson,
} from './xtream-benchmark-artifacts';
import { runXtreamBenchmarkIteration } from './xtream-benchmark-iteration';
import { createXtreamBenchmarkSummary } from './xtream-benchmark-report';
import {
assertXtreamControlReadyAfterObservationReset,
createXtreamControlClient,
} from './xtream-control-client';
import type { XtreamDiagnosticArtifactEvidence } from './xtream-diagnostic-artifacts';
import type { XtreamValidatedIterationResult } from './xtream-summary-contract';
export async function runXtreamBenchmark(): Promise<void> {
const configuration = await resolveXtreamBenchmarkConfiguration();
const safety = createXtreamArtifactSafetyContext(
configuration.controlToken
);
const control = createXtreamControlClient({
origin: configuration.mockOrigin,
token: configuration.controlToken,
});
const iterations: XtreamValidatedIterationResult[] = [];
const diagnosticEvidence = new Map<
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence
>();
const seenElectronPids = new Set<number>();
try {
await control.resetAll();
const expectedFixture = await control.prepare();
await control.resetObservations();
assertXtreamControlReadyAfterObservationReset(
await control.state(),
expectedFixture
);
const manifest = createXtreamBenchmarkManifest(
configuration,
expectedFixture
);
await writeXtreamSafeJson(
join(configuration.outputDirectory, 'manifest.json'),
manifest,
safety
);
const definitions = createXtreamIterationDefinitions(
configuration.smoke
);
for (const scenario of listXtreamBenchmarkScenarios()) {
const scenarioDirectory = join(
configuration.outputDirectory,
scenario.id
);
await mkdir(scenarioDirectory, {
mode: 0o700,
recursive: false,
});
for (const definition of definitions.filter(
({ scenarioId }) => scenarioId === scenario.id
)) {
console.log(
`[performance] ${configuration.variant}/${scenario.id}/${definition.runId} starting`
);
const persisted = await runXtreamBenchmarkIteration({
configuration,
control,
definition,
expectedFixture,
scenarioDirectory,
seenElectronPids,
});
iterations.push(persisted.iteration);
if (persisted.diagnosticEvidence) {
diagnosticEvidence.set(
persisted.iteration,
persisted.diagnosticEvidence
);
}
console.log(
`[performance] ${scenario.id}/${definition.runId} total=${persisted.iteration.phases.totalMs.toFixed(3)}ms`
);
}
}
await assertXtreamBenchmarkBuildUnchanged(configuration);
const summary = createXtreamBenchmarkSummary(
manifest,
iterations,
diagnosticEvidence
);
await writeXtreamSafeJson(
join(configuration.outputDirectory, 'summary.json'),
summary,
safety
);
if (summary.validForComparison !== (configuration.mode === 'formal')) {
throw new Error('xtream-benchmark-comparison-validity-invalid');
}
console.log(
`[performance] completed: ${join(
configuration.outputDirectory,
'summary.json'
)}`
);
} finally {
await control.resetAll().catch(() => undefined);
}
}
@@ -0,0 +1,180 @@
import type { MakeDirectoryOptions } from 'node:fs';
import { lstat, mkdir, rmdir } from 'node:fs/promises';
import {
basename,
dirname,
isAbsolute,
join,
relative,
resolve,
sep,
} from 'node:path';
import { assertXtreamSafePathIdentifier } from './xtream-exact-schema';
export const XTREAM_BENCHMARK_FORMAL_CDP_PORT = 9222;
const OUTPUT_TIMESTAMP =
/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z(?:-[a-z][a-z0-9-]{0,63})?$/u;
export interface XtreamBenchmarkOutputLayout {
readonly outputDirectory: string;
readonly outputRoot: string;
readonly performanceRoot: string;
}
export type XtreamMkdir = (
path: string,
options?: MakeDirectoryOptions
) => Promise<string | undefined | void>;
export function resolveXtreamBenchmarkOutputLayout(
repositoryRoot: string,
requestedRoot: string,
variant: string
): XtreamBenchmarkOutputLayout {
const root = resolve(repositoryRoot);
const performanceRoot = join(root, 'dist', 'performance');
const outputRoot = resolve(requestedRoot);
if (
!isAbsolute(repositoryRoot) ||
!isAbsolute(requestedRoot) ||
requestedRoot !== outputRoot ||
dirname(outputRoot) !== performanceRoot ||
!isValidTimestampDirectory(basename(outputRoot))
) {
throw new Error(
'Performance output must be an absolute fresh timestamp path below dist/performance'
);
}
try {
assertXtreamSafePathIdentifier(variant, 'variant');
} catch {
throw new Error('IPTVNATOR_PERF_VARIANT must be a safe Xtream variant');
}
const outputDirectory = join(outputRoot, variant);
if (!isStrictDescendant(outputRoot, outputDirectory)) {
throw new Error('IPTVNATOR_PERF_VARIANT must be a safe Xtream variant');
}
return Object.freeze({ outputDirectory, outputRoot, performanceRoot });
}
export function resolveXtreamBenchmarkCdpPort(
mode: 'formal' | 'smoke',
requestedPort: string | undefined
): number {
const port =
requestedPort === undefined
? XTREAM_BENCHMARK_FORMAL_CDP_PORT
: Number(requestedPort);
if (
(requestedPort !== undefined && !/^[1-9]\d*$/u.test(requestedPort)) ||
!Number.isSafeInteger(port) ||
port < 1024 ||
port > 65_535
) {
throw new Error('IPTVNATOR_PERF_CDP_PORT is invalid');
}
if (mode === 'formal' && port !== XTREAM_BENCHMARK_FORMAL_CDP_PORT) {
throw new Error(
`Formal Xtream performance runs require CDP port ${XTREAM_BENCHMARK_FORMAL_CDP_PORT}`
);
}
return port;
}
export async function assertXtreamBenchmarkOutputPathHasNoSymlinks(
repositoryRoot: string,
outputRoot: string
): Promise<void> {
const root = resolve(repositoryRoot);
const output = resolve(outputRoot);
if (!isStrictDescendant(root, output)) {
throw new Error('Xtream performance output escapes the repository');
}
const components = relative(root, output).split(sep);
let current = root;
for (const component of ['', ...components]) {
current = component ? join(current, component) : current;
try {
if ((await lstat(current)).isSymbolicLink()) {
throw new Error(
`Xtream performance output contains a symbolic link: ${current}`
);
}
} catch (error) {
if (isMissingPath(error)) return;
throw error;
}
}
}
export async function createExclusiveXtreamBenchmarkOutputLayout(
repositoryRoot: string,
layout: XtreamBenchmarkOutputLayout,
createDirectory: XtreamMkdir = mkdir
): Promise<void> {
await createDirectory(layout.performanceRoot, {
mode: 0o700,
recursive: true,
});
await assertXtreamBenchmarkOutputPathHasNoSymlinks(
repositoryRoot,
layout.outputRoot
);
await createDirectory(layout.outputRoot, {
mode: 0o700,
recursive: false,
});
try {
await createDirectory(layout.outputDirectory, {
mode: 0o700,
recursive: false,
});
} catch (error) {
await rmdir(layout.outputRoot).catch(() => undefined);
throw error;
}
await assertXtreamBenchmarkOutputPathHasNoSymlinks(
repositoryRoot,
layout.outputDirectory
);
}
function isValidTimestampDirectory(value: string): boolean {
const match = OUTPUT_TIMESTAMP.exec(value);
if (!match) return false;
const [, year, month, day, hour, minute, second] = match;
const timestamp = Date.UTC(
Number(year),
Number(month) - 1,
Number(day),
Number(hour),
Number(minute),
Number(second)
);
return (
Number.isFinite(timestamp) &&
new Date(timestamp).toISOString().replace(/[-:]/gu, '').slice(0, 15) ===
`${year}${month}${day}T${hour}${minute}${second}`
);
}
function isStrictDescendant(parent: string, candidate: string): boolean {
const pathFromParent = relative(resolve(parent), resolve(candidate));
return (
pathFromParent.length > 0 &&
pathFromParent !== '..' &&
!pathFromParent.startsWith(`..${sep}`) &&
!isAbsolute(pathFromParent)
);
}
function isMissingPath(error: unknown): boolean {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
error.code === 'ENOENT'
);
}
@@ -0,0 +1,178 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
createXtreamIterationDefinitions,
XTREAM_ITERATION_KIND,
XTREAM_SCENARIO_ID,
type XtreamIterationDefinition,
} from './xtream-benchmark-contract';
import {
validateXtreamDiagnosticArtifacts,
type XtreamDiagnosticArtifactEvidence,
type XtreamDiagnosticCapture,
} from './xtream-diagnostic-artifacts';
import { CPU_PROFILE } from './initial-import-diagnostic-test-fixtures';
import {
iteration,
persist,
withWorkerRequestOverlap,
} from './xtream-benchmark-report.fixtures';
import type { XtreamValidatedIterationResult } from './xtream-summary-contract';
const HEAP_SNAPSHOT = {
edges: [0, 0, 0],
nodes: [0, 0, 1, 0, 1, 0],
snapshot: {
meta: {
edge_fields: ['type', 'name_or_index', 'to_node'],
edge_types: [['context'], 'string_or_number', 'node'],
node_fields: [
'type',
'name',
'id',
'self_size',
'edge_count',
'detachedness',
],
node_types: [
['hidden'],
'string',
'number',
'number',
'number',
'number',
],
},
},
strings: ['', 'root'],
};
const RENDERER_TRACE = {
traceEvents: [{ name: 'xtream-diagnostic', ph: 'X', ts: 1 }],
};
const directories: string[] = [];
export interface XtreamReportFixture {
readonly diagnosticEvidence: ReadonlyMap<
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence
>;
readonly iterations: readonly XtreamValidatedIterationResult[];
}
export async function formalReportFixture(): Promise<XtreamReportFixture> {
return reportFixture(createXtreamIterationDefinitions(false), 10, true);
}
export async function smokeReportFixture(): Promise<XtreamReportFixture> {
return reportFixture(createXtreamIterationDefinitions(true), 100, false);
}
export async function clearXtreamProvenanceFixtureDirectories(): Promise<void> {
await Promise.all(
directories
.splice(0)
.map((directory) => rm(directory, { force: true, recursive: true }))
);
}
async function reportFixture(
definitions: readonly XtreamIterationDefinition[],
identityOffset: number,
mixedInitialOverlap: boolean
): Promise<XtreamReportFixture> {
const entries = await Promise.all(
definitions.map(async (definition, index) => {
const identity = index + 1;
let raw = iteration(definition, identity);
let evidence: XtreamDiagnosticArtifactEvidence | null = null;
if (definition.kind === XTREAM_ITERATION_KIND.DIAGNOSTIC) {
evidence = await diagnosticEvidence(identity + identityOffset);
raw = {
...raw,
processIdentity: {
...raw.processIdentity,
profileDirectorySha256: evidence.directorySha256,
},
};
}
if (
mixedInitialOverlap &&
definition.scenarioId ===
XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE &&
['run-02', 'run-04'].includes(definition.runId)
) {
raw = withWorkerRequestOverlap(raw, 2);
}
return {
evidence,
iteration: await persist(raw, identity + identityOffset),
};
})
);
return {
diagnosticEvidence: new Map(
entries.flatMap(({ evidence, iteration: result }) =>
evidence ? [[result, evidence] as const] : []
)
),
iterations: entries.map(({ iteration: result }) => result),
};
}
async function diagnosticEvidence(
identity: number
): Promise<XtreamDiagnosticArtifactEvidence> {
const directory = await mkdtemp(
join(tmpdir(), `iptvnator-xtream-report-diagnostic-${identity}-`)
);
directories.push(directory);
await writeArtifacts(directory);
return validateXtreamDiagnosticArtifacts(capture(directory), directory);
}
function capture(directory: string): XtreamDiagnosticCapture {
return {
main: {
cpuProfilePath: join(directory, 'main.cpuprofile'),
heapSnapshotPath: join(directory, 'main.heapsnapshot'),
workers: [
{
kind: 'database.worker',
ordinal: 1,
profilePath: join(
directory,
'database.worker-1.cpuprofile'
),
snapshotPath: join(
directory,
'database.worker-1.heapsnapshot'
),
},
],
},
renderer: {
cpuProfilePath: join(directory, 'renderer.cpuprofile'),
heapSnapshotPath: join(directory, 'renderer.heapsnapshot'),
tracePath: join(directory, 'renderer.trace.json'),
},
};
}
async function writeArtifacts(directory: string): Promise<void> {
const artifacts = [
['main.cpuprofile', CPU_PROFILE],
['renderer.cpuprofile', CPU_PROFILE],
['database.worker-1.cpuprofile', CPU_PROFILE],
['main.heapsnapshot', HEAP_SNAPSHOT],
['renderer.heapsnapshot', HEAP_SNAPSHOT],
['database.worker-1.heapsnapshot', HEAP_SNAPSHOT],
['renderer.trace.json', RENDERER_TRACE],
] as const;
await Promise.all(
artifacts.map(([filename, value]) =>
writeFile(join(directory, filename), JSON.stringify(value))
)
);
}
@@ -0,0 +1,400 @@
import { createHash } from 'node:crypto';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
XTREAM_ITERATION_KIND,
XTREAM_SCENARIO_ID,
type XtreamIterationDefinition,
} from './xtream-benchmark-contract';
import {
computeXtreamBuildFilesAggregate,
type XtreamBenchmarkBuildIdentity,
type XtreamBuildFileIdentity,
} from './xtream-build-identity';
import { persistThenValidateXtreamIteration } from './xtream-benchmark-report';
import { bindFixtureCancellationEvidence } from './xtream-cancellation-evidence.fixture';
import { bindFixtureIpcWorkerIdentities } from './xtream-ipc-worker-identity.fixture';
import { scenarioCapture } from './xtream-phase-attribution.fixtures';
import { attributeXtreamPhases } from './xtream-phase-attribution';
import {
XTREAM_MEMORY_ACCOUNTING,
type XtreamArtifactSafetyContext,
type XtreamBenchmarkManifest,
type XtreamPersistenceReceipt,
type XtreamRawIterationResult,
type XtreamValidatedIterationResult,
} from './xtream-summary-contract';
import {
invalidateWorkerRequestMetrics,
workerRequestEvidence,
} from './xtream-worker-request-evidence.fixtures';
import { bindFixtureWorkerEvidence } from './xtream-worker-phase-evidence.fixture';
import type { WorkerRequestPerformanceMetrics } from './m3u-refresh-cancellation-contract';
export const TOKEN = 'random-control-token-that-must-never-persist';
const buildFile = (path: string, marker: string): XtreamBuildFileIdentity => ({
bytes: marker.length,
path,
sha256: sha256(marker),
});
const rendererFiles = [
buildFile('dist/apps/web/index.html', 'renderer-index'),
buildFile('dist/apps/web/main-FIXTURE.js', 'renderer-javascript'),
buildFile('dist/apps/web/main-FIXTURE.js.map', 'renderer-source-map'),
] as const;
export const BUILD_IDENTITY: XtreamBenchmarkBuildIdentity = {
electron: {
databaseWorker: {
javascript: buildFile(
'dist/apps/electron-backend/workers/database.worker.js',
'database-worker-javascript'
),
sourceMap: buildFile(
'dist/apps/electron-backend/workers/database.worker.js.map',
'database-worker-source-map'
),
},
main: {
javascript: buildFile(
'dist/apps/electron-backend/main.js',
'main-javascript'
),
sourceMap: buildFile(
'dist/apps/electron-backend/main.js.map',
'main-source-map'
),
},
playlistRefreshWorker: {
javascript: buildFile(
'dist/apps/electron-backend/workers/playlist-refresh.worker.js',
'playlist-worker-javascript'
),
sourceMap: buildFile(
'dist/apps/electron-backend/workers/playlist-refresh.worker.js.map',
'playlist-worker-source-map'
),
},
preload: {
javascript: buildFile(
'dist/apps/electron-backend/main.preload.js',
'preload-javascript'
),
sourceMap: buildFile(
'dist/apps/electron-backend/main.preload.js.map',
'preload-source-map'
),
},
},
renderer: {
aggregateSha256: computeXtreamBuildFilesAggregate(rendererFiles),
bytes: rendererFiles.reduce((total, file) => total + file.bytes, 0),
executableJavaScriptCount: 1,
files: rendererFiles,
sourceMapCount: 1,
},
};
const SAFETY: XtreamArtifactSafetyContext = {
controlToken: TOKEN,
syntheticCredentials: {
password: 'performance',
username: 'performance',
},
};
export const MANIFEST: XtreamBenchmarkManifest = {
build: BUILD_IDENTITY,
environment: {
cdpAddress: '127.0.0.1',
cdpPort: 9222,
databaseWorkerBatchDelayMs: 0,
exposeGc: true,
freshUserDataPerIteration: true,
perfCapture: '1',
perfWorkerProfiling: '1',
traceRendererConsole: '0',
},
fixture: {
bytes: 12_345,
catalogSha256: hash('1'),
categories: 100,
items: 100_000,
scenario: 'performance-100k',
seed: 91_001,
},
memoryAccounting: XTREAM_MEMORY_ACCOUNTING,
mode: 'formal',
runtime: {
arch: 'arm64',
electronVersion: '38.0.0',
harnessNodeVersion: '22.0.0',
platform: 'darwin',
},
source: {
gitCommit: '0123456789abcdef0123456789abcdef01234567',
gitDiffSha256: hash('2'),
gitDirty: false,
},
suite: 'xtream-performance-100k',
variant: 'baseline',
};
const directories: string[] = [];
export async function clearXtreamReportFixtureDirectories(): Promise<void> {
await Promise.all(
directories
.splice(0)
.map((directory) => rm(directory, { force: true, recursive: true }))
);
}
export async function persist(
raw: XtreamRawIterationResult,
id: number,
afterWrite?: () => void
): Promise<XtreamValidatedIterationResult> {
return persistWith(raw, async (serialized) => {
const directory = await mkdtemp(
join(tmpdir(), `iptvnator-xtream-raw-${id}-`)
);
directories.push(directory);
const absolutePath = join(directory, 'raw.json');
await writeFile(absolutePath, serialized, 'utf8');
afterWrite?.();
return {
absolutePath,
bytes: Buffer.byteLength(serialized),
sha256: sha256(serialized),
};
});
}
export function persistWith(
raw: XtreamRawIterationResult,
write: (serialized: string) => Promise<XtreamPersistenceReceipt>
): Promise<XtreamValidatedIterationResult> {
return persistThenValidateXtreamIteration(raw, SAFETY, write);
}
export function iteration(
definition: XtreamIterationDefinition,
identity: number
): XtreamRawIterationResult {
const diagnostic = definition.kind === XTREAM_ITERATION_KIND.DIAGNOSTIC;
const background =
definition.scenarioId === XTREAM_SCENARIO_ID.BACKGROUND_UI;
const workerBinding = bindFixtureWorkerEvidence(
scenarioCapture(definition.scenarioId),
workerRequestEvidence(definition.scenarioId)
);
const cancellationBinding = bindFixtureCancellationEvidence(
workerBinding.phaseCapture,
workerBinding.requests
);
const ipcWorkerBinding = bindFixtureIpcWorkerIdentities(
cancellationBinding.phaseCapture,
workerBinding.requests
);
const phaseCapture = ipcWorkerBinding.phaseCapture;
const phases = attributeXtreamPhases(phaseCapture);
const requestEvidence = ipcWorkerBinding.requests;
const capturedRequestEvidence = background
? invalidateWorkerRequestMetrics(requestEvidence, 3)
: requestEvidence;
const requestTotalCount = requestEvidence.length;
const profile = {
cpuProfileDurationMicros: diagnostic ? 2_000 : null,
cpuProfileUnavailableReason: diagnostic
? null
: 'not-captured-outside-diagnostic',
} as const;
return {
...definition,
processIdentity: {
captureGeneration: 1,
electronPid: 10_000 + identity,
freshProcess: true,
generationIdentitySha256: numberedHash(identity, 3),
launchId: `launch-${identity}`,
profileDirectorySha256: numberedHash(identity, 4),
},
phaseCapture,
phases,
renderer: {
...profile,
actionLatencyMs: background ? 7 : null,
consoleErrors: [],
dashboardPaintVisibility: background
? 'visible-without-overlay'
: null,
frameGapMaxMs: 45,
frameGapP95Ms: 20,
heartbeatMaxMs: 12,
heartbeatP95Ms: 5,
longTaskCount: 2,
longTaskMaxMs: 75,
navigationLatencyMs: background ? 8 : null,
peakHeapUsedBytes: 300,
peakRssBytes: 500,
postGcHeapUsedBytes: 200,
searchLatencyMs: background ? 9 : null,
storeToPaintMs: phases.angularRenderingMs,
targetId: `renderer-${identity}`,
},
main: {
...profile,
browserWindowId: 1,
cpuMetricScope: 'electron-main-thread',
cpuSystemMicros: 200,
cpuUserMicros: 1_000,
eventLoopDelayMaxMs: 8,
eventLoopDelayP95Ms: 2,
eventLoopDelayP99Ms: 4,
eventLoopUtilization: null,
eventLoopUtilizationUnavailableReason:
'electron-main-embedded-event-loop',
peakHeapUsedBytes: 400,
peakRssBytes: 1_000,
postGcHeapUsedBytes: 250,
responsiveEvents: 0,
unresponsiveEvents: 0,
webContentsId: 2,
},
databaseWorker: {
...profile,
cpuMetricScope: 'sum-valid-request-thread-cpu',
cpuSystemMicros:
capturedRequestEvidence.filter(
({ invalidReason }) => invalidReason === null
).length * 10,
cpuUserMicros:
capturedRequestEvidence.filter(
({ invalidReason }) => invalidReason === null
).length * 20,
currentForCapture: true,
eventLoopDelayMaxMs: 9,
eventLoopDelayMetricScope: 'valid-request-window-proxy',
eventLoopDelayP95Ms: 3,
eventLoopDelayP99Ms: 5,
eventLoopUtilization: 0.6,
kind: 'database.worker',
ordinal: 1,
peakExternalBytes: 40,
peakHeapUsedBytes: 250,
postGcHeapUsedBytes: 150,
requests: {
evidence: capturedRequestEvidence,
eventLoopDelayMaxMs: 9,
eventLoopDelayP95Ms: 3,
eventLoopDelayP99Ms: 5,
eventLoopUtilization: 0.5,
invalidReasons: background
? [
{
count: 3,
reason: 'overlapping-database-worker-requests',
},
]
: [],
invalidSampleCount: background ? 3 : 0,
metricScope: 'valid-request-window-proxy',
totalCount: requestTotalCount,
validSampleCount: requestTotalCount - (background ? 3 : 0),
},
rssBytes: null,
rssUnavailableReason: 'unavailable-per-worker-thread',
wholeWorkerMetricScope: 'whole-worker-operation-window',
},
playlistRefreshWorker: {
instantiated: false,
reason: 'not-instantiated-by-xtream-flow',
},
cancellation: cancellationBinding.cancellation,
cancellationEvidence: cancellationBinding.cancellationEvidence,
catalogVerification: {
databaseStateValid: true,
fixtureIdentityValid: true,
providerCategoryCount: 100,
providerItemCount: 100_000,
verifiedAfterCapture: true,
},
validity: {
actionContractValid: true,
catalogContractValid: true,
databaseWorkerCount: 1,
dbPhaseContractValid: true,
diagnosticArtifactsValid: diagnostic ? true : null,
lateDatabaseRequestCount: 0,
playlistWorkerCount: 0,
rendererTargetCount: 1,
},
};
}
export function withWorkerRequestOverlap(
raw: XtreamRawIterationResult,
invalidSampleCount: number
): XtreamRawIterationResult {
return {
...raw,
databaseWorker: {
...raw.databaseWorker,
requests: {
...raw.databaseWorker.requests,
evidence: invalidateWorkerRequestMetrics(
raw.databaseWorker.requests.evidence.map(
restoreValidRequestMetrics
),
invalidSampleCount
),
invalidReasons:
invalidSampleCount === 0
? []
: [
{
count: invalidSampleCount,
reason: 'overlapping-database-worker-requests',
},
],
invalidSampleCount,
validSampleCount:
raw.databaseWorker.requests.totalCount - invalidSampleCount,
},
},
};
}
function restoreValidRequestMetrics(
request: WorkerRequestPerformanceMetrics
): WorkerRequestPerformanceMetrics {
if (
request.workEndedEpochMs === null ||
request.responsePostedEpochMs === null
) {
throw new Error('invalid Xtream request fixture');
}
return {
...request,
eventLoopDelay: { maxMs: 9, p95Ms: 3, p99Ms: 5 },
eventLoopDelayUnavailableReason: null,
eventLoopUtilization: 0.5,
eventLoopUtilizationUnavailableReason: null,
histogramFlushedEpochMs:
(request.workEndedEpochMs + request.responsePostedEpochMs) / 2,
invalidReason: null,
threadCpuSystemMicros: 10,
threadCpuUnavailableReason: null,
threadCpuUserMicros: 20,
};
}
export function sha256(value: string): string {
return createHash('sha256').update(value).digest('hex');
}
function hash(character: string): string {
return character.repeat(64);
}
function numberedHash(value: number, salt: number): string {
return sha256(`${salt}:${value}`);
}
@@ -0,0 +1,351 @@
import assert from 'node:assert/strict';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, it } from 'node:test';
import {
createXtreamIterationDefinitions,
type XtreamBenchmarkManifest,
} from './xtream-benchmark-contract';
import { createXtreamBenchmarkSummary } from './xtream-benchmark-report';
import {
MANIFEST,
TOKEN,
clearXtreamReportFixtureDirectories,
iteration,
persist,
persistWith,
sha256,
} from './xtream-benchmark-report.fixtures';
import {
clearXtreamProvenanceFixtureDirectories,
formalReportFixture,
smokeReportFixture,
} from './xtream-benchmark-report-provenance.fixtures';
afterEach(async () => {
await Promise.all([
clearXtreamReportFixtureDirectories(),
clearXtreamProvenanceFixtureDirectories(),
]);
});
describe('Xtream benchmark report contract', () => {
it('writes exact raw bytes before semantic validation and verifies a real receipt', async () => {
const raw = iteration(
itemAt(createXtreamIterationDefinitions(false), 1),
1
);
const invalid = {
...raw,
main: { ...raw.main, peakRssBytes: -1 },
};
const order: string[] = [];
await assert.rejects(
() =>
persist(invalid, 1, () => {
order.push('raw-written');
}),
/invalid Xtream iteration/
);
order.push('validation-rejected');
assert.deepEqual(order, ['raw-written', 'validation-rejected']);
await assert.rejects(
() =>
persistWith(raw, async (serialized) => ({
absolutePath: join(tmpdir(), 'does-not-exist.json'),
bytes: Buffer.byteLength(serialized),
sha256: sha256(serialized),
})),
/persistence receipt/
);
});
it('brands the exact persisted bytes when the caller mutates during write', async () => {
const raw = iteration(
itemAt(createXtreamIterationDefinitions(false), 1),
1
);
const persistedPeakRss = raw.main.peakRssBytes;
const validated = await persist(raw, 2, () => {
Object.assign(raw.main, { peakRssBytes: persistedPeakRss + 1 });
});
assert.equal(validated.main.peakRssBytes, persistedPeakRss);
assert.notEqual(validated.main.peakRssBytes, raw.main.peakRssBytes);
assert.throws(
() => Object.assign(validated.main, { peakRssBytes: 1 }),
TypeError
);
});
it('blocks exact token and credential-shaped bytes before write but permits suite labels', async () => {
const raw = iteration(
itemAt(createXtreamIterationDefinitions(false), 1),
1
);
let writes = 0;
for (const leaked of [
TOKEN,
'performance:performance',
'http://performance:performance@127.0.0.1:43123',
'?username=performance&password=performance',
]) {
await assert.rejects(
() =>
persistWith(
{
...raw,
renderer: {
...raw.renderer,
consoleErrors: [leaked],
},
},
async () => {
writes += 1;
throw new Error('must not write');
}
),
/credential-safe/
);
}
assert.equal(writes, 0);
await assert.doesNotReject(() => persist(raw, 2));
});
it('requires exact clean formal source, runtime, CDP, and environment evidence', async () => {
const { diagnosticEvidence, iterations } = await formalReportFixture();
assert.doesNotThrow(() =>
createXtreamBenchmarkSummary(
MANIFEST,
iterations,
diagnosticEvidence
)
);
for (const invalid of [
{ ...MANIFEST, source: { ...MANIFEST.source, gitDirty: true } },
{
...MANIFEST,
environment: { ...MANIFEST.environment, cdpPort: 9223 },
},
{
...MANIFEST,
environment: {
...MANIFEST.environment,
perfWorkerProfiling: '0',
},
},
{
...MANIFEST,
runtime: { ...MANIFEST.runtime, electronVersion: '' },
},
{ ...MANIFEST, variant: { length: 1 } },
]) {
assert.throws(
() =>
createXtreamBenchmarkSummary(
invalid as unknown as XtreamBenchmarkManifest,
iterations,
diagnosticEvidence
),
/invalid Xtream benchmark manifest/
);
}
});
it('requires ordered definitions and unique process/profile/generation identity', async () => {
const { diagnosticEvidence, iterations } = await formalReportFixture();
const shuffled = [...iterations];
[shuffled[0], shuffled[1]] = [itemAt(shuffled, 1), itemAt(shuffled, 0)];
assert.throws(
() =>
createXtreamBenchmarkSummary(
MANIFEST,
shuffled,
diagnosticEvidence
),
/ordered Xtream benchmark schedule/
);
const reused = [...iterations];
const first = itemAt(reused, 0);
const second = itemAt(reused, 1);
reused[1] = {
...second,
processIdentity: first.processIdentity,
};
assert.throws(
() =>
createXtreamBenchmarkSummary(
MANIFEST,
reused,
diagnosticEvidence
),
/fresh process identity/
);
});
it('rejects forged validated objects and excludes diagnostic CPU profiles from headline metrics', async () => {
const { diagnosticEvidence, iterations } = await formalReportFixture();
const forged = [...iterations];
forged[0] = { ...itemAt(forged, 0) };
assert.throws(
() =>
createXtreamBenchmarkSummary(
MANIFEST,
forged,
diagnosticEvidence
),
/validated persistence evidence/
);
const summary = createXtreamBenchmarkSummary(
MANIFEST,
iterations,
diagnosticEvidence
);
const background = summary.scenarios.find(
({ scenarioId }) => scenarioId === 'xtream-background-ui'
);
const initial = summary.scenarios.find(
({ scenarioId }) => scenarioId === 'xtream-initial-import-large'
);
assert.ok(background);
assert.ok(initial);
assert.deepEqual(background.databaseWorkerRequestInvalidReasons, [
'overlapping-database-worker-requests',
]);
assert.equal(
background.metrics.databaseWorkerRequestInvalidSampleCount.min,
3
);
assert.equal(
background.metrics.databaseWorkerRequestInvalidSampleCount.max,
3
);
assert.deepEqual(initial.databaseWorkerRequestInvalidReasons, [
'overlapping-database-worker-requests',
]);
assert.equal(
initial.metrics.databaseWorkerRequestInvalidSampleCount.min,
0
);
assert.equal(
initial.metrics.databaseWorkerRequestInvalidSampleCount.max,
2
);
assert.ok(
summary.scenarios.every(
({
databaseWorkerEventLoopDelayMetricScope,
databaseWorkerRequestMetricScope,
databaseWorkerWholeWorkerMetricScope,
metrics,
}) =>
databaseWorkerEventLoopDelayMetricScope ===
'valid-request-window-proxy' &&
databaseWorkerRequestMetricScope ===
'valid-request-window-proxy' &&
databaseWorkerWholeWorkerMetricScope ===
'whole-worker-operation-window' &&
metrics.databaseWorkerRequestEventLoopDelayP95Ms.count === 5
)
);
assert.equal(summary.validForComparison, true);
assert.ok(Object.isFrozen(summary));
assert.ok(Object.isFrozen(summary.manifest));
assert.ok(Object.isFrozen(summary.iterations));
assert.ok(
Object.isFrozen(
itemAt(summary.iterations, 0).databaseWorker.requests.evidence
)
);
assert.notEqual(summary.manifest, MANIFEST);
assert.notEqual(summary.iterations, iterations);
assert.ok(
summary.scenarios.every(
({
diagnosticProfiles,
mainEventLoopUtilization,
measuredRuns,
metrics,
phases,
}) =>
measuredRuns === 5 &&
metrics.totalMs.count === 5 &&
diagnosticProfiles.mainDurationMicros !== null &&
diagnosticProfiles.artifactProvenance.artifacts.length ===
7 &&
Object.isFrozen(
diagnosticProfiles.artifactProvenance.artifacts
) &&
mainEventLoopUtilization.reason ===
'electron-main-embedded-event-loop' &&
mainEventLoopUtilization.value === null &&
phases.indexes.reason ===
'schema-indexes-pre-exist; operation-creates-none' &&
phases.restoreUserData.value === null &&
phases.structuredCloneAttribution ===
'selected-ipc-proxy-includes-queueing-and-scheduling' &&
!Object.prototype.hasOwnProperty.call(
metrics,
'mainCpuProfileDurationMicros'
)
)
);
assert.equal(
initial.phases.restoreUserData.reason,
'not-invoked-by-scenario'
);
assert.equal(background.phases.restoreUserData.reason, 'no-user-data');
});
it('requires branded one-to-one diagnostic evidence bound to each iteration directory', async () => {
const { diagnosticEvidence, iterations } = await formalReportFixture();
const entries = [...diagnosticEvidence.entries()];
const first = itemAt(entries, 0);
const second = itemAt(entries, 1);
const missing = new Map(entries.slice(1));
const swapped = new Map(entries);
swapped.set(first[0], second[1]);
swapped.set(second[0], first[1]);
const extra = new Map(entries);
extra.set(itemAt(iterations, 0), first[1]);
const forged = new Map(entries);
forged.set(first[0], structuredClone(first[1]));
const malformed = new Map(entries);
malformed.set(first[0], null as unknown as (typeof entries)[number][1]);
for (const evidence of [missing, swapped, extra, forged, malformed]) {
assert.throws(
() =>
createXtreamBenchmarkSummary(
MANIFEST,
iterations,
evidence
),
/diagnostic artifact evidence/
);
}
});
it('keeps one-run smoke output ineligible after all clean gates pass', async () => {
const { diagnosticEvidence, iterations } = await smokeReportFixture();
const summary = createXtreamBenchmarkSummary(
{ ...MANIFEST, mode: 'smoke' },
iterations,
diagnosticEvidence
);
assert.equal(summary.validForComparison, false);
});
});
function itemAt<T>(items: readonly T[], index: number): T {
const item = items[index];
assert.ok(item);
return item;
}
@@ -0,0 +1,387 @@
import {
createXtreamIterationDefinitions,
listXtreamBenchmarkScenarios,
summarizeXtreamNumbers,
type XtreamScenarioId,
} from './xtream-benchmark-contract';
import {
serializeXtreamArtifactForPersistence,
type XtreamDiagnosticArtifactEvidence,
} from './xtream-diagnostic-artifacts';
import {
bindXtreamDiagnosticEvidence,
cloneXtreamSummaryValue,
copyXtreamDiagnosticProvenance,
} from './xtream-benchmark-summary-evidence';
import {
assertFreshXtreamProcessIdentities,
assertOrderedXtreamSchedule,
freezeDeep,
isFiniteNonNegative,
parseXtreamBenchmarkManifest,
snapshotXtreamIterations,
toXtreamRawIteration,
} from './xtream-exact-schema';
import { assertXtreamIterationResult } from './xtream-iteration-validity';
import { assertUniqueXtreamPersistencePaths } from './xtream-persistence-schedule';
import { verifyXtreamPersistenceReceipt } from './xtream-raw-persistence-receipt';
import {
XTREAM_NOT_APPLICABLE,
XTREAM_RENDERER_VISIBILITY,
XTREAM_STRUCTURED_CLONE_ATTRIBUTION,
XTREAM_WORKER_REQUEST_METRIC_SCOPE,
XTREAM_WORKER_WHOLE_METRIC_SCOPE,
type NumericDistribution,
type XtreamArtifactSafetyContext,
type XtreamBenchmarkManifest,
type XtreamBenchmarkSummary,
type XtreamPersistedRawEvidence,
type XtreamPersistenceReceipt,
type XtreamRawIterationResult,
type XtreamScenarioMetrics,
type XtreamScenarioSummary,
type XtreamValidatedIterationResult,
} from './xtream-summary-contract';
const VALIDATED = new WeakSet<object>();
export async function persistThenValidateXtreamIteration(
raw: XtreamRawIterationResult,
safety: XtreamArtifactSafetyContext,
persistRaw: (serialized: string) => Promise<XtreamPersistenceReceipt>
): Promise<XtreamValidatedIterationResult> {
const serialized = serializeXtreamArtifactForPersistence(raw, safety);
const receipt = await persistRaw(serialized);
const evidence = await verifyXtreamPersistenceReceipt(serialized, receipt);
const persisted: unknown = JSON.parse(serialized);
assertXtreamIterationResult(persisted);
return brandValidatedIteration(persisted, evidence);
}
export function createXtreamBenchmarkSummary(
manifest: XtreamBenchmarkManifest,
iterations: readonly XtreamValidatedIterationResult[],
diagnosticEvidence: ReadonlyMap<
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence
>
): XtreamBenchmarkSummary {
const canonicalManifest = parseXtreamBenchmarkManifest(manifest);
const expected = createXtreamIterationDefinitions(
canonicalManifest.mode === 'smoke'
);
const canonicalIterations = snapshotXtreamIterations(iterations);
assertOrderedXtreamSchedule(
canonicalIterations,
expected,
canonicalManifest.mode
);
assertFreshXtreamProcessIdentities(canonicalIterations);
for (const iteration of canonicalIterations) {
assertValidatedIteration(iteration);
if (iteration.validationStatus !== 'valid') invalidIteration();
assertXtreamIterationResult(toXtreamRawIteration(iteration));
}
assertUniqueXtreamPersistencePaths(canonicalIterations);
const boundDiagnosticEvidence = bindXtreamDiagnosticEvidence(
canonicalIterations,
diagnosticEvidence
);
const measuredRunCount = canonicalManifest.mode === 'formal' ? 5 : 1;
const comparable =
canonicalManifest.mode === 'formal' &&
canonicalIterations
.filter(({ kind }) => kind === 'measured')
.every(({ eligibleForComparison }) => eligibleForComparison);
return freezeDeep({
iterations: cloneXtreamSummaryValue(canonicalIterations),
manifest: cloneXtreamSummaryValue(canonicalManifest),
scenarios: listXtreamBenchmarkScenarios().map(({ id }) =>
summarizeScenario(
canonicalIterations,
id,
measuredRunCount,
boundDiagnosticEvidence
)
),
validForComparison: comparable,
});
}
function summarizeScenario(
iterations: readonly XtreamValidatedIterationResult[],
scenarioId: XtreamScenarioId,
measuredRunCount: number,
diagnosticEvidence: ReadonlyMap<
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence
>
): XtreamScenarioSummary {
const measured = iterations.filter(
(iteration) =>
iteration.scenarioId === scenarioId && iteration.kind === 'measured'
);
const diagnostic = iterations.find(
(iteration) =>
iteration.scenarioId === scenarioId &&
iteration.kind === 'diagnostic'
);
if (measured.length !== measuredRunCount || !diagnostic) {
scheduleError(measuredRunCount === 5 ? 'formal' : 'smoke');
}
const provenance = diagnosticEvidence.get(diagnostic);
if (!provenance) invalidIteration();
const restores =
scenarioId === 'xtream-refresh-large' ||
scenarioId === 'xtream-background-ui';
return {
scenarioId,
measuredRuns: measuredRunCount,
databaseWorkerEventLoopDelayMetricScope:
XTREAM_WORKER_REQUEST_METRIC_SCOPE.VALID_REQUEST_WINDOW_PROXY,
databaseWorkerRequestInvalidReasons: [
...new Set(
measured.flatMap((run) =>
run.databaseWorker.requests.invalidReasons.map(
({ reason }) => reason
)
)
),
],
databaseWorkerRequestMetricScope:
XTREAM_WORKER_REQUEST_METRIC_SCOPE.VALID_REQUEST_WINDOW_PROXY,
databaseWorkerWholeWorkerMetricScope:
XTREAM_WORKER_WHOLE_METRIC_SCOPE.OPERATION_WINDOW,
dashboardPaintVisibility:
scenarioId === 'xtream-background-ui'
? XTREAM_RENDERER_VISIBILITY.VISIBLE_WITHOUT_OVERLAY
: null,
diagnosticProfiles: {
artifactProvenance: copyXtreamDiagnosticProvenance(provenance),
databaseWorkerDurationMicros:
provenance.profiles.databaseWorker.durationMicros,
mainDurationMicros: provenance.profiles.main.durationMicros,
rendererDurationMicros: provenance.profiles.renderer.durationMicros,
},
metrics: summarizeMetrics(measured, scenarioId, measuredRunCount),
memory: {
databaseWorkerRss: notApplicable(
XTREAM_NOT_APPLICABLE.DATABASE_WORKER_RSS
),
nativeAndSqlite: notApplicable(
XTREAM_NOT_APPLICABLE.NATIVE_SQLITE_MEMORY
),
playlistRefreshWorker: notApplicable(
XTREAM_NOT_APPLICABLE.PLAYLIST_REFRESH_WORKER
),
},
mainEventLoopUtilization: notApplicable(
XTREAM_NOT_APPLICABLE.MAIN_EVENT_LOOP_UTILIZATION
),
phases: {
debugCopy: notApplicable(XTREAM_NOT_APPLICABLE.DEBUG_COPY),
indexes: notApplicable(XTREAM_NOT_APPLICABLE.INDEXES_PRE_EXIST),
transactionCommit: notApplicable(
XTREAM_NOT_APPLICABLE.COMMIT_INCLUDED_IN_SQLITE_WRITE
),
restoreUserData: notApplicable(
restores
? XTREAM_NOT_APPLICABLE.RESTORE_NO_USER_DATA
: XTREAM_NOT_APPLICABLE.RESTORE_NOT_INVOKED
),
backgroundSearchMarker:
scenarioId === 'xtream-background-ui'
? notApplicable(XTREAM_NOT_APPLICABLE.SOURCES_SEARCH_MARKER)
: null,
structuredCloneAttribution:
XTREAM_STRUCTURED_CLONE_ATTRIBUTION.SELECTED_IPC_PROXY,
},
rendererLongTaskCountTotal: sumValues(
measured,
(run) => run.renderer.longTaskCount
),
responsiveEventsTotal: sumValues(
measured,
(run) => run.main.responsiveEvents
),
unresponsiveEventsTotal: sumValues(
measured,
(run) => run.main.unresponsiveEvents
),
};
}
function summarizeMetrics(
runs: readonly XtreamValidatedIterationResult[],
scenarioId: XtreamScenarioId,
count: number
): XtreamScenarioMetrics {
const metric = (
select: (run: XtreamValidatedIterationResult) => number
): NumericDistribution => distribution(runs, select, count);
const optional = (
select: (run: XtreamValidatedIterationResult) => number | null
): NumericDistribution =>
distribution(runs, (run) => requireNumber(select(run)), count);
return {
totalMs: metric((run) => run.phases.totalMs),
dataAcquisitionMs: metric((run) => run.phases.dataAcquisitionMs),
jsonParsingMs: metric((run) => run.phases.jsonParsingMs),
normalizationMs: metric((run) => run.phases.normalizationMs),
serializationMs: metric((run) => run.phases.serializationMs),
deserializationMs: metric((run) => run.phases.deserializationMs),
structuredCloneProxyMs: metric(
(run) => run.phases.structuredCloneProxyMs
),
sqliteReadMs: metric((run) => run.phases.sqliteReadMs),
sqliteWriteMs: metric((run) => run.phases.sqliteWriteMs),
storeUpdateMs: metric((run) => run.phases.storeUpdateMs),
angularRenderingMs: metric((run) => run.phases.angularRenderingMs),
rendererPeakRssBytes: metric((run) => run.renderer.peakRssBytes),
rendererPeakHeapBytes: metric((run) => run.renderer.peakHeapUsedBytes),
rendererPostGcHeapBytes: metric(
(run) => run.renderer.postGcHeapUsedBytes
),
rendererLongTaskCount: metric((run) => run.renderer.longTaskCount),
rendererLongTaskMaxMs: metric((run) => run.renderer.longTaskMaxMs),
rendererFrameGapP95Ms: metric((run) => run.renderer.frameGapP95Ms),
rendererFrameGapMaxMs: metric((run) => run.renderer.frameGapMaxMs),
rendererHeartbeatP95Ms: metric((run) => run.renderer.heartbeatP95Ms),
rendererHeartbeatMaxMs: metric((run) => run.renderer.heartbeatMaxMs),
rendererNavigationLatencyMs:
scenarioId === 'xtream-background-ui'
? optional((run) => run.renderer.navigationLatencyMs)
: null,
rendererSearchLatencyMs:
scenarioId === 'xtream-background-ui'
? optional((run) => run.renderer.searchLatencyMs)
: null,
rendererActionLatencyMs:
scenarioId === 'xtream-background-ui'
? optional((run) => run.renderer.actionLatencyMs)
: null,
mainCpuUserMicros: metric((run) => run.main.cpuUserMicros),
mainCpuSystemMicros: metric((run) => run.main.cpuSystemMicros),
mainPeakRssBytes: metric((run) => run.main.peakRssBytes),
mainPeakHeapBytes: metric((run) => run.main.peakHeapUsedBytes),
mainPostGcHeapBytes: metric((run) => run.main.postGcHeapUsedBytes),
mainEventLoopDelayP95Ms: metric((run) => run.main.eventLoopDelayP95Ms),
mainEventLoopDelayP99Ms: metric((run) => run.main.eventLoopDelayP99Ms),
mainEventLoopDelayMaxMs: metric((run) => run.main.eventLoopDelayMaxMs),
databaseWorkerCpuUserMicros: metric(
(run) => run.databaseWorker.cpuUserMicros
),
databaseWorkerCpuSystemMicros: metric(
(run) => run.databaseWorker.cpuSystemMicros
),
databaseWorkerPeakHeapBytes: metric(
(run) => run.databaseWorker.peakHeapUsedBytes
),
databaseWorkerPostGcHeapBytes: metric(
(run) => run.databaseWorker.postGcHeapUsedBytes
),
databaseWorkerRequestTotalCount: metric(
(run) => run.databaseWorker.requests.totalCount
),
databaseWorkerRequestValidSampleCount: metric(
(run) => run.databaseWorker.requests.validSampleCount
),
databaseWorkerRequestInvalidSampleCount: metric(
(run) => run.databaseWorker.requests.invalidSampleCount
),
databaseWorkerRequestEventLoopDelayP95Ms: metric(
(run) => run.databaseWorker.requests.eventLoopDelayP95Ms
),
databaseWorkerRequestEventLoopDelayP99Ms: metric(
(run) => run.databaseWorker.requests.eventLoopDelayP99Ms
),
databaseWorkerRequestEventLoopDelayMaxMs: metric(
(run) => run.databaseWorker.requests.eventLoopDelayMaxMs
),
databaseWorkerRequestEventLoopUtilization: metric(
(run) => run.databaseWorker.requests.eventLoopUtilization
),
databaseWorkerPeakExternalBytes: metric(
(run) => run.databaseWorker.peakExternalBytes
),
databaseWorkerEventLoopDelayP95Ms: metric(
(run) => run.databaseWorker.eventLoopDelayP95Ms
),
databaseWorkerEventLoopDelayP99Ms: metric(
(run) => run.databaseWorker.eventLoopDelayP99Ms
),
databaseWorkerEventLoopDelayMaxMs: metric(
(run) => run.databaseWorker.eventLoopDelayMaxMs
),
databaseWorkerEventLoopUtilization: metric(
(run) => run.databaseWorker.eventLoopUtilization
),
cancelAcknowledgementLatencyMs:
scenarioId === 'xtream-cancel-import'
? optional((run) => run.cancellation.acknowledgementLatencyMs)
: null,
};
}
function distribution(
runs: readonly XtreamValidatedIterationResult[],
select: (run: XtreamValidatedIterationResult) => number,
expectedCount: number
): NumericDistribution {
const values = runs.map(select);
if (
values.length !== expectedCount ||
values.some((value) => !isFiniteNonNegative(value))
) {
throw new Error(
'Xtream metric requires complete finite measured values'
);
}
return summarizeXtreamNumbers(values);
}
function sumValues(
runs: readonly XtreamValidatedIterationResult[],
select: (run: XtreamValidatedIterationResult) => number
): number {
return runs.reduce((total, run) => total + select(run), 0);
}
function requireNumber(value: number | null): number {
if (!isFiniteNonNegative(value)) invalidIteration();
return value;
}
function notApplicable(reason: string): { reason: string; value: null } {
return { reason, value: null };
}
function brandValidatedIteration(
raw: XtreamRawIterationResult,
persistence: XtreamPersistedRawEvidence
): XtreamValidatedIterationResult {
const result = freezeDeep({
...raw,
persistence,
validationStatus: 'valid' as const,
});
VALIDATED.add(result);
return result;
}
function assertValidatedIteration(value: XtreamValidatedIterationResult): void {
if (!VALIDATED.has(value)) {
throw new Error('Xtream validated persistence evidence is missing');
}
}
function scheduleError(mode: 'formal' | 'smoke'): never {
throw new Error(
`Xtream benchmark requires exactly ${mode === 'formal' ? 'five' : 'one'} measured runs per scenario`
);
}
function invalidIteration(): never {
throw new Error('invalid Xtream iteration');
}
@@ -0,0 +1,52 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
XTREAM_SCENARIO_ID,
type XtreamScenarioId,
} from './xtream-benchmark-contract';
import {
deriveXtreamMeasuredPlaylistId,
xtreamScenarioRequiresSeed,
} from './xtream-benchmark-runtime';
describe('Xtream benchmark runtime evidence', () => {
it('seeds only scenarios that measure an operation on an existing portal', () => {
const expected: Record<XtreamScenarioId, boolean> = {
[XTREAM_SCENARIO_ID.INITIAL_IMPORT_LARGE]: false,
[XTREAM_SCENARIO_ID.REFRESH_LARGE]: true,
[XTREAM_SCENARIO_ID.DELETE_LARGE]: true,
[XTREAM_SCENARIO_ID.CANCEL_IMPORT]: false,
[XTREAM_SCENARIO_ID.BACKGROUND_UI]: true,
};
for (const scenarioId of Object.values(XTREAM_SCENARIO_ID)) {
assert.equal(
xtreamScenarioRequiresSeed(scenarioId),
expected[scenarioId]
);
}
});
it('derives exactly one non-null measured playlist identity', () => {
assert.equal(
deriveXtreamMeasuredPlaylistId([
{ playlistId: null },
{ playlistId: 'playlist-1' },
{ playlistId: 'playlist-1' },
]),
'playlist-1'
);
for (const requests of [
[{ playlistId: null }],
[{ playlistId: 'playlist-1' }, { playlistId: 'playlist-2' }],
[{ playlistId: '' }],
]) {
assert.throws(
() => deriveXtreamMeasuredPlaylistId(requests),
/xtream-measured-playlist-identity-invalid/
);
}
});
});
@@ -0,0 +1,52 @@
import type { Page } from '@playwright/test';
import {
XTREAM_SCENARIO_ID,
type XtreamScenarioId,
} from './xtream-benchmark-contract';
export function xtreamScenarioRequiresSeed(
scenarioId: XtreamScenarioId
): boolean {
return (
scenarioId === XTREAM_SCENARIO_ID.REFRESH_LARGE ||
scenarioId === XTREAM_SCENARIO_ID.DELETE_LARGE ||
scenarioId === XTREAM_SCENARIO_ID.BACKGROUND_UI
);
}
export function deriveXtreamMeasuredPlaylistId(
requests: readonly { readonly playlistId: string | null }[]
): string {
const identities = new Set(
requests
.map(({ playlistId }) => playlistId)
.filter(
(playlistId): playlistId is string =>
typeof playlistId === 'string' && playlistId.length > 0
)
);
const playlistId = identities.values().next().value as string | undefined;
if (identities.size !== 1 || !playlistId) {
throw new Error('xtream-measured-playlist-identity-invalid');
}
return playlistId;
}
export async function disableGenericElectronFixtureProbes(
page: Page
): Promise<void> {
await page.evaluate(() => {
window.__portalDebugUnsubscribe?.();
window.__portalDebugUnsubscribe = undefined;
window.__portalDebugEvents = [];
window.__dbOperationUnsubscribe?.();
window.__dbOperationUnsubscribe = undefined;
window.__dbOperationEvents = [];
if (window.__rendererFrameRequestId) {
window.cancelAnimationFrame(window.__rendererFrameRequestId);
}
window.__rendererFrameRequestId = undefined;
window.__rendererFrameCount = 0;
});
}
@@ -0,0 +1,112 @@
import {
assertXtreamDiagnosticArtifactEvidence,
type XtreamDiagnosticArtifactEvidence,
} from './xtream-diagnostic-artifacts';
import type {
XtreamDiagnosticArtifactProvenance,
XtreamValidatedIterationResult,
} from './xtream-summary-contract';
export function bindXtreamDiagnosticEvidence(
iterations: readonly XtreamValidatedIterationResult[],
evidenceByIteration: ReadonlyMap<
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence
>
): ReadonlyMap<
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence
> {
const diagnostic = iterations.filter(({ kind }) => kind === 'diagnostic');
const evidenceEntries = snapshotEvidenceEntries(evidenceByIteration);
if (evidenceEntries.length !== diagnostic.length) {
invalidEvidence();
}
const diagnosticSet = new Set(diagnostic);
const boundEvidence = new Map<
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence
>();
for (const [iteration, evidence] of evidenceEntries) {
if (!diagnosticSet.has(iteration)) {
invalidEvidence();
}
try {
assertXtreamDiagnosticArtifactEvidence(evidence);
} catch {
invalidEvidence();
}
if (
iteration.processIdentity.profileDirectorySha256 !==
evidence.directorySha256 ||
iteration.renderer.cpuProfileDurationMicros !==
evidence.profiles.renderer.durationMicros ||
iteration.main.cpuProfileDurationMicros !==
evidence.profiles.main.durationMicros ||
iteration.databaseWorker.cpuProfileDurationMicros !==
evidence.profiles.databaseWorker.durationMicros ||
iteration.databaseWorker.ordinal !==
evidence.profiles.databaseWorker.ordinal
) {
invalidEvidence();
}
Map.prototype.set.call(boundEvidence, iteration, evidence);
}
if (
diagnostic.some(
(iteration) => !Map.prototype.has.call(boundEvidence, iteration)
)
) {
invalidEvidence();
}
return boundEvidence;
}
export function copyXtreamDiagnosticProvenance(
evidence: XtreamDiagnosticArtifactEvidence
): XtreamDiagnosticArtifactProvenance {
assertXtreamDiagnosticArtifactEvidence(evidence);
return {
artifacts: evidence.artifacts.map((artifact) => ({ ...artifact })),
directorySha256: evidence.directorySha256,
};
}
export function cloneXtreamSummaryValue<T>(value: T): T {
return structuredClone(value);
}
function snapshotEvidenceEntries(
value: ReadonlyMap<
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence
>
): readonly (readonly [
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence,
])[] {
try {
if (!(value instanceof Map)) invalidEvidence();
const sizeGetter = Object.getOwnPropertyDescriptor(
Map.prototype,
'size'
)?.get;
if (!sizeGetter) invalidEvidence();
const size = Reflect.apply(sizeGetter, value, []) as unknown;
const entries: [
XtreamValidatedIterationResult,
XtreamDiagnosticArtifactEvidence,
][] = [];
Map.prototype.forEach.call(value, (evidence, iteration) => {
entries.push([iteration, evidence]);
});
if (size !== entries.length) invalidEvidence();
return entries;
} catch {
invalidEvidence();
}
}
function invalidEvidence(): never {
throw new Error('invalid Xtream diagnostic artifact evidence binding');
}
@@ -0,0 +1,214 @@
import {
computeXtreamBuildFilesAggregate,
type XtreamBenchmarkBuildIdentity,
type XtreamBuildFileIdentity,
type XtreamBuildPairIdentity,
} from './xtream-build-identity';
const SHA256 = /^[a-f0-9]{64}$/u;
const RENDERER_FILE =
/^dist\/apps\/web\/[A-Za-z0-9][A-Za-z0-9._-]{0,254}\.js(?:\.map)?$/u;
const BUILD_KEYS = ['electron', 'renderer'] as const;
const ELECTRON_KEYS = [
'databaseWorker',
'main',
'playlistRefreshWorker',
'preload',
] as const;
const PAIR_KEYS = ['javascript', 'sourceMap'] as const;
const FILE_KEYS = ['bytes', 'path', 'sha256'] as const;
const RENDERER_KEYS = [
'aggregateSha256',
'bytes',
'executableJavaScriptCount',
'files',
'sourceMapCount',
] as const;
const ELECTRON_PATHS = {
databaseWorker: 'dist/apps/electron-backend/workers/database.worker.js',
main: 'dist/apps/electron-backend/main.js',
playlistRefreshWorker:
'dist/apps/electron-backend/workers/playlist-refresh.worker.js',
preload: 'dist/apps/electron-backend/main.preload.js',
} as const;
export function parseXtreamBenchmarkBuildIdentity(
value: unknown
): XtreamBenchmarkBuildIdentity {
try {
const input = exactRecord(value, BUILD_KEYS);
const electronInput = exactRecord(input['electron'], ELECTRON_KEYS);
const rendererInput = exactRecord(input['renderer'], RENDERER_KEYS);
const electron = Object.freeze({
databaseWorker: pair(
electronInput['databaseWorker'],
ELECTRON_PATHS.databaseWorker
),
main: pair(electronInput['main'], ELECTRON_PATHS.main),
playlistRefreshWorker: pair(
electronInput['playlistRefreshWorker'],
ELECTRON_PATHS.playlistRefreshWorker
),
preload: pair(electronInput['preload'], ELECTRON_PATHS.preload),
});
const renderer = rendererIdentity(rendererInput);
return Object.freeze({ electron, renderer });
} catch {
throw new Error('invalid Xtream benchmark build identity');
}
}
function pair(value: unknown, javascriptPath: string): XtreamBuildPairIdentity {
const input = exactRecord(value, PAIR_KEYS);
return Object.freeze({
javascript: fileIdentity(input['javascript'], javascriptPath),
sourceMap: fileIdentity(input['sourceMap'], `${javascriptPath}.map`),
});
}
function rendererIdentity(
input: Record<string, unknown>
): XtreamBenchmarkBuildIdentity['renderer'] {
const files = exactArray(input['files']).map((value) =>
fileIdentity(value)
);
if (
files.length < 3 ||
files.length > 10_000 ||
!isStrictlySorted(files.map((file) => file.path))
) {
invalid();
}
const indexFiles = files.filter(
(file) => file.path === 'dist/apps/web/index.html'
);
const javascript = files.filter((file) => file.path.endsWith('.js'));
const sourceMaps = files.filter((file) => file.path.endsWith('.js.map'));
if (
indexFiles.length !== 1 ||
javascript.length === 0 ||
files.length !== 1 + javascript.length + sourceMaps.length ||
javascript.length !== sourceMaps.length ||
javascript.some(
(file) =>
!sourceMaps.some(
(sourceMap) => sourceMap.path === `${file.path}.map`
)
) ||
sourceMaps.some(
(sourceMap) =>
!javascript.some(
(file) => file.path === sourceMap.path.slice(0, -4)
)
)
) {
invalid();
}
const bytes = files.reduce((total, file) => total + file.bytes, 0);
if (
!isPositiveSafeInteger(input['bytes']) ||
input['bytes'] !== bytes ||
input['executableJavaScriptCount'] !== javascript.length ||
input['sourceMapCount'] !== sourceMaps.length ||
typeof input['aggregateSha256'] !== 'string' ||
!SHA256.test(input['aggregateSha256']) ||
input['aggregateSha256'] !== computeXtreamBuildFilesAggregate(files)
) {
invalid();
}
return Object.freeze({
aggregateSha256: input['aggregateSha256'],
bytes,
executableJavaScriptCount: javascript.length,
files: Object.freeze(files),
sourceMapCount: sourceMaps.length,
}) as XtreamBenchmarkBuildIdentity['renderer'];
}
function fileIdentity(
value: unknown,
expectedPath?: string
): XtreamBuildFileIdentity {
const input = exactRecord(value, FILE_KEYS);
const path = input['path'];
const validRendererPath =
path === 'dist/apps/web/index.html' ||
(typeof path === 'string' && RENDERER_FILE.test(path));
if (
!isPositiveSafeInteger(input['bytes']) ||
typeof path !== 'string' ||
(expectedPath === undefined
? !validRendererPath
: path !== expectedPath) ||
typeof input['sha256'] !== 'string' ||
!SHA256.test(input['sha256'])
) {
invalid();
}
return Object.freeze({
bytes: input['bytes'],
path,
sha256: input['sha256'],
}) as XtreamBuildFileIdentity;
}
function exactArray(value: unknown): unknown[] {
if (!Array.isArray(value)) invalid();
const length = Reflect.get(value, 'length') as unknown;
if (
!Number.isSafeInteger(length) ||
Number(length) < 0 ||
Number(length) > 10_000
) {
invalid();
}
const expectedKeys = new Set([
'length',
...Array.from({ length: Number(length) }, (_, index) => String(index)),
]);
const keys = Reflect.ownKeys(value);
if (
keys.length !== expectedKeys.size ||
keys.some((key) => typeof key !== 'string' || !expectedKeys.has(key))
) {
invalid();
}
return Array.from({ length: Number(length) }, (_, index) =>
Reflect.get(value, String(index))
);
}
function exactRecord(
value: unknown,
expectedKeys: readonly string[]
): Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
invalid();
}
const keys = Reflect.ownKeys(value);
if (
keys.length !== expectedKeys.length ||
keys.some(
(key) => typeof key !== 'string' || !expectedKeys.includes(key)
)
) {
invalid();
}
return Object.fromEntries(
expectedKeys.map((key) => [key, Reflect.get(value, key)])
);
}
function isStrictlySorted(values: readonly string[]): boolean {
return values.every(
(value, index) => index === 0 || (values[index - 1] ?? '') < value
);
}
function isPositiveSafeInteger(value: unknown): value is number {
return Number.isSafeInteger(value) && Number(value) > 0;
}
function invalid(): never {
throw new Error('invalid');
}
@@ -0,0 +1,246 @@
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import {
link,
mkdir,
mkdtemp,
rm,
symlink,
unlink,
writeFile,
} from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, it } from 'node:test';
import {
captureXtreamBuildIdentity,
computeXtreamBuildFilesAggregate,
} from './xtream-build-identity';
import { assertXtreamBenchmarkManifest } from './xtream-exact-schema';
import { BUILD_IDENTITY, MANIFEST } from './xtream-benchmark-report.fixtures';
const directories: string[] = [];
afterEach(async () => {
await Promise.all(
directories
.splice(0)
.map((directory) => rm(directory, { force: true, recursive: true }))
);
});
describe('Xtream benchmark build identity', () => {
it('hashes exact Electron pairs and sorted top-level renderer code', async () => {
const root = await buildFixture();
const identity = await captureXtreamBuildIdentity(root);
assert.deepEqual(identity.electron.main.javascript, {
bytes: 4,
path: 'dist/apps/electron-backend/main.js',
sha256: sha256('main'),
});
assert.deepEqual(identity.electron.preload.sourceMap, {
bytes: 11,
path: 'dist/apps/electron-backend/main.preload.js.map',
sha256: sha256('preload-map'),
});
assert.deepEqual(
identity.renderer.files.map((file) => file.path),
[
'dist/apps/web/chunk-A.js',
'dist/apps/web/chunk-A.js.map',
'dist/apps/web/index.html',
'dist/apps/web/main-A.js',
'dist/apps/web/main-A.js.map',
]
);
assert.equal(identity.renderer.executableJavaScriptCount, 2);
assert.equal(identity.renderer.sourceMapCount, 2);
assert.equal(
identity.renderer.bytes,
identity.renderer.files.reduce(
(total, file) => total + file.bytes,
0
)
);
assert.equal(
identity.renderer.aggregateSha256,
computeXtreamBuildFilesAggregate(identity.renderer.files)
);
assert.equal(
identity.renderer.files.some((file) =>
file.path.includes('app-config.js')
),
false
);
});
it('fails closed when an executable or its source map is missing', async () => {
const missingElectron = await buildFixture();
await unlink(
join(
missingElectron,
'dist/apps/electron-backend/workers/database.worker.js.map'
)
);
await assert.rejects(
captureXtreamBuildIdentity(missingElectron),
/invalid Xtream performance build/
);
const missingRendererMap = await buildFixture();
await unlink(join(missingRendererMap, 'dist/apps/web/chunk-A.js.map'));
await assert.rejects(
captureXtreamBuildIdentity(missingRendererMap),
/invalid Xtream performance build/
);
const orphanRendererMap = await buildFixture();
await writeFile(
join(orphanRendererMap, 'dist/apps/web/orphan.js.map'),
'orphan'
);
await assert.rejects(
captureXtreamBuildIdentity(orphanRendererMap),
/invalid Xtream performance build/
);
});
it('rejects symlinked and hard-linked build evidence', async () => {
const symlinkRoot = await buildFixture();
const mainPath = join(
symlinkRoot,
'dist/apps/electron-backend/main.js'
);
const external = join(symlinkRoot, 'external-main.js');
await writeFile(external, 'main');
await unlink(mainPath);
await symlink(external, mainPath);
await assert.rejects(
captureXtreamBuildIdentity(symlinkRoot),
/invalid Xtream performance build/
);
const hardLinkRoot = await buildFixture();
await link(
join(hardLinkRoot, 'dist/apps/web/main-A.js'),
join(hardLinkRoot, 'main-alias.js')
);
await assert.rejects(
captureXtreamBuildIdentity(hardLinkRoot),
/invalid Xtream performance build/
);
});
it('produces a different aggregate for path, byte, or hash drift', () => {
const files = [
{ bytes: 1, path: 'a.js', sha256: 'a'.repeat(64) },
{ bytes: 2, path: 'a.js.map', sha256: 'b'.repeat(64) },
];
const baseline = computeXtreamBuildFilesAggregate(files);
for (const changed of [
[{ ...files[0], path: 'b.js' }, files[1]],
[{ ...files[0], bytes: 2 }, files[1]],
[{ ...files[0], sha256: 'c'.repeat(64) }, files[1]],
]) {
assert.notEqual(
computeXtreamBuildFilesAggregate(changed),
baseline
);
}
});
it('binds exact build descriptors into the persisted benchmark manifest', () => {
assert.doesNotThrow(() => assertXtreamBenchmarkManifest(MANIFEST));
const files = BUILD_IDENTITY.renderer.files;
const invalidBuilds = [
{
...BUILD_IDENTITY,
renderer: {
...BUILD_IDENTITY.renderer,
aggregateSha256: 'f'.repeat(64),
},
},
{
...BUILD_IDENTITY,
renderer: {
...BUILD_IDENTITY.renderer,
bytes: BUILD_IDENTITY.renderer.bytes + 1,
},
},
{
...BUILD_IDENTITY,
renderer: {
...BUILD_IDENTITY.renderer,
files: [...files].reverse(),
},
},
{
...BUILD_IDENTITY,
renderer: {
...BUILD_IDENTITY.renderer,
files: files.slice(0, -1),
},
},
{
...BUILD_IDENTITY,
electron: {
...BUILD_IDENTITY.electron,
main: {
...BUILD_IDENTITY.electron.main,
javascript: {
...BUILD_IDENTITY.electron.main.javascript,
path: 'dist/apps/electron-backend/other.js',
},
},
},
},
];
for (const build of invalidBuilds) {
assert.throws(
() => assertXtreamBenchmarkManifest({ ...MANIFEST, build }),
/invalid Xtream benchmark manifest/
);
}
});
});
async function buildFixture(): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'iptvnator-xtream-build-'));
directories.push(root);
const backend = join(root, 'dist/apps/electron-backend');
const workers = join(backend, 'workers');
const renderer = join(root, 'dist/apps/web');
await Promise.all([
mkdir(workers, { recursive: true }),
mkdir(join(renderer, 'assets'), { recursive: true }),
]);
await Promise.all([
writeFile(join(backend, 'main.js'), 'main'),
writeFile(join(backend, 'main.js.map'), 'main-map'),
writeFile(join(backend, 'main.preload.js'), 'preload'),
writeFile(join(backend, 'main.preload.js.map'), 'preload-map'),
writeFile(join(workers, 'database.worker.js'), 'database'),
writeFile(join(workers, 'database.worker.js.map'), 'database-map'),
writeFile(join(workers, 'playlist-refresh.worker.js'), 'refresh'),
writeFile(
join(workers, 'playlist-refresh.worker.js.map'),
'refresh-map'
),
writeFile(join(renderer, 'index.html'), '<app-root/>'),
writeFile(join(renderer, 'main-A.js'), 'renderer-main'),
writeFile(join(renderer, 'main-A.js.map'), 'renderer-main-map'),
writeFile(join(renderer, 'chunk-A.js'), 'renderer-chunk'),
writeFile(join(renderer, 'chunk-A.js.map'), 'renderer-chunk-map'),
writeFile(join(renderer, 'styles.css'), 'ignored'),
writeFile(join(renderer, 'assets/app-config.js'), 'ignored'),
]);
return root;
}
function sha256(value: string): string {
return createHash('sha256').update(value).digest('hex');
}
@@ -0,0 +1,202 @@
import { createHash } from 'node:crypto';
import { readdir } from 'node:fs/promises';
import { isAbsolute, join } from 'node:path';
import { readXtreamSafeRegularFile } from './xtream-safe-file-read';
export interface XtreamBuildFileIdentity {
readonly bytes: number;
readonly path: string;
readonly sha256: string;
}
export interface XtreamBuildPairIdentity {
readonly javascript: XtreamBuildFileIdentity;
readonly sourceMap: XtreamBuildFileIdentity;
}
export interface XtreamBenchmarkBuildIdentity {
readonly electron: {
readonly databaseWorker: XtreamBuildPairIdentity;
readonly main: XtreamBuildPairIdentity;
readonly playlistRefreshWorker: XtreamBuildPairIdentity;
readonly preload: XtreamBuildPairIdentity;
};
readonly renderer: {
readonly aggregateSha256: string;
readonly bytes: number;
readonly executableJavaScriptCount: number;
readonly files: readonly XtreamBuildFileIdentity[];
readonly sourceMapCount: number;
};
}
const BACKEND_ROOT = 'dist/apps/electron-backend';
const RENDERER_ROOT = 'dist/apps/web';
const ELECTRON_PATHS = {
databaseWorker: `${BACKEND_ROOT}/workers/database.worker.js`,
main: `${BACKEND_ROOT}/main.js`,
playlistRefreshWorker: `${BACKEND_ROOT}/workers/playlist-refresh.worker.js`,
preload: `${BACKEND_ROOT}/main.preload.js`,
} as const;
export async function captureXtreamBuildIdentity(
workspaceRoot: string
): Promise<XtreamBenchmarkBuildIdentity> {
try {
if (!isAbsolute(workspaceRoot)) invalid();
const [databaseWorker, main, playlistRefreshWorker, preload, renderer] =
await Promise.all([
readPair(workspaceRoot, ELECTRON_PATHS.databaseWorker),
readPair(workspaceRoot, ELECTRON_PATHS.main),
readPair(workspaceRoot, ELECTRON_PATHS.playlistRefreshWorker),
readPair(workspaceRoot, ELECTRON_PATHS.preload),
readRenderer(workspaceRoot),
]);
return Object.freeze({
electron: Object.freeze({
databaseWorker,
main,
playlistRefreshWorker,
preload,
}),
renderer,
});
} catch {
throw new Error('invalid Xtream performance build');
}
}
export function computeXtreamBuildFilesAggregate(
files: readonly XtreamBuildFileIdentity[]
): string {
try {
const length = Reflect.get(files, 'length') as unknown;
if (
!Number.isSafeInteger(length) ||
Number(length) <= 0 ||
Number(length) > 10_000
) {
invalid();
}
const snapshot = Array.from({ length: Number(length) }, (_, index) => {
const value = Reflect.get(files, String(index)) as
XtreamBuildFileIdentity | undefined;
if (
!value ||
!Number.isSafeInteger(value.bytes) ||
value.bytes < 0 ||
typeof value.path !== 'string' ||
value.path.length === 0 ||
typeof value.sha256 !== 'string' ||
!/^[a-f0-9]{64}$/u.test(value.sha256)
) {
invalid();
}
return [value.path, value.bytes, value.sha256] as const;
});
return sha256(Buffer.from(JSON.stringify(snapshot), 'utf8'));
} catch {
throw new Error('invalid Xtream build file identity');
}
}
async function readPair(
workspaceRoot: string,
javascriptPath: string
): Promise<XtreamBuildPairIdentity> {
const [javascript, sourceMap] = await Promise.all([
readIdentity(workspaceRoot, javascriptPath),
readIdentity(workspaceRoot, `${javascriptPath}.map`),
]);
return Object.freeze({ javascript, sourceMap });
}
async function readRenderer(
workspaceRoot: string
): Promise<XtreamBenchmarkBuildIdentity['renderer']> {
const rendererDirectory = join(workspaceRoot, RENDERER_ROOT);
const namesBefore = await readRendererNames(rendererDirectory);
assertRendererPairing(namesBefore);
const relativePaths = namesBefore.map((name) => `${RENDERER_ROOT}/${name}`);
const files = await Promise.all(
relativePaths.map((path) => readIdentity(workspaceRoot, path))
);
const namesAfter = await readRendererNames(rendererDirectory);
if (
namesAfter.length !== namesBefore.length ||
namesAfter.some((name, index) => name !== namesBefore[index])
) {
invalid();
}
const executableJavaScriptCount = namesBefore.filter((name) =>
name.endsWith('.js')
).length;
const sourceMapCount = namesBefore.filter((name) =>
name.endsWith('.js.map')
).length;
const bytes = files.reduce((total, file) => total + file.bytes, 0);
if (!Number.isSafeInteger(bytes) || bytes <= 0) invalid();
const frozenFiles = Object.freeze(files);
return Object.freeze({
aggregateSha256: computeXtreamBuildFilesAggregate(frozenFiles),
bytes,
executableJavaScriptCount,
files: frozenFiles,
sourceMapCount,
});
}
async function readRendererNames(directory: string): Promise<string[]> {
const entries = await readdir(directory, { withFileTypes: true });
const relevant = entries.filter(
(entry) =>
entry.name === 'index.html' ||
entry.name.endsWith('.js') ||
entry.name.endsWith('.js.map')
);
if (
relevant.some((entry) => !entry.isFile()) ||
relevant.filter((entry) => entry.name === 'index.html').length !== 1
) {
invalid();
}
return relevant.map((entry) => entry.name).sort();
}
function assertRendererPairing(names: readonly string[]): void {
const javascript = names.filter((name) => name.endsWith('.js'));
const sourceMaps = names.filter((name) => name.endsWith('.js.map'));
const javascriptSet = new Set(javascript);
const sourceMapSet = new Set(sourceMaps);
if (
javascript.length === 0 ||
javascript.length !== sourceMaps.length ||
javascript.some((name) => !sourceMapSet.has(`${name}.map`)) ||
sourceMaps.some((name) => !javascriptSet.has(name.slice(0, -4)))
) {
invalid();
}
}
async function readIdentity(
workspaceRoot: string,
relativePath: string
): Promise<XtreamBuildFileIdentity> {
const { content } = await readXtreamSafeRegularFile(
join(workspaceRoot, relativePath)
);
return Object.freeze({
bytes: content.byteLength,
path: relativePath,
sha256: sha256(content),
});
}
function sha256(value: Uint8Array): string {
return createHash('sha256').update(value).digest('hex');
}
function invalid(): never {
throw new Error('invalid');
}
@@ -0,0 +1,258 @@
import type { DatabaseWorkerCancelTimelineRecord } from './database-worker-cancel-capture';
import { snapshotXtreamCancellationEvidence } from './xtream-cancellation-evidence-snapshot';
import {
isFiniteNonNegative,
isPositiveSafeInteger,
} from './xtream-exact-schema';
import type {
XtreamAttributionPhaseEvent,
XtreamIpcAttributionSpan,
} from './xtream-phase-attribution';
import {
hasDistinctMonotonicXtreamIpcProducerIdentities,
XTREAM_IPC_PRODUCER_NAMESPACE,
xtreamIpcProducerNamespaceForWorkerOperation,
type XtreamIpcProducerIdentity,
} from './xtream-ipc-producer-identity';
import { XTREAM_ATTRIBUTION_PHASE as PHASE } from './xtream-phase-inventory';
import type {
XtreamCancellationMetrics,
XtreamRawIterationResult,
} from './xtream-summary-contract';
type WorkerRequest =
XtreamRawIterationResult['databaseWorker']['requests']['evidence'][number];
const RECORD_TYPES = [
'db-cancel-dispatched',
'db-cancel-received',
'db-cancel-terminal-received',
] as const;
export function assertXtreamCancellationEvidenceBinding(
raw: XtreamRawIterationResult,
events: readonly XtreamAttributionPhaseEvent[],
ipcSpans: readonly XtreamIpcAttributionSpan[],
requests: readonly WorkerRequest[]
): void {
const failed = requests.filter(
({ operation, success }) => operation === 'DB_SAVE_CONTENT' && !success
);
if (raw.scenarioId !== 'xtream-cancel-import') {
if (failed.length !== 0 || raw.cancellationEvidence !== null) invalid();
return;
}
const request = failed[0];
if (
failed.length !== 1 ||
!request ||
typeof request.requestId !== 'string' ||
typeof request.operationId !== 'string' ||
raw.cancellation.failedDatabaseRequestId !== request.requestId ||
raw.cancellation.failedOperationId !== request.operationId
) {
invalid();
}
const timeline = cancellationTimeline(raw, request);
const databasePreload = ipcPair(ipcSpans, 'dbCancelOperation');
assertCancellationProducerIdentity(timeline, databasePreload[0], requests);
assertCancellationTimeline(
raw.cancellation,
request,
timeline,
phaseSpan(
events,
PHASE.SQLITE_CONTENT_WRITE_TRANSACTIONS,
request.requestId
),
phaseSpan(events, PHASE.CANCEL_SESSION),
phaseSpan(events, PHASE.STORE_IMPORT_TERMINAL),
ipcPair(ipcSpans, 'xtreamCancelSession'),
databasePreload,
raw.phaseCapture.uiPaintEpochMs
);
}
interface Span {
readonly endEpochMs: number;
readonly startEpochMs: number;
}
function assertCancellationTimeline(
value: XtreamCancellationMetrics,
request: WorkerRequest,
timeline: readonly DatabaseWorkerCancelTimelineRecord[],
write: Span,
networkCancel: Span,
terminal: Span,
networkPreload: readonly [
XtreamIpcAttributionSpan,
XtreamIpcAttributionSpan,
],
databasePreload: readonly [
XtreamIpcAttributionSpan,
XtreamIpcAttributionSpan,
],
uiPaintEpochMs: number
): void {
const [dispatchRecord, receiptRecord, workerTerminalRecord] = timeline;
if (!dispatchRecord || !receiptRecord || !workerTerminalRecord) invalid();
const click = epoch(value.clickEpochMs);
const preloadStart = epoch(value.preloadStartEpochMs);
const preloadReturn = epoch(value.preloadReturnEpochMs);
const dispatch = epoch(value.databaseDispatchEpochMs);
const receipt = epoch(value.workerReceiptEpochMs);
const authoritative = epoch(value.authoritativeTerminalEpochMs);
const painted = epoch(value.paintedEpochMs);
if (
request.requestReceivedEpochMs === null ||
request.workEndedEpochMs === null ||
click < write.startEpochMs ||
click < request.requestReceivedEpochMs ||
click > write.endEpochMs ||
networkPreload[0].startEpochMs < click ||
networkPreload[0].endEpochMs > networkCancel.startEpochMs ||
networkCancel.endEpochMs > networkPreload[1].startEpochMs ||
networkPreload[1].startEpochMs > databasePreload[0].startEpochMs ||
preloadStart !== databasePreload[0].startEpochMs ||
dispatchRecord.ipcCallId !== databasePreload[0].ipcCallId ||
dispatchRecord.sourceEpochMs !== databasePreload[0].sourceEpochMs ||
databasePreload[0].endEpochMs > dispatch ||
dispatch !== dispatchRecord.epochMs ||
dispatch < request.requestReceivedEpochMs ||
dispatch > write.endEpochMs ||
databasePreload[1].startEpochMs < dispatch ||
preloadReturn !== databasePreload[1].endEpochMs ||
preloadReturn < dispatch ||
receipt !== receiptRecord.epochMs ||
receipt < dispatch ||
receipt > request.workEndedEpochMs ||
receipt > authoritative ||
workerTerminalRecord.epochMs < receipt ||
workerTerminalRecord.epochMs > authoritative ||
authoritative !== request.responseEpochMs ||
terminal.startEpochMs < authoritative ||
terminal.endEpochMs > painted ||
painted !== uiPaintEpochMs
) {
invalid();
}
}
function cancellationTimeline(
raw: XtreamRawIterationResult,
request: WorkerRequest
): readonly DatabaseWorkerCancelTimelineRecord[] {
const evidence = snapshotXtreamCancellationEvidence(
raw.cancellationEvidence
);
if (evidence === null) invalid();
const timeline = evidence.timeline;
if (timeline.length !== RECORD_TYPES.length) invalid();
const first = timeline[0];
if (!first) invalid();
for (let index = 0; index < timeline.length; index += 1) {
const record = timeline[index];
if (
!record ||
record.type !== RECORD_TYPES[index] ||
!isFiniteNonNegative(record.epochMs) ||
!isFiniteNonNegative(record.sourceEpochMs) ||
!isPositiveSafeInteger(record.ipcCallId) ||
typeof record.operationId !== 'string' ||
record.operationId.length === 0 ||
typeof record.requestId !== 'string' ||
record.requestId.length === 0 ||
record.ipcCallId !== first.ipcCallId ||
record.operationId !== first.operationId ||
record.requestId !== first.requestId ||
record.sourceEpochMs !== first.sourceEpochMs
) {
invalid();
}
}
if (
first.requestId !== request.requestId ||
first.operationId !== request.operationId
) {
invalid();
}
return timeline;
}
function assertCancellationProducerIdentity(
timeline: readonly DatabaseWorkerCancelTimelineRecord[],
databasePreload: XtreamIpcAttributionSpan,
requests: readonly WorkerRequest[]
): void {
const dispatch = timeline[0];
if (
!dispatch ||
dispatch.ipcCallId !== databasePreload.ipcCallId ||
dispatch.sourceEpochMs !== databasePreload.sourceEpochMs
) {
invalid();
}
const identities: XtreamIpcProducerIdentity[] = [];
for (const { ipcCallId, operation, sourceEpochMs } of requests) {
if (ipcCallId === null && sourceEpochMs === null) continue;
const namespace = xtreamIpcProducerNamespaceForWorkerOperation(
typeof operation === 'string' ? operation : ''
);
if (
namespace === null ||
!isPositiveSafeInteger(ipcCallId) ||
!isFiniteNonNegative(sourceEpochMs)
) {
invalid();
}
if (namespace === XTREAM_IPC_PRODUCER_NAMESPACE.XTREAM_PRELOAD) {
identities.push({ ipcCallId, namespace, sourceEpochMs });
}
}
identities.push({
ipcCallId: databasePreload.ipcCallId,
namespace: XTREAM_IPC_PRODUCER_NAMESPACE.XTREAM_PRELOAD,
sourceEpochMs: databasePreload.sourceEpochMs,
});
if (!hasDistinctMonotonicXtreamIpcProducerIdentities(identities)) {
invalid();
}
}
function phaseSpan(
events: readonly XtreamAttributionPhaseEvent[],
phase: string,
correlationId?: string
): Span {
const selected = events.filter(
(event) =>
event.phase === phase &&
(correlationId === undefined ||
event.correlationId === correlationId)
);
const start = selected.find(({ boundary }) => boundary === 'start');
const end = selected.find(({ boundary }) => boundary === 'end');
if (selected.length !== 2 || !start || !end) invalid();
return { endEpochMs: end.epochMs, startEpochMs: start.epochMs };
}
function ipcPair(
spans: readonly XtreamIpcAttributionSpan[],
method: string
): readonly [XtreamIpcAttributionSpan, XtreamIpcAttributionSpan] {
const selected = spans.filter((span) => span.method === method);
const request = selected.find(({ boundary }) => boundary === 'request');
const response = selected.find(({ boundary }) => boundary === 'response');
if (selected.length !== 2 || !request || !response) invalid();
return [request, response];
}
function epoch(value: number | null): number {
if (!isFiniteNonNegative(value)) invalid();
return value;
}
function invalid(): never {
throw new Error('invalid Xtream persisted cancellation evidence');
}
@@ -0,0 +1,115 @@
import type { DatabaseWorkerCancelTimelineRecord } from './database-worker-cancel-capture';
import type { XtreamCancellationEvidence } from './xtream-summary-contract';
const EVIDENCE_KEYS = ['timeline'] as const;
const RECORD_KEYS = [
'epochMs',
'ipcCallId',
'operationId',
'requestId',
'sourceEpochMs',
'type',
] as const;
const MAX_TIMELINE_LENGTH = 16;
export function snapshotXtreamCancellationEvidence(
value: unknown
): XtreamCancellationEvidence | null {
try {
const evidence = snapshotExactRecord(value, EVIDENCE_KEYS);
const timeline = snapshotExactArray(
evidence['timeline'],
snapshotTimelineRecord
);
return Object.freeze({ timeline });
} catch {
return null;
}
}
function snapshotTimelineRecord(
value: unknown
): DatabaseWorkerCancelTimelineRecord {
return snapshotExactRecord(
value,
RECORD_KEYS
) as unknown as DatabaseWorkerCancelTimelineRecord;
}
function snapshotExactArray<T>(
value: unknown,
snapshotEntry: (entry: unknown) => T
): readonly T[] {
if (!Array.isArray(value)) invalid();
const length = dataDescriptorValue(
Reflect.getOwnPropertyDescriptor(value, 'length')
);
if (
!Number.isSafeInteger(length) ||
Number(length) < 0 ||
Number(length) > MAX_TIMELINE_LENGTH
) {
invalid();
}
const expectedKeys = new Set<PropertyKey>(['length']);
const snapshot: T[] = [];
for (let index = 0; index < Number(length); index += 1) {
const key = String(index);
expectedKeys.add(key);
snapshot[index] = snapshotEntry(
dataDescriptorValue(Reflect.getOwnPropertyDescriptor(value, key))
);
}
const ownKeys = Reflect.ownKeys(value);
if (
ownKeys.length !== expectedKeys.size ||
ownKeys.some((key) => !expectedKeys.has(key))
) {
invalid();
}
return Object.freeze(snapshot);
}
function snapshotExactRecord(
value: unknown,
keys: readonly string[]
): Readonly<Record<string, unknown>> {
if (
typeof value !== 'object' ||
value === null ||
Array.isArray(value) ||
![Object.prototype, null].includes(Reflect.getPrototypeOf(value))
) {
invalid();
}
const ownKeys = Reflect.ownKeys(value);
if (
ownKeys.length !== keys.length ||
ownKeys.some((key) => typeof key !== 'string' || !keys.includes(key))
) {
invalid();
}
const snapshot: Record<string, unknown> = {};
for (const key of keys) {
snapshot[key] = dataDescriptorValue(
Reflect.getOwnPropertyDescriptor(value, key)
);
}
return Object.freeze(snapshot);
}
function dataDescriptorValue(
descriptor: PropertyDescriptor | undefined
): unknown {
if (
!descriptor ||
!Object.prototype.hasOwnProperty.call(descriptor, 'value')
) {
invalid();
}
return descriptor.value;
}
function invalid(): never {
throw new Error('invalid Xtream cancellation evidence snapshot');
}
Loaded 100 of 385 files, more files were not shown because too many files have changed in this diff. Show more