Files
pi-file-claiming/tests/performance.test.ts
2026-06-19 12:46:02 -04:00

264 lines
8.6 KiB
TypeScript

/**
* performance.test.ts — Performance tests for lock operations.
*
* Tests measure latency of:
* - Single lock acquisition (median, p95)
* - Single lock release
* - Conflict checking
* - Bulk acquire/release
* - Lock info retrieval
* - Registry operation cycle
*
* Thresholds define acceptable performance bounds.
*
* @module file-claiming/performance.test
*/
import { performance } from "node:perf_hooks";
import {
assert,
mockOwner,
TEST_FILE_A,
} from "./test-utils.ts";
// Performance thresholds (ms)
const THRESHOLDS = {
lockAcquisition: 10,
lockRelease: 5,
conflictCheck: 5,
bulkAcquire: 100,
bulkRelease: 50,
lockInfo: 5,
registryOperation: 10,
conflictResolution: 5,
cleanup: 20,
minThroughputPerMs: 0.01,
};
// Lazy module loading
let _acq: any = null;
let _reg: any = null;
let _cfg: any = null;
function getAcq() { if (!_acq) _acq = require("../src/lock-acquisition"); return _acq; }
function getReg() { if (!_reg) _reg = require("../index"); return _reg; }
function getCfg() { if (!_cfg) _cfg = require("../src/config"); return _cfg; }
function resetAll(): void {
getReg().resetRegistry();
getCfg().resetConfig();
}
function measureTimeSync<T>(fn: () => T): { result: T; elapsedMs: number } {
const start = performance.now();
const result = fn();
return { result, elapsedMs: performance.now() - start };
}
// ---------------------------------------------------------------------------
// Test: Single lock acquisition latency
// ---------------------------------------------------------------------------
function testSingleAcquisitionLatency() {
resetAll();
const { acquireLock } = getAcq();
const owner = mockOwner("agent", "perf-test");
const times: number[] = [];
for (let i = 0; i < 100; i++) {
const path = `/tmp/perf-lock-${i}.ts`;
const { elapsedMs } = measureTimeSync(() => {
acquireLock({ path, lockType: "write", owner, autoReleaseTTL: 300_000 });
});
times.push(elapsedMs);
}
const avg = times.reduce((a, b) => a + b, 0) / times.length;
const sorted = [...times].sort((a, b) => a - b);
const median = sorted[Math.floor(sorted.length / 2)];
const p95 = sorted[Math.floor(sorted.length * 0.95)];
assert(avg < THRESHOLDS.lockAcquisition, `Avg ${avg.toFixed(3)}ms < ${THRESHOLDS.lockAcquisition}ms`);
console.log(` ✅ Single acquisition: avg=${avg.toFixed(3)}ms median=${median.toFixed(3)}ms p95=${p95.toFixed(3)}ms`);
}
// ---------------------------------------------------------------------------
// Test: Single lock release latency
// ---------------------------------------------------------------------------
function testSingleReleaseLatency() {
resetAll();
const { acquireLock } = getAcq();
const registry = getReg().getClaimRegistry();
const owner = mockOwner("agent", "perf-release");
const claimIds: string[] = [];
for (let i = 0; i < 100; i++) {
const result = acquireLock({ path: `/tmp/perf-rel-${i}.ts`, lockType: "write", owner });
if (result.claim) claimIds.push(result.claim.id);
}
const times: number[] = [];
for (const claimId of claimIds) {
const { elapsedMs } = measureTimeSync(() => registry.release(claimId));
times.push(elapsedMs);
}
const avg = times.reduce((a, b) => a + b, 0) / times.length;
assert(avg < THRESHOLDS.lockRelease, `Avg release ${avg.toFixed(3)}ms < ${THRESHOLDS.lockRelease}ms`);
console.log(` ✅ Single release: avg=${avg.toFixed(3)}ms`);
}
// ---------------------------------------------------------------------------
// Test: Conflict check latency
// ---------------------------------------------------------------------------
function testConflictCheckLatency() {
resetAll();
const { acquireLock } = getAcq();
const registry = getReg().getClaimRegistry();
const owner = mockOwner("agent", "perf-c");
const other = mockOwner("agent", "perf-o");
acquireLock({ path: TEST_FILE_A, lockType: "write", owner });
const times: number[] = [];
for (let i = 0; i < 100; i++) {
const { elapsedMs } = measureTimeSync(() =>
registry.checkConflict(TEST_FILE_A, "write", other)
);
times.push(elapsedMs);
}
const avg = times.reduce((a, b) => a + b, 0) / times.length;
assert(avg < THRESHOLDS.conflictCheck, `Avg conflict check ${avg.toFixed(3)}ms < ${THRESHOLDS.conflictCheck}ms`);
console.log(` ✅ Conflict check: avg=${avg.toFixed(3)}ms`);
}
// ---------------------------------------------------------------------------
// Test: Bulk acquisition throughput
// ---------------------------------------------------------------------------
function testBulkAcquisition() {
resetAll();
const { acquireLock } = getAcq();
const owner = mockOwner("agent", "perf-bulk");
const count = 500;
const start = performance.now();
for (let i = 0; i < count; i++) {
acquireLock({ path: `/tmp/perf-bulk-${i}.ts`, lockType: "write", owner, autoReleaseTTL: 300_000 });
}
const elapsed = performance.now() - start;
assert(elapsed < THRESHOLDS.bulkAcquire, `Bulk ${count} locks: ${elapsed.toFixed(0)}ms < ${THRESHOLDS.bulkAcquire}ms`);
console.log(` ✅ Bulk acquire ${count} locks: ${elapsed.toFixed(0)}ms (${(count/elapsed).toFixed(2)} ops/ms)`);
}
// ---------------------------------------------------------------------------
// Test: Bulk release
// ---------------------------------------------------------------------------
function testBulkRelease() {
resetAll();
const { acquireLock } = getAcq();
const registry = getReg().getClaimRegistry();
const owner = mockOwner("agent", "perf-bulk-rel");
for (let i = 0; i < 500; i++) {
acquireLock({ path: `/tmp/perf-bulkr-${i}.ts`, lockType: "write", owner });
}
const start = performance.now();
registry.releaseAllByOwner(owner);
const elapsed = performance.now() - start;
assert(elapsed < THRESHOLDS.bulkRelease, `Bulk release: ${elapsed.toFixed(0)}ms < ${THRESHOLDS.bulkRelease}ms`);
console.log(` ✅ Bulk release: ${elapsed.toFixed(0)}ms`);
}
// ---------------------------------------------------------------------------
// Test: Lock info retrieval
// ---------------------------------------------------------------------------
function testLockInfoLatency() {
resetAll();
const { acquireLock, getLockInfo } = getAcq();
const owner = mockOwner("agent", "perf-info");
for (let i = 0; i < 50; i++) {
acquireLock({ path: `/tmp/perf-inf-${i}.ts`, lockType: i % 2 === 0 ? "write" : "read", owner });
}
const times: number[] = [];
for (let i = 0; i < 50; i++) {
const { elapsedMs } = measureTimeSync(() => getLockInfo(`/tmp/perf-inf-${i}.ts`));
times.push(elapsedMs);
}
const avg = times.reduce((a, b) => a + b, 0) / times.length;
assert(avg < THRESHOLDS.lockInfo, `Avg lock info ${avg.toFixed(3)}ms < ${THRESHOLDS.lockInfo}ms`);
console.log(` ✅ Lock info: avg=${avg.toFixed(3)}ms`);
}
// ---------------------------------------------------------------------------
// Test: Registry operation cycle
// ---------------------------------------------------------------------------
function testRegistryOperationCycle() {
resetAll();
const registry = getReg().getClaimRegistry();
const owner = mockOwner("agent", "perf-cycle");
const times: number[] = [];
for (let i = 0; i < 500; i++) {
const claimId = `cycle-${i}`;
const path = `/tmp/perf-cyc-${i}.ts`;
const { elapsedMs } = measureTimeSync(() => {
registry.acquire({ id: claimId, path, lockType: "write", status: "active", owner, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() });
registry.checkConflict(path, "write", owner);
registry.release(claimId);
});
times.push(elapsedMs);
}
const avg = times.reduce((a, b) => a + b, 0) / times.length;
assert(avg < THRESHOLDS.registryOperation, `Avg cycle ${avg.toFixed(3)}ms < ${THRESHOLDS.registryOperation}ms`);
console.log(` ✅ Registry cycle: avg=${avg.toFixed(3)}ms`);
}
// ---------------------------------------------------------------------------
// Test runner
// ---------------------------------------------------------------------------
function runTests() {
console.log("Running Performance Tests\n");
console.log("Thresholds:");
for (const [key, val] of Object.entries(THRESHOLDS)) {
console.log(` ${key}: ${val}ms`);
}
console.log("");
const tests = [
testSingleAcquisitionLatency,
testSingleReleaseLatency,
testConflictCheckLatency,
testBulkAcquisition,
testBulkRelease,
testLockInfoLatency,
testRegistryOperationCycle,
];
for (const test of tests) {
try {
test();
} catch (err) {
console.error(`\n❌ Performance test ${test.name} failed: ${err}`);
process.exit(1);
}
}
console.log("\n✅ All performance tests passed!");
}
runTests();