import { createIntegrationRequestHandler } from "../../packages/request-handler/src/lib/integration-request-handler.ts"; import { createRequestHandler } from "../../packages/request-handler/src/lib/request-handler.ts"; const allocations = Number(process.env.REQUEST_MEMORY_ALLOCATIONS ?? 64); const allocationBytes = Number(process.env.REQUEST_MEMORY_ALLOCATION_BYTES ?? 1024 * 1024); const maxRetainedBytes = Number(process.env.REQUEST_MEMORY_MAX_RETAINED_BYTES ?? 16 * 1024 * 1024); const positiveTtlMs = Number(process.env.REQUEST_MEMORY_POSITIVE_TTL_MS ?? 25); if (!globalThis.gc) throw new Error("Run this benchmark with node --expose-gc"); if (!Number.isInteger(allocations) || allocations < 2) throw new Error("REQUEST_MEMORY_ALLOCATIONS must be >= 2"); if (!Number.isInteger(allocationBytes) || allocationBytes < 1) { throw new Error("REQUEST_MEMORY_ALLOCATION_BYTES must be positive"); } const collectGarbage = async () => { for (let index = 0; index < 4; index++) { globalThis.gc?.(); await new Promise((resolve) => setImmediate(resolve)); } }; const retainedRoots: unknown[] = []; const measureArrayBufferGrowthAsync = async (workload: () => Promise) => { await collectGarbage(); const before = process.memoryUsage().arrayBuffers; const retainedRoot = await workload(); retainedRoots.push(retainedRoot); await collectGarbage(); const growth = process.memoryUsage().arrayBuffers - before; retainedRoots.pop(); return growth; }; const zeroTtlGrowthBytes = await measureArrayBufferGrowthAsync(async () => { const handler = createRequestHandler({ cacheTtlMs: 0, requestAsync: async () => new Uint8Array(allocationBytes), }); for (let id = 0; id < allocations; id++) { await handler.handler({ id }).getDataAsync(); } return handler; }); const integrationGrowthBytes = await measureArrayBufferGrowthAsync(async () => { const handler = createIntegrationRequestHandler>({ cacheTtlMs: 0, requestAsync: async (integration) => (integration as typeof integration & { benchmarkPayload: Uint8Array }).benchmarkPayload.byteLength, }); for (let id = 0; id < allocations; id++) { await handler .handler( { id: `benchmark-${id}`, kind: "mock", name: "benchmark", url: "http://benchmark.invalid", externalUrl: null, decryptedSecrets: [], benchmarkPayload: new Uint8Array(allocationBytes), } as never, {}, ) .getDataAsync(); } return handler; }); const expiredPositiveTtlGrowthBytes = await measureArrayBufferGrowthAsync(async () => { const handler = createRequestHandler({ cacheTtlMs: positiveTtlMs, requestAsync: async () => new Uint8Array(allocationBytes), }); for (let id = 0; id < allocations; id++) { await handler.handler({ id }).getDataAsync(); } await new Promise((resolve) => setTimeout(resolve, positiveTtlMs + 25)); return handler; }); const positiveControlGrowthBytes = await measureArrayBufferGrowthAsync(async () => Array.from({ length: allocations }, () => new Uint8Array(allocationBytes)), ); const positiveControlMinimumBytes = allocations * allocationBytes * 0.8; const result = { allocations, allocationBytes, allocatedPerScenarioBytes: allocations * allocationBytes, maxRetainedBytes, positiveTtlMs, zeroTtlGrowthBytes, integrationGrowthBytes, expiredPositiveTtlGrowthBytes, positiveControlGrowthBytes, positiveControlMinimumBytes, zeroTtlPass: zeroTtlGrowthBytes <= maxRetainedBytes, integrationPass: integrationGrowthBytes <= maxRetainedBytes, expiredPositiveTtlPass: expiredPositiveTtlGrowthBytes <= maxRetainedBytes, positiveControlPass: positiveControlGrowthBytes >= positiveControlMinimumBytes, }; console.log(JSON.stringify(result, null, 2)); if (!result.zeroTtlPass || !result.integrationPass || !result.expiredPositiveTtlPass || !result.positiveControlPass) { process.exitCode = 1; }