From 0ce8901ede5ae468e7afc2f3a7fc59f55d1f4cea Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 27 Jul 2026 10:12:42 +0200 Subject: [PATCH] test(performance): address benchmark review feedback --- .../database-request-identity-capture.spec.ts | 4 + .../database-request-identity-capture.ts | 2 + .../m3u-import-benchmark-lifecycle.spec.ts | 1 + .../m3u-import-summary-contract.ts | 131 ++++++++++++++++ .../performance/m3u-import-summary.spec.ts | 12 ++ .../src/performance/m3u-import-summary.ts | 144 ++---------------- .../src/performance/m3u-import.benchmark.ts | 3 +- .../performance/m3u-refresh-main-capture.ts | 3 + docs/architecture/m3u-playlist-module.md | 24 +-- 9 files changed, 182 insertions(+), 142 deletions(-) create mode 100644 apps/electron-backend-e2e/src/performance/m3u-import-summary-contract.ts diff --git a/apps/electron-backend-e2e/src/performance/database-request-identity-capture.spec.ts b/apps/electron-backend-e2e/src/performance/database-request-identity-capture.spec.ts index 446629d3c..4c3eac434 100644 --- a/apps/electron-backend-e2e/src/performance/database-request-identity-capture.spec.ts +++ b/apps/electron-backend-e2e/src/performance/database-request-identity-capture.spec.ts @@ -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(), []); }); diff --git a/apps/electron-backend-e2e/src/performance/database-request-identity-capture.ts b/apps/electron-backend-e2e/src/performance/database-request-identity-capture.ts index 0770384f6..2de76e7e3 100644 --- a/apps/electron-backend-e2e/src/performance/database-request-identity-capture.ts +++ b/apps/electron-backend-e2e/src/performance/database-request-identity-capture.ts @@ -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 = []; diff --git a/apps/electron-backend-e2e/src/performance/m3u-import-benchmark-lifecycle.spec.ts b/apps/electron-backend-e2e/src/performance/m3u-import-benchmark-lifecycle.spec.ts index 3abcdca03..f1dcd053a 100644 --- a/apps/electron-backend-e2e/src/performance/m3u-import-benchmark-lifecycle.spec.ts +++ b/apps/electron-backend-e2e/src/performance/m3u-import-benchmark-lifecycle.spec.ts @@ -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', () => { diff --git a/apps/electron-backend-e2e/src/performance/m3u-import-summary-contract.ts b/apps/electron-backend-e2e/src/performance/m3u-import-summary-contract.ts new file mode 100644 index 000000000..6f843f8b2 --- /dev/null +++ b/apps/electron-backend-e2e/src/performance/m3u-import-summary-contract.ts @@ -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; + }; +} diff --git a/apps/electron-backend-e2e/src/performance/m3u-import-summary.spec.ts b/apps/electron-backend-e2e/src/performance/m3u-import-summary.spec.ts index 56a634d10..558b33365 100644 --- a/apps/electron-backend-e2e/src/performance/m3u-import-summary.spec.ts +++ b/apps/electron-backend-e2e/src/performance/m3u-import-summary.spec.ts @@ -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, diff --git a/apps/electron-backend-e2e/src/performance/m3u-import-summary.ts b/apps/electron-backend-e2e/src/performance/m3u-import-summary.ts index ec01781c9..5a81e0a87 100644 --- a/apps/electron-backend-e2e/src/performance/m3u-import-summary.ts +++ b/apps/electron-backend-e2e/src/performance/m3u-import-summary.ts @@ -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; diff --git a/apps/electron-backend-e2e/src/performance/m3u-import.benchmark.ts b/apps/electron-backend-e2e/src/performance/m3u-import.benchmark.ts index 4f0205c30..dab027bc9 100644 --- a/apps/electron-backend-e2e/src/performance/m3u-import.benchmark.ts +++ b/apps/electron-backend-e2e/src/performance/m3u-import.benchmark.ts @@ -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 } diff --git a/apps/electron-backend-e2e/src/performance/m3u-refresh-main-capture.ts b/apps/electron-backend-e2e/src/performance/m3u-refresh-main-capture.ts index 7fd751390..2c4536f8e 100644 --- a/apps/electron-backend-e2e/src/performance/m3u-refresh-main-capture.ts +++ b/apps/electron-backend-e2e/src/performance/m3u-refresh-main-capture.ts @@ -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 => { databaseWorkerPostGcCutoffApi.beginCapture(); diff --git a/docs/architecture/m3u-playlist-module.md b/docs/architecture/m3u-playlist-module.md index ecf7463f7..6c177858f 100644 --- a/docs/architecture/m3u-playlist-module.md +++ b/docs/architecture/m3u-playlist-module.md @@ -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.