Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
79 lines
2.5 KiB
JavaScript
79 lines
2.5 KiB
JavaScript
// Isolated retained-heap measurements; no QQ session, disk logs or live requests.
|
|
// node --expose-gc tools/bench-common-memory.mjs logs|colored-logs|cache [source-directory]
|
|
// The optional directory contains log-sanitize.ts and single-flight-cache.ts,
|
|
// allowing the same workload to run against sources from a baseline revision.
|
|
import { performance } from 'node:perf_hooks';
|
|
import { setImmediate, setTimeout } from 'node:timers/promises';
|
|
import { resolve } from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
if (!global.gc) {
|
|
throw new Error('Run with node --expose-gc');
|
|
}
|
|
|
|
const mode = process.argv[2] ?? 'logs';
|
|
const source = (name) =>
|
|
process.argv[3]
|
|
? pathToFileURL(resolve(process.argv[3], name))
|
|
: new URL(`../packages/common/src/${name}`, import.meta.url);
|
|
|
|
async function heapUsed() {
|
|
await setImmediate();
|
|
global.gc();
|
|
global.gc();
|
|
return process.memoryUsage().heapUsed;
|
|
}
|
|
|
|
const mib = (bytes) => Number((bytes / 1024 / 1024).toFixed(2));
|
|
|
|
if (mode === 'logs' || mode === 'colored-logs') {
|
|
const { sanitizeLogLine } = await import(source('log-sanitize.ts'));
|
|
const before = await heapUsed();
|
|
const records = [];
|
|
const started = performance.now();
|
|
|
|
for (let i = 0; i < 1000; i += 1) {
|
|
const body = `${i}: ${'0123456789abcdef'.repeat(512)}`;
|
|
const line = mode === 'colored-logs' ? `\x1b[32m${body}\x1b[0m` : body;
|
|
records.push(sanitizeLogLine(line));
|
|
}
|
|
|
|
const elapsedMs = Number((performance.now() - started).toFixed(1));
|
|
const retainedHeapMiB = mib((await heapUsed()) - before);
|
|
console.log(JSON.stringify({ mode, records: records.length, retainedHeapMiB, elapsedMs }));
|
|
} else if (mode === 'cache') {
|
|
const { createSingleFlightCache } = await import(source('single-flight-cache.ts'));
|
|
const ttlMs = 2000;
|
|
const cache = createSingleFlightCache({
|
|
ttlMs,
|
|
load: async (key) =>
|
|
Array.from({ length: 1000 }, (_, i) => ({
|
|
uin: key * 1000 + i,
|
|
nickname: `member-${key}-${i}`,
|
|
})),
|
|
});
|
|
const before = await heapUsed();
|
|
|
|
for (let key = 0; key < 500; key += 1) {
|
|
await cache.get(key);
|
|
}
|
|
|
|
const populatedHeapMiB = mib((await heapUsed()) - before);
|
|
await setTimeout(ttlMs + 100);
|
|
const idleHeapMiB = mib((await heapUsed()) - before);
|
|
// Keep the cache itself live through both samples, as it is in ContactsApi.
|
|
cache.clear();
|
|
console.log(
|
|
JSON.stringify({
|
|
mode,
|
|
rosters: 500,
|
|
membersPerRoster: 1000,
|
|
ttlMs,
|
|
populatedHeapMiB,
|
|
idleHeapMiB,
|
|
}),
|
|
);
|
|
} else {
|
|
throw new Error(`Unknown workload: ${mode}`);
|
|
}
|