/** * 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(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();