test(performance): address benchmark review feedback

This commit is contained in:
4gray committed 2026-07-27 10:12:42 +02:00
1 parent 77e8fd92e3
commit 0ce8901ede
9 files changed
+182 -142

No files matched your search

@@ -114,6 +114,7 @@ test('captures exact successful preload completion markers for DB return-clone a
})
);
assert.equal(api.successMarkerCount(), 2);
assert.deepEqual(api.takeSuccessMarkers(), [
{
ipcCallId: 7,
@@ -130,6 +131,7 @@ test('captures exact successful preload completion markers for DB return-clone a
sourceEpochMs: 175.5,
},
]);
assert.equal(api.successMarkerCount(), 0);
assert.deepEqual(api.takeSuccessMarkers(), []);
});
@@ -153,6 +155,7 @@ test('fails closed for malformed or invalid successful preload markers and reset
})
);
}
assert.equal(api.successMarkerCount(), 0);
assert.deepEqual(api.takeSuccessMarkers(), []);
ipcMain.emit(
@@ -164,6 +167,7 @@ test('fails closed for malformed or invalid successful preload markers and reset
);
api.stop();
api.start();
assert.equal(api.successMarkerCount(), 0);
assert.deepEqual(api.takeSuccessMarkers(), []);
});
@@ -22,6 +22,7 @@ export interface DatabaseRequestIdentityCaptureApi {
matchDatabaseRequest(message: unknown): DatabaseRequestIdentity;
start(): void;
stop(): void;
successMarkerCount(): number;
takeSuccessMarkers(): readonly PreloadDatabaseSuccessMarker[];
}
@@ -333,6 +334,7 @@ export function installDatabaseRequestIdentityCaptureInMain(
quarantinedKeys = [];
successMarkers = [];
},
successMarkerCount: () => successMarkers.length,
takeSuccessMarkers: () => {
const captured = Object.freeze([...successMarkers]);
successMarkers = [];
@@ -158,6 +158,7 @@ describe('initial M3U import benchmark lifecycle', () => {
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', () => {
@@ -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;
};
}
@@ -1,4 +1,6 @@
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';
@@ -16,6 +18,16 @@ import {
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,
@@ -1,8 +1,6 @@
/* eslint-disable max-lines -- The formal summary keeps its complete measured schema and aggregation contract together. */
import type { M3uImportRendererCaptureMetrics } from './m3u-import-renderer-capture';
import type { M3uImportScenarioId } from './m3u-import-benchmark-contract';
import type {
DatabaseWorkerPostGcValidity,
MainCaptureMetrics,
NumericDistribution,
PerformanceIterationKind,
@@ -16,138 +14,24 @@ import {
import {
deriveM3uImportPhaseMetrics,
M3U_IMPORT_INDEX_COMMIT_UNAVAILABLE_REASON,
type M3uImportPhaseMetrics,
} from './m3u-import-report';
import { summarizeNumbers } from './performance-statistics';
import {
assessDatabaseWorkerPeakMemoryValidity,
type DatabaseWorkerPeakMemoryValidity,
} from './database-worker-peak-memory-validity';
import { assessDatabaseWorkerPeakMemoryValidity } from './database-worker-peak-memory-validity';
import { assessDatabaseWorkerPostGcValidity } from './database-worker-post-gc-validity';
import {
assessDatabaseWorkerRequestMetricsValidity,
type DatabaseWorkerRequestMetricsValidity,
} from './database-worker-request-metrics-validity';
import {
assessRendererRssValidity,
type RendererRssValidity,
} from './renderer-rss-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 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;
};
}
export type {
M3uImportBenchmarkManifest,
M3uImportBenchmarkSummary,
M3uImportIterationResult,
M3uImportMeasuredSummary,
} from './m3u-import-summary-contract';
export function createM3uImportIterationResult(input: {
readonly channelCount: number;
@@ -268,7 +268,8 @@ async function waitForDatabaseSettlement(
status.databaseRequests === 2 &&
status.databaseUpsertsCompleted === 1 &&
status.databaseGetsCompleted === 1 &&
status.databasePending === 0
status.databasePending === 0 &&
status.preloadSuccessMarkers === 2
);
},
{ timeout: 120_000 }
@@ -70,6 +70,7 @@ export interface MainCaptureStatus {
readonly playlistResponsesFailed: number;
readonly playlistResponses: number;
readonly playlistResponsesSucceeded: number;
readonly preloadSuccessMarkers: number;
}
export interface MainCaptureRolloverResult {
@@ -1188,6 +1189,8 @@ export async function installMainCapture(
entry['type'] === 'playlist-response' &&
entry['success'] === true
).length,
preloadSuccessMarkers:
databaseRequestIdentityCapture.successMarkerCount(),
}),
start: async (options: MainCaptureStartOptions): Promise<void> => {
databaseWorkerPostGcCutoffApi.beginCapture();
+13 -11
View File
@@ -128,17 +128,19 @@ therefore `N/A` with
Instrumentation is development-only, opt-in, fail-neutral, and count-only. It
must not scan or log playlist payloads to generate metadata. Renderer
long-task, frame-gap, and heartbeat samples are clipped to the measured
operation boundary. Formal comparison fails closed after writing raw results
if exact-window renderer RSS, database-worker peak heap/external samples, or
the database worker's explicit post-GC heap is missing or incoherent. Both
initial-import database requests in every measured run must also contain
coherent event-loop delay, event-loop utilization, and thread-CPU metrics;
the validity record exposes the exact expected and valid request counts rather
than silently dropping nullable samples. An iteration failure idempotently
stops renderer timers, closes trace listeners/output, and starts best-effort
probe/session teardown without waiting on a wedged renderer before Electron is
closed. A partial capture-start failure performs the same rollback before it
escapes to the benchmark lifecycle.
operation boundary. Main capture stops only after the upsert and route-reload
GET responses plus both asynchronous preload success markers have arrived, so
return-clone attribution cannot race capture shutdown. Formal comparison fails
closed after writing raw results if exact-window renderer RSS, database-worker
peak heap/external samples, or the database worker's explicit post-GC heap is
missing or incoherent. Both initial-import database requests in every measured
run must also contain coherent event-loop delay, event-loop utilization, and
thread-CPU metrics; the validity record exposes the exact expected and valid
request counts rather than silently dropping nullable samples. An iteration
failure idempotently stops renderer timers, closes trace listeners/output, and
starts best-effort probe/session teardown without waiting on a wedged renderer
before Electron is closed. A partial capture-start failure performs the same
rollback before it escapes to the benchmark lifecycle.
Diagnostic artifacts require separate renderer, main, and database-worker CPU
profiles plus renderer/main/database-worker heap snapshots and a Chromium
trace; raw profiles remain ignored.