refactor(perf): expose serializable renderer RSS capture

This commit is contained in:
4gray committed 2026-07-27 02:15:58 +02:00
1 parent e27685dc5d
commit 5aab81e2cb
2 files changed
+247 -150

No files matched your search

@@ -2,6 +2,7 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import * as rendererProcessRssCaptureModule from './renderer-process-rss-capture';
import {
captureRendererProcessRssSample,
createRendererProcessRssCapture,
@@ -12,6 +13,34 @@ import {
const RENDERER_PID = 42;
const RENDERER_CREATION_TIME = 1_721_234_567_890;
test('exposes a self-contained factory for the Electron main-process injection boundary', () => {
const factory = (
rendererProcessRssCaptureModule as unknown as Record<string, unknown>
)['createRendererProcessRssCaptureApi'];
assert.equal(typeof factory, 'function');
const source = (factory as () => unknown).toString();
const restoredFactory = Function(
`"use strict"; return (${source});`
)() as () => {
create(
webContentsOsPid: unknown,
processMetrics: readonly unknown[]
): RendererProcessRssCapture;
};
assert.deepEqual(restoredFactory().create(RENDERER_PID, [metric()]), {
identity: {
creationTime: RENDERER_CREATION_TIME,
pid: RENDERER_PID,
},
missingSampleCount: 0,
peakRssBytes: 1_263_616,
unavailableReason: null,
validSampleCount: 1,
});
});
interface MetricOverrides {
readonly creationTime?: unknown;
readonly pid?: unknown;
@@ -21,132 +21,237 @@ export interface RendererProcessRssCapture {
readonly validSampleCount: number;
}
type JsonRecord = Record<string, unknown>;
const KIBIBYTE_BYTES = 1_024;
const MAX_WORKING_SET_KIB = Math.floor(
Number.MAX_SAFE_INTEGER / KIBIBYTE_BYTES
);
function readRecord(value: unknown): JsonRecord | null {
return typeof value === 'object' && value !== null
? (value as JsonRecord)
: null;
export interface RendererProcessRssCaptureApi {
readonly create: (
webContentsOsPid: unknown,
processMetrics: readonly unknown[]
) => RendererProcessRssCapture;
readonly sample: (
capture: RendererProcessRssCapture,
webContentsOsPid: unknown,
processMetrics: readonly unknown[]
) => RendererProcessRssCapture;
}
function isValidOsPid(value: unknown): value is number {
return Number.isSafeInteger(value) && Number(value) > 0;
}
export function createRendererProcessRssCaptureApi(): RendererProcessRssCaptureApi {
type JsonRecord = Record<string, unknown>;
function findPidCandidates(
processMetrics: readonly unknown[],
pid: number
): JsonRecord[] {
return processMetrics
.map((metric) => readRecord(metric))
.filter(
(metric): metric is JsonRecord =>
metric !== null && metric['pid'] === pid
);
}
const KIBIBYTE_BYTES = 1_024;
const MAX_WORKING_SET_KIB = Math.floor(
Number.MAX_SAFE_INTEGER / KIBIBYTE_BYTES
);
function readMetric(metric: JsonRecord):
| {
readonly creationTime: number;
readonly rssBytes: number;
}
| RendererProcessRssUnavailableReason {
if (metric['type'] !== 'Tab' && metric['type'] !== 'Renderer') {
return 'renderer-process-metric-type-invalid';
}
const helpers = {
readRecord(value: unknown): JsonRecord | null {
return typeof value === 'object' && value !== null
? (value as JsonRecord)
: null;
},
const creationTime = metric['creationTime'];
if (
typeof creationTime !== 'number' ||
!Number.isFinite(creationTime) ||
creationTime < 0
) {
return 'renderer-process-metric-creation-time-invalid';
}
isValidOsPid(value: unknown): value is number {
return Number.isSafeInteger(value) && Number(value) > 0;
},
const memory = readRecord(metric['memory']);
const workingSetSize = memory?.['workingSetSize'];
if (
!Number.isSafeInteger(workingSetSize) ||
Number(workingSetSize) <= 0 ||
Number(workingSetSize) > MAX_WORKING_SET_KIB
) {
return 'renderer-process-metric-working-set-invalid';
}
findPidCandidates(
processMetrics: readonly unknown[],
pid: number
): JsonRecord[] {
return processMetrics
.map((metric) => helpers.readRecord(metric))
.filter(
(metric): metric is JsonRecord =>
metric !== null && metric['pid'] === pid
);
},
return {
creationTime,
rssBytes: Number(workingSetSize) * KIBIBYTE_BYTES,
readMetric(metric: JsonRecord):
| {
readonly creationTime: number;
readonly rssBytes: number;
}
| RendererProcessRssUnavailableReason {
if (metric['type'] !== 'Tab' && metric['type'] !== 'Renderer') {
return 'renderer-process-metric-type-invalid';
}
const creationTime = metric['creationTime'];
if (
typeof creationTime !== 'number' ||
!Number.isFinite(creationTime) ||
creationTime < 0
) {
return 'renderer-process-metric-creation-time-invalid';
}
const memory = helpers.readRecord(metric['memory']);
const workingSetSize = memory?.['workingSetSize'];
if (
!Number.isSafeInteger(workingSetSize) ||
Number(workingSetSize) <= 0 ||
Number(workingSetSize) > MAX_WORKING_SET_KIB
) {
return 'renderer-process-metric-working-set-invalid';
}
return {
creationTime,
rssBytes: Number(workingSetSize) * KIBIBYTE_BYTES,
};
},
unavailableCapture(
unavailableReason: RendererProcessRssUnavailableReason,
options: {
readonly identity?: RendererProcessIdentity | null;
readonly missingSampleCount?: number;
readonly validSampleCount?: number;
} = {}
): RendererProcessRssCapture {
return Object.freeze({
identity: options.identity ?? null,
missingSampleCount: options.missingSampleCount ?? 0,
peakRssBytes: null,
unavailableReason,
validSampleCount: options.validSampleCount ?? 0,
});
},
invalidateCapture(
capture: RendererProcessRssCapture,
unavailableReason: RendererProcessRssUnavailableReason,
missingSampleCount = capture.missingSampleCount
): RendererProcessRssCapture {
return helpers.unavailableCapture(unavailableReason, {
identity: capture.identity,
missingSampleCount,
validSampleCount: capture.validSampleCount,
});
},
create(
webContentsOsPid: unknown,
processMetrics: readonly unknown[]
): RendererProcessRssCapture {
if (!helpers.isValidOsPid(webContentsOsPid)) {
return helpers.unavailableCapture('renderer-os-pid-invalid');
}
const candidates = helpers.findPidCandidates(
processMetrics,
webContentsOsPid
);
if (candidates.length === 0) {
return helpers.unavailableCapture(
'renderer-process-metric-missing-at-start',
{
missingSampleCount: 1,
}
);
}
if (candidates.length !== 1) {
return helpers.unavailableCapture(
'renderer-process-metric-ambiguous'
);
}
const metric = helpers.readMetric(candidates[0] as JsonRecord);
if (typeof metric === 'string') {
return helpers.unavailableCapture(metric);
}
return Object.freeze({
identity: Object.freeze({
creationTime: metric.creationTime,
pid: webContentsOsPid,
}),
missingSampleCount: 0,
peakRssBytes: metric.rssBytes,
unavailableReason: null,
validSampleCount: 1,
});
},
sample(
capture: RendererProcessRssCapture,
webContentsOsPid: unknown,
processMetrics: readonly unknown[]
): RendererProcessRssCapture {
if (capture.unavailableReason !== null) {
return capture;
}
if (!helpers.isValidOsPid(webContentsOsPid)) {
return helpers.invalidateCapture(
capture,
'renderer-os-pid-invalid'
);
}
const identity = capture.identity;
if (identity === null || webContentsOsPid !== identity.pid) {
return helpers.invalidateCapture(
capture,
'renderer-process-identity-changed'
);
}
const candidates = helpers.findPidCandidates(
processMetrics,
webContentsOsPid
);
if (candidates.length === 0) {
return helpers.invalidateCapture(
capture,
'renderer-process-metric-missing-during-capture',
capture.missingSampleCount + 1
);
}
if (candidates.length !== 1) {
return helpers.invalidateCapture(
capture,
'renderer-process-metric-ambiguous'
);
}
const metric = helpers.readMetric(candidates[0] as JsonRecord);
if (typeof metric === 'string') {
return helpers.invalidateCapture(capture, metric);
}
if (metric.creationTime !== identity.creationTime) {
return helpers.invalidateCapture(
capture,
'renderer-process-identity-changed'
);
}
return Object.freeze({
identity,
missingSampleCount: capture.missingSampleCount,
peakRssBytes: Math.max(
capture.peakRssBytes ?? metric.rssBytes,
metric.rssBytes
),
unavailableReason: null,
validSampleCount: capture.validSampleCount + 1,
});
},
};
}
function unavailableCapture(
unavailableReason: RendererProcessRssUnavailableReason,
options: {
readonly identity?: RendererProcessIdentity | null;
readonly missingSampleCount?: number;
readonly validSampleCount?: number;
} = {}
): RendererProcessRssCapture {
return Object.freeze({
identity: options.identity ?? null,
missingSampleCount: options.missingSampleCount ?? 0,
peakRssBytes: null,
unavailableReason,
validSampleCount: options.validSampleCount ?? 0,
create: helpers.create,
sample: helpers.sample,
});
}
function invalidateCapture(
capture: RendererProcessRssCapture,
unavailableReason: RendererProcessRssUnavailableReason,
missingSampleCount = capture.missingSampleCount
): RendererProcessRssCapture {
return unavailableCapture(unavailableReason, {
identity: capture.identity,
missingSampleCount,
validSampleCount: capture.validSampleCount,
});
}
const rendererProcessRssCaptureApi = createRendererProcessRssCaptureApi();
export function createRendererProcessRssCapture(
webContentsOsPid: unknown,
processMetrics: readonly unknown[]
): RendererProcessRssCapture {
if (!isValidOsPid(webContentsOsPid)) {
return unavailableCapture('renderer-os-pid-invalid');
}
const candidates = findPidCandidates(processMetrics, webContentsOsPid);
if (candidates.length === 0) {
return unavailableCapture('renderer-process-metric-missing-at-start', {
missingSampleCount: 1,
});
}
if (candidates.length !== 1) {
return unavailableCapture('renderer-process-metric-ambiguous');
}
const metric = readMetric(candidates[0] as JsonRecord);
if (typeof metric === 'string') {
return unavailableCapture(metric);
}
return Object.freeze({
identity: Object.freeze({
creationTime: metric.creationTime,
pid: webContentsOsPid,
}),
missingSampleCount: 0,
peakRssBytes: metric.rssBytes,
unavailableReason: null,
validSampleCount: 1,
});
return rendererProcessRssCaptureApi.create(
webContentsOsPid,
processMetrics
);
}
export function captureRendererProcessRssSample(
@@ -154,46 +259,9 @@ export function captureRendererProcessRssSample(
webContentsOsPid: unknown,
processMetrics: readonly unknown[]
): RendererProcessRssCapture {
if (capture.unavailableReason !== null) {
return capture;
}
if (!isValidOsPid(webContentsOsPid)) {
return invalidateCapture(capture, 'renderer-os-pid-invalid');
}
const identity = capture.identity;
if (identity === null || webContentsOsPid !== identity.pid) {
return invalidateCapture(capture, 'renderer-process-identity-changed');
}
const candidates = findPidCandidates(processMetrics, webContentsOsPid);
if (candidates.length === 0) {
return invalidateCapture(
capture,
'renderer-process-metric-missing-during-capture',
capture.missingSampleCount + 1
);
}
if (candidates.length !== 1) {
return invalidateCapture(capture, 'renderer-process-metric-ambiguous');
}
const metric = readMetric(candidates[0] as JsonRecord);
if (typeof metric === 'string') {
return invalidateCapture(capture, metric);
}
if (metric.creationTime !== identity.creationTime) {
return invalidateCapture(capture, 'renderer-process-identity-changed');
}
return Object.freeze({
identity,
missingSampleCount: capture.missingSampleCount,
peakRssBytes: Math.max(
capture.peakRssBytes ?? metric.rssBytes,
metric.rssBytes
),
unavailableReason: null,
validSampleCount: capture.validSampleCount + 1,
});
return rendererProcessRssCaptureApi.sample(
capture,
webContentsOsPid,
processMetrics
);
}