fix(perf): reject incomplete renderer RSS samples

This commit is contained in:
4gray committed 2026-07-27 02:27:57 +02:00
1 parent 554eecfee0
commit 2fda6cea07
5 files changed
+136 -9

No files matched your search

@@ -1,4 +1,7 @@
import type { RendererProcessRssCapture } from './renderer-process-rss-capture';
import type {
RendererProcessRssCapture,
RendererProcessRssUnavailableReason,
} from './renderer-process-rss-capture';
export const PERFORMANCE_ITERATION_KIND = {
DIAGNOSTIC: 'diagnostic',
@@ -254,6 +257,22 @@ 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[];
@@ -309,5 +328,6 @@ export interface CancellationBenchmarkSummary {
};
readonly validity: {
readonly databaseWorkerPostGc: DatabaseWorkerPostGcValidity;
readonly rendererRss: RendererRssValidity;
};
}
@@ -9,6 +9,7 @@ import type {
NumericDistribution,
PerformanceWorkerKind,
RendererCaptureMetrics,
RendererRssValidity,
WorkerRequestPerformanceMetrics,
} from './m3u-refresh-cancellation-contract';
import {
@@ -310,10 +311,55 @@ export function createCancellationBenchmarkSummary(
validity: Object.freeze({
databaseWorkerPostGc:
assessDatabaseWorkerPostGcValidity(iterations),
rendererRss: assessRendererRssValidity(iterations),
}),
});
}
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
@@ -157,6 +157,13 @@ export async function runM3uRefreshCancellationBenchmark(): Promise<void> {
`Cancellation effect was not observed in every measured run: ${summary.cancellationEffectRate}`
);
}
if (!summary.validity.rendererRss.validForBenchmark) {
throw new Error(
`Renderer RSS capture is invalid: ${JSON.stringify(
summary.validity.rendererRss.invalidMeasuredRuns
)}`
);
}
if (!summary.validity.databaseWorkerPostGc.validForBenchmark) {
throw new Error(
`Database worker post-GC capture is invalid: ${JSON.stringify(
@@ -1,5 +1,6 @@
/* eslint-disable playwright/expect-expect -- These are Node assertion-based performance contract tests. */
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import {
@@ -13,7 +14,8 @@ import type { RendererProcessRssCapture } from './renderer-process-rss-capture';
function measuredIteration(
rss: RendererProcessRssCapture,
responsiveEvents: number,
unresponsiveEvents: number
unresponsiveEvents: number,
runId: string
): CancellationIterationResult {
return {
cancellationEffectObserved: true,
@@ -51,7 +53,7 @@ function measuredIteration(
longTasksMs: [],
},
},
runId: 'measured',
runId,
} as unknown as CancellationIterationResult;
}
@@ -71,19 +73,24 @@ test('summary uses only exact target-window RSS and scoped responsiveness events
validSampleCount: 3,
},
1,
2
2,
'run-01'
),
measuredIteration(
{
identity: null,
identity: {
creationTime: 1_721_234_567_890,
pid: 43,
},
missingSampleCount: 1,
peakRssBytes: null,
unavailableReason:
'renderer-process-metric-missing-at-start',
validSampleCount: 0,
'renderer-process-metric-missing-during-capture',
validSampleCount: 2,
},
3,
4
4,
'run-02'
),
]
);
@@ -99,4 +106,48 @@ test('summary uses only exact target-window RSS and scoped responsiveness events
});
assert.equal(summary.measured.responsiveEvents, 4);
assert.equal(summary.measured.unresponsiveEvents, 6);
assert.deepEqual(
(
summary as unknown as {
readonly validity: {
readonly rendererRss: unknown;
};
}
).validity.rendererRss,
{
invalidMeasuredRuns: [
{
missingSampleCount: 1,
reason: 'renderer-process-metric-missing-during-capture',
runId: 'run-02',
validSampleCount: 2,
},
],
measuredRunCount: 2,
validForBenchmark: false,
validForComparison: false,
validMeasuredRunCount: 1,
}
);
});
test('benchmark preserves raw artifacts before rejecting incomplete renderer RSS', () => {
const source = readFileSync(
new URL('./m3u-refresh-cancellation.benchmark.ts', import.meta.url),
'utf8'
);
const summaryWrite = source.indexOf(
"writeJson(join(config.outputDirectory, 'summary.json'), summary)"
);
const validityGuard = source.indexOf(
'summary.validity.rendererRss.validForBenchmark'
);
assert.ok(summaryWrite >= 0, 'summary artifact write must exist');
assert.ok(validityGuard >= 0, 'renderer RSS validity guard must exist');
assert.ok(
summaryWrite < validityGuard,
'raw summary must be durable before the renderer RSS guard fails'
);
assert.match(source, /Renderer RSS capture is invalid/);
});
+4 -1
View File
@@ -129,7 +129,10 @@ one exact `app.getAppMetrics()` PID and creation time, while responsive and
unresponsive events come only from that window. Identity changes, missing or
ambiguous process metrics, and invalid working-set values fail closed with a
raw reason and nullable RSS; summaries exclude unavailable RSS instead of
reporting zero. Each worker response retains a raw
reporting zero. Formal runs also list every invalid measured capture under
`summary.validity.rendererRss` and fail after persisting the raw summary unless
all measured iterations contribute one valid exact-window RSS value. Each
worker response retains a raw
request-scoped record containing request/operation identity, received/work/flush
timestamps, thread CPU, event-loop utilization, event-loop delay, and fixed
unavailability or invalid reasons. Missing or malformed profiling metadata