Files
core/benchmarks/profile-clean.js
John Dvorak 717ae1031e initial commit: @arbiter/core authorization engine with js-rigor hardening
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.
2026-07-31 13:44:06 -07:00

240 lines
10 KiB
JavaScript

import { Arbiter } from '../src/core/Arbiter.js';
const arbiter = new Arbiter();
const SIZE = 5000;
// Build scenario
for (let i = 0; i < SIZE; i++) {
arbiter.addNode(`user:${i}`, 'user');
arbiter.addNode(`resource:${i}`, 'resource');
const isSafe = i % 2 === 0;
arbiter.addRelation(`user:${i}`, 'risk_score', `resource:${i}`, 1.0, { value: isSafe ? 20 : 80 });
arbiter.addRelation(`user:${i}`, 'risk_bonus', `resource:${i}`, 1.0, { value: isSafe ? 5 : 15 });
arbiter.addRelation(`user:${i}`, 'risk_noise', `resource:${i}`, 1.0, { value: isSafe ? 0 : 10 });
arbiter.addRelation(`resource:${i}`, 'risk_limit', `resource:${i}`, 1.0, { value: 40 });
arbiter.addRelation(`resource:${i}`, 'risk_cap', `resource:${i}`, 1.0, { value: 45 });
}
['risk_score', 'risk_bonus', 'risk_noise', 'risk_limit', 'risk_cap'].forEach(r =>
arbiter.setRelationConfig(r, { type: 'direct' }));
arbiter.setRelationConfig('risk_ok_owa', {
type: 'relational_comparator',
comparator: '<=',
fallbackBehavior: 'deny',
left: {
rule: {
union: {
rules: [
{ type: 'direct', relation: 'risk_score' },
{ type: 'direct', relation: 'risk_bonus' },
{ type: 'direct', relation: 'risk_noise' }
],
aggregator: 'owa',
owaWeights: [0.5, 0.3, 0.2]
}
},
extractValue: true,
valueRelation: 'risk_score',
aggregator: 'owa',
owaWeights: [0.5, 0.3, 0.2]
},
right: {
rule: {
union: {
rules: [
{ type: 'direct', relation: 'risk_limit' },
{ type: 'direct', relation: 'risk_cap' }
],
aggregator: 'owa',
owaWeights: [0.6, 0.4]
}
},
extractValue: true,
valueRelation: 'risk_limit',
evaluateFrom: 'object',
aggregator: 'owa',
owaWeights: [0.6, 0.4]
}
});
// Warm up
for (let i = 0; i < 100; i++) {
arbiter.check(`user:${i}`, 'risk_ok_owa', `resource:${i}`, { fastPath: true });
}
const ITERATIONS = 2000;
function measure(label, fn) {
const start = process.hrtime.bigint();
for (let i = 0; i < ITERATIONS; i++) fn(i % SIZE);
const ms = Number(process.hrtime.bigint() - start) / 1e6;
return { label, ms, qps: Math.round((ITERATIONS / ms) * 1000), perOp: ms / ITERATIONS };
}
console.log('=== BASELINE MEASUREMENTS ===\n');
// Baseline: single direct check
const directCheck = measure('Direct check (single)', (idx) => {
arbiter.check(`user:${idx}`, 'risk_score', `resource:${idx}`, { fastPath: true });
});
console.log(`${directCheck.label}: ${directCheck.qps} QPS (${(directCheck.perOp * 1000).toFixed(2)}µs/op)`);
// Full comparator check
const fullCheck = measure('Full comparator check', (idx) => {
arbiter.check(`user:${idx}`, 'risk_ok_owa', `resource:${idx}`, { fastPath: true });
});
console.log(`${fullCheck.label}: ${fullCheck.qps} QPS (${(fullCheck.perOp * 1000).toFixed(2)}µs/op)`);
console.log(`\nSlowdown: ${(fullCheck.perOp / directCheck.perOp).toFixed(1)}x`);
console.log(`Extra time per check: ${((fullCheck.perOp - directCheck.perOp) * 1000).toFixed(2)}µs`);
console.log('\n=== COMPONENT BREAKDOWN ===\n');
// What does a comparator check actually do?
// 1. Resolve node IDs (2x)
// 2. Get relation config
// 3. Cycle detection (visited set scan)
// 4. Left operand: evaluate union (3 direct rules) + extract values + aggregate
// 5. Right operand: evaluate union (2 direct rules) + extract values + aggregate
// 6. Compare intervals
// 7. Build result meta
// Measure individual components
const ruleEval = arbiter.authChecker.ruleEvaluator;
const comparatorHandler = ruleEval.ruleHandlers.relational_comparator.numericRule;
const config = arbiter.relationConfigs.get('risk_ok_owa');
// Component 1: Node ID resolution (2 lookups)
const nodeIdLookup = measure('Node ID lookup (2x)', (idx) => {
arbiter.nodeIdByKey.get(`user:${idx}`);
arbiter.nodeIdByKey.get(`resource:${idx}`);
});
console.log(`${nodeIdLookup.label}: ${(nodeIdLookup.perOp * 1000).toFixed(3)}µs`);
// Component 2: Config lookup
const configLookup = measure('Config lookup', () => {
arbiter.relationConfigs.get('risk_ok_owa');
});
console.log(`${configLookup.label}: ${(configLookup.perOp * 1000).toFixed(3)}µs`);
// Component 3: Direct check via indices (what union does internally per rule)
const directIndex = measure('Direct index lookup', (idx) => {
const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
arbiter.indices.getDirectRelation(userId, 'risk_score', objectId);
});
console.log(`${directIndex.label}: ${(directIndex.perOp * 1000).toFixed(3)}µs`);
// Component 4: Evaluate a single direct rule via ruleEvaluator
const singleDirect = measure('Single direct rule eval', (idx) => {
const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
ruleEval.evaluateRule(userId, `user:${idx}`, objectId, `resource:${idx}`,
{ type: 'direct', relation: 'risk_score' }, new Set(), 'test', { fastPath: true });
});
console.log(`${singleDirect.label}: ${(singleDirect.perOp * 1000).toFixed(3)}µs`);
// Component 5: Union of 3 direct rules
const union3 = measure('Union (3 direct rules)', (idx) => {
const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
ruleEval.evaluateRule(userId, `user:${idx}`, objectId, `resource:${idx}`,
config.left.rule, new Set(), 'test', { fastPath: true });
});
console.log(`${union3.label}: ${(union3.perOp * 1000).toFixed(3)}µs`);
// Component 6: Union of 2 direct rules
const union2 = measure('Union (2 direct rules)', (idx) => {
const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
ruleEval.evaluateRule(objectId, `resource:${idx}`, objectId, `resource:${idx}`,
config.right.rule, new Set(), 'test', { fastPath: true });
});
console.log(`${union2.label}: ${(union2.perOp * 1000).toFixed(3)}µs`);
// Component 7: _evaluateOperand (left) - includes union + extract + aggregate
const leftOp = measure('Left _evaluateOperand', (idx) => {
const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
comparatorHandler._evaluateOperand(
userId, `user:${idx}`, objectId, `resource:${idx}`,
config.left, new Set(), 'test', { fastPath: true }, 'left', 1.0, null
);
});
console.log(`${leftOp.label}: ${(leftOp.perOp * 1000).toFixed(3)}µs`);
// Component 8: _evaluateOperand (right)
const rightOp = measure('Right _evaluateOperand', (idx) => {
const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
comparatorHandler._evaluateOperand(
userId, `user:${idx}`, objectId, `resource:${idx}`,
config.right, new Set(), 'test', { fastPath: true }, 'right', 1.0, null
);
});
console.log(`${rightOp.label}: ${(rightOp.perOp * 1000).toFixed(3)}µs`);
// Component 9: _evaluateRule (both operands + compare)
const evalRule = measure('comparator._evaluateRule', (idx) => {
const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
comparatorHandler._evaluateRule(
userId, `user:${idx}`, objectId, `resource:${idx}`,
config, new Set(), 'test', { fastPath: true }
);
});
console.log(`${evalRule.label}: ${(evalRule.perOp * 1000).toFixed(3)}µs`);
console.log('\n=== OVERHEAD ANALYSIS ===\n');
const directIdxTime = directIndex.perOp * 1000;
const singleDirectTime = singleDirect.perOp * 1000;
const union3Time = union3.perOp * 1000;
const leftOpTime = leftOp.perOp * 1000;
const rightOpTime = rightOp.perOp * 1000;
const evalRuleTime = evalRule.perOp * 1000;
const fullCheckTime = fullCheck.perOp * 1000;
console.log(`Index lookup: ${directIdxTime.toFixed(2)}µs`);
console.log(`Single direct rule: ${singleDirectTime.toFixed(2)}µs (+${(singleDirectTime - directIdxTime).toFixed(2)}µs overhead)`);
console.log(`Union (3 rules): ${union3Time.toFixed(2)}µs (expected ~${(singleDirectTime * 3).toFixed(2)}µs, actual overhead: ${(union3Time - singleDirectTime * 3).toFixed(2)}µs)`);
console.log(`Left operand: ${leftOpTime.toFixed(2)}µs (+${(leftOpTime - union3Time).toFixed(2)}µs for extract+aggregate)`);
console.log(`Right operand: ${rightOpTime.toFixed(2)}µs`);
console.log(`_evaluateRule: ${evalRuleTime.toFixed(2)}µs (expected ~${(leftOpTime + rightOpTime).toFixed(2)}µs, actual: ${evalRuleTime.toFixed(2)}µs)`);
console.log(`Full check: ${fullCheckTime.toFixed(2)}µs (+${(fullCheckTime - evalRuleTime).toFixed(2)}µs AuthChecker overhead)`);
console.log('\n=== THEORETICAL VS ACTUAL ===\n');
const theoreticalMin = (singleDirectTime * 5) + 2; // 5 direct lookups + some overhead
console.log(`Theoretical minimum (5 direct lookups): ~${theoreticalMin.toFixed(2)}µs`);
console.log(`Actual: ${fullCheckTime.toFixed(2)}µs`);
console.log(`Overhead factor: ${(fullCheckTime / theoreticalMin).toFixed(1)}x`);
// Check Set creation overhead
console.log('\n=== SET CREATION OVERHEAD ===\n');
const setCreate = measure('new Set()', () => new Set());
const setWithAdd = measure('new Set() + 1 add', () => {
const s = new Set();
s.add({ userKey: 'user:0', relation: 'test', objectKey: 'resource:0' });
});
console.log(`new Set(): ${(setCreate.perOp * 1000).toFixed(3)}µs`);
console.log(`new Set() + 1 object add: ${(setWithAdd.perOp * 1000).toFixed(3)}µs`);
console.log(`Sets created per check: ~6-8`);
console.log(`Total Set overhead: ~${((setCreate.perOp * 7) * 1000).toFixed(2)}µs`);
// Check object allocation in visited scan
console.log('\n=== VISITED SCAN OVERHEAD ===\n');
const visited = new Set();
for (let i = 0; i < 10; i++) {
visited.add({ userKey: `user:${i}`, relation: 'test', objectKey: `resource:${i}` });
}
const visitedScan = measure('Scan visited (10 entries)', () => {
const userKey = 'user:5';
const relation = 'test';
const objectKey = 'resource:5';
for (const v of visited) {
if (v.userKey === userKey && v.relation === relation && v.objectKey === objectKey) break;
}
});
console.log(`Scan 10-entry visited set: ${(visitedScan.perOp * 1000).toFixed(3)}µs`);
console.log(`(This happens at each level of rule evaluation)`);