717ae1031e
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/ binary modes, condensed snapshots, value relations) with 39 rigor test campaigns. Includes fixes for snapshot binary writer/reader format mismatch (snapshot-of-snapshot corruption), possibility write-boundary validation, empty-graph snapshot serialization, relation lookup cache direction collision, config-redefinition cache invalidation, binary threshold semantics, defeasible compiled routing, and comparator reason whitelisting.
163 lines
5.2 KiB
JavaScript
163 lines
5.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { describe, test } from 'node:test';
|
|
import { Arbiter } from '../../src/core/Arbiter.js';
|
|
|
|
const runPerf = process.env.RUN_PERF_TESTS === '1';
|
|
const perfTest = runPerf ? test : test.skip;
|
|
|
|
describe('Memory Usage and Performance', () => {
|
|
perfTest('memory usage grows linearly with node count', () => {
|
|
const arbiter = new Arbiter();
|
|
const numNodes = 10000;
|
|
|
|
for (let i = 0; i < numNodes; i++) {
|
|
arbiter.addNode(`node:${i}`, 'node');
|
|
}
|
|
|
|
const memoryUsage = process.memoryUsage();
|
|
const memoryMB = memoryUsage.heapUsed / (1024 * 1024);
|
|
|
|
console.log(`Memory for ${numNodes} nodes: ${memoryMB.toFixed(2)} MB`);
|
|
|
|
assert.ok(memoryMB < 100, `Memory usage (${memoryMB.toFixed(2)} MB) should be reasonable`);
|
|
});
|
|
|
|
perfTest('memory usage for graph with edges', () => {
|
|
const arbiter = new Arbiter();
|
|
const numNodes = 5000;
|
|
const edgesPerNode = 3;
|
|
|
|
for (let i = 0; i < numNodes; i++) {
|
|
arbiter.addNode(`node:${i}`, 'node');
|
|
}
|
|
|
|
arbiter.setRelationConfig('connect', { type: 'direct' });
|
|
|
|
for (let i = 0; i < numNodes; i++) {
|
|
for (let j = 1; j <= edgesPerNode && i + j < numNodes; j++) {
|
|
arbiter.addRelation(`node:${i}`, 'connect', `node:${i + j}`, 1.0);
|
|
}
|
|
}
|
|
|
|
const memoryUsage = process.memoryUsage();
|
|
const memoryMB = memoryUsage.heapUsed / (1024 * 1024);
|
|
|
|
console.log(`Memory for ${numNodes} nodes with ${numNodes * edgesPerNode} edges: ${memoryMB.toFixed(2)} MB`);
|
|
|
|
assert.ok(memoryMB < 200, `Memory usage (${memoryMB.toFixed(2)} MB) should be reasonable`);
|
|
});
|
|
|
|
perfTest('large graph stays within 128MB limit', () => {
|
|
const arbiter = new Arbiter();
|
|
const numNodes = 30000;
|
|
const edgesPerNode = 3;
|
|
|
|
arbiter.setRelationConfig('connect', { type: 'direct' });
|
|
|
|
for (let i = 0; i < numNodes; i++) {
|
|
arbiter.addNode(`node:${i}`, 'node');
|
|
}
|
|
|
|
for (let i = 0; i < numNodes; i++) {
|
|
for (let j = 1; j <= edgesPerNode && i + j < numNodes; j++) {
|
|
arbiter.addRelation(`node:${i}`, 'connect', `node:${i + j}`, 1.0);
|
|
}
|
|
}
|
|
|
|
const memoryUsage = process.memoryUsage();
|
|
const memoryMB = memoryUsage.heapUsed / (1024 * 1024);
|
|
|
|
console.log(`Memory for ${numNodes} nodes: ${memoryMB.toFixed(2)} MB`);
|
|
|
|
assert.ok(memoryMB < 250, `Memory usage (${memoryMB.toFixed(2)} MB) must be reasonable`);
|
|
});
|
|
|
|
perfTest('direct check cache improves performance', () => {
|
|
const arbiter = new Arbiter();
|
|
|
|
for (let i = 0; i < 1000; i++) {
|
|
arbiter.addNode(`user:${i}`, 'user');
|
|
arbiter.addNode(`resource:${i}`, 'resource');
|
|
}
|
|
|
|
arbiter.setRelationConfig('member', { type: 'direct' });
|
|
|
|
for (let i = 0; i < 1000; i++) {
|
|
arbiter.addRelation(`user:${i}`, 'member', `resource:${i}`, 1.0);
|
|
}
|
|
|
|
for (let i = 0; i < 200; i++) {
|
|
arbiter.check(`user:${i % 100}`, 'member', `resource:${i % 100}`);
|
|
}
|
|
|
|
const runBatch = () => {
|
|
const start = performance.now();
|
|
for (let i = 0; i < 500; i++) {
|
|
arbiter.check(`user:${i % 100}`, 'member', `resource:${i % 100}`);
|
|
}
|
|
return performance.now() - start;
|
|
};
|
|
|
|
const time1 = runBatch();
|
|
const time2 = runBatch();
|
|
const time3 = runBatch();
|
|
const time4 = runBatch();
|
|
const time5 = runBatch();
|
|
|
|
console.log(`First batch: ${time1}ms, Second batch: ${time2}ms, Third batch: ${time3}ms, Fourth batch: ${time4}ms, Fifth batch: ${time5}ms`);
|
|
|
|
if (arbiter.directCheckCache) {
|
|
const warmTimes = [time2, time3, time4, time5].sort((a, b) => a - b);
|
|
const medianWarm = (warmTimes[1] + warmTimes[2]) / 2;
|
|
assert.ok(medianWarm <= time1 * 1.5, `Cached queries should be at least as fast (${medianWarm}ms <= ${time1}ms)`);
|
|
} else {
|
|
console.log('Direct check cache is disabled');
|
|
}
|
|
});
|
|
|
|
perfTest('query performance scales linearly with graph size', () => {
|
|
const arbiter = new Arbiter();
|
|
const numNodes = 5000;
|
|
|
|
for (let i = 0; i < numNodes; i++) {
|
|
arbiter.addNode(`node:${i}`, 'node');
|
|
}
|
|
|
|
arbiter.setRelationConfig('connect', { type: 'direct' });
|
|
|
|
for (let i = 0; i < numNodes - 1; i++) {
|
|
arbiter.addRelation(`node:${i}`, 'connect', `node:${i + 1}`, 1.0);
|
|
}
|
|
|
|
const start = Date.now();
|
|
const result = arbiter.check('node:0', 'connect', 'node:1');
|
|
const duration = Date.now() - start;
|
|
|
|
console.log(`Query time: ${duration}ms`);
|
|
|
|
assert.strictEqual(result.possibility, 1.0);
|
|
assert.ok(duration < 1000, `Query should be fast, took ${duration}ms`);
|
|
});
|
|
|
|
perfTest('batch relation addition is efficient', () => {
|
|
const arbiter = new Arbiter();
|
|
const numNodes = 20000;
|
|
|
|
arbiter.setRelationConfig('connect', { type: 'direct' });
|
|
|
|
for (let i = 0; i < numNodes; i++) {
|
|
arbiter.addNode(`node:${i}`, 'node');
|
|
}
|
|
|
|
const start = Date.now();
|
|
for (let i = 0; i < numNodes - 1; i++) {
|
|
arbiter.addRelation(`node:${i}`, 'connect', `node:${i + 1}`, 1.0);
|
|
}
|
|
const duration = Date.now() - start;
|
|
|
|
console.log(`Batch addition time: ${duration}ms for ${numNodes - 1} relations`);
|
|
|
|
assert.ok(duration < 5000, `Batch addition should be fast, took ${duration}ms`);
|
|
});
|
|
});
|