import { Arbiter } from '../src/core/Arbiter.js'; const arbiter = new Arbiter(); const SIZE = 5000; // Build nodes and relations 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' })); const leftRule = { 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] } }; const rightRule = { union: { rules: [ { type: 'direct', relation: 'risk_limit' }, { type: 'direct', relation: 'risk_cap' } ], aggregator: 'owa', owaWeights: [0.6, 0.4] } }; arbiter.setRelationConfig('risk_ok_owa', { type: 'relational_comparator', comparator: '<=', fallbackBehavior: 'deny', left: { rule: leftRule, extractValue: true, valueRelation: 'risk_score', aggregator: 'owa', owaWeights: [0.5, 0.3, 0.2] }, right: { rule: rightRule, 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; const comparatorRule = arbiter.authChecker.ruleEvaluator.ruleHandlers.relational_comparator.numericRule; 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) }; } // Profile each step of the comparator const results = []; // 1. Node ID lookup results.push(measure('nodeIdLookup', (idx) => { arbiter.nodeIdByKey.get(`user:${idx}`); arbiter.nodeIdByKey.get(`resource:${idx}`); })); // 2. Left union evaluation results.push(measure('leftUnionEval', (idx) => { const userId = arbiter.nodeIdByKey.get(`user:${idx}`); const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); arbiter.authChecker.ruleEvaluator.evaluateRule( userId, `user:${idx}`, objectId, `resource:${idx}`, leftRule, new Set(), 'risk_ok_owa', { fastPath: true } ); })); // 3. Right union evaluation results.push(measure('rightUnionEval', (idx) => { const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); arbiter.authChecker.ruleEvaluator.evaluateRule( objectId, `resource:${idx}`, objectId, `resource:${idx}`, rightRule, new Set(), 'risk_ok_owa', { fastPath: true } ); })); // 4. _extractValues (left) results.push(measure('leftExtract', (idx) => { const userId = arbiter.nodeIdByKey.get(`user:${idx}`); const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule( userId, `user:${idx}`, objectId, `resource:${idx}`, leftRule, new Set(), 'risk_ok_owa', { fastPath: true } ); comparatorRule._extractValues( userId, `user:${idx}`, objectId, `resource:${idx}`, leftRule, 'risk_score', ruleResult, 'auto', null, 24*60*60*1000, null ); })); // 5. _extractValues (right) results.push(measure('rightExtract', (idx) => { const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule( objectId, `resource:${idx}`, objectId, `resource:${idx}`, rightRule, new Set(), 'risk_ok_owa', { fastPath: true } ); comparatorRule._extractValues( objectId, `resource:${idx}`, objectId, `resource:${idx}`, rightRule, 'risk_limit', ruleResult, 'object', null, 24*60*60*1000, null ); })); // 6. Full _evaluateOperand (includes eval + extract + aggregate) results.push(measure('leftOperand', (idx) => { const userId = arbiter.nodeIdByKey.get(`user:${idx}`); const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); comparatorRule._evaluateOperand( userId, `user:${idx}`, objectId, `resource:${idx}`, { rule: leftRule, extractValue: true, valueRelation: 'risk_score', aggregator: 'owa', owaWeights: [0.5, 0.3, 0.2] }, new Set(), 'risk_ok_owa', { fastPath: true }, 'left', 1.0, null ); })); results.push(measure('rightOperand', (idx) => { const userId = arbiter.nodeIdByKey.get(`user:${idx}`); const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); comparatorRule._evaluateOperand( userId, `user:${idx}`, objectId, `resource:${idx}`, { rule: rightRule, extractValue: true, valueRelation: 'risk_limit', evaluateFrom: 'object', aggregator: 'owa', owaWeights: [0.6, 0.4] }, new Set(), 'risk_ok_owa', { fastPath: true }, 'right', 1.0, null ); })); // 7. Full comparator evaluation results.push(measure('fullComparator', (idx) => { const userId = arbiter.nodeIdByKey.get(`user:${idx}`); const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); comparatorRule._evaluateRule( userId, `user:${idx}`, objectId, `resource:${idx}`, arbiter.relationConfigs.get('risk_ok_owa'), new Set(), 'risk_ok_owa', { fastPath: true } ); })); // 8. Through authChecker (adds visited set, config lookup, etc) results.push(measure('viaAuthChecker', (idx) => { arbiter.check(`user:${idx}`, 'risk_ok_owa', `resource:${idx}`, { fastPath: true }); })); console.log('Profile Results (per iteration):'); console.log('================================'); for (const r of results) { console.log(`${r.label.padEnd(20)} ${r.ms.toFixed(2).padStart(8)}ms ${r.qps.toString().padStart(8)} QPS`); } // Calculate incremental costs console.log('\n--- Incremental Analysis ---'); const nodeIdMs = results[0].ms; const leftUnionMs = results[1].ms; const rightUnionMs = results[2].ms; const leftExtractMs = results[3].ms; const rightExtractMs = results[4].ms; const leftOperandMs = results[5].ms; const rightOperandMs = results[6].ms; const fullComparatorMs = results[7].ms; const viaAuthCheckerMs = results[8].ms; console.log(`Node ID lookup: ${nodeIdMs.toFixed(2)}ms`); console.log(`Left union eval only: ${(leftUnionMs - nodeIdMs).toFixed(2)}ms`); console.log(`Right union eval only: ${(rightUnionMs - nodeIdMs).toFixed(2)}ms`); console.log(`Extract overhead (left): ${(leftExtractMs - leftUnionMs).toFixed(2)}ms`); console.log(`Extract overhead (right): ${(rightExtractMs - rightUnionMs).toFixed(2)}ms`); console.log(`Aggregate overhead (left): ${(leftOperandMs - leftExtractMs).toFixed(2)}ms`); console.log(`Aggregate overhead (right): ${(rightOperandMs - rightExtractMs).toFixed(2)}ms`); console.log(`Compare + meta overhead: ${(fullComparatorMs - leftOperandMs - rightOperandMs + nodeIdMs).toFixed(2)}ms`); console.log(`AuthChecker overhead: ${(viaAuthCheckerMs - fullComparatorMs).toFixed(2)}ms`); // Check what percentage each part takes console.log('\n--- Time Distribution (% of total) ---'); const total = viaAuthCheckerMs; console.log(`Node ID lookup: ${((nodeIdMs / total) * 100).toFixed(1)}%`); console.log(`Left union: ${(((leftUnionMs - nodeIdMs) / total) * 100).toFixed(1)}%`); console.log(`Right union: ${(((rightUnionMs - nodeIdMs) / total) * 100).toFixed(1)}%`); console.log(`Left extract: ${(((leftExtractMs - leftUnionMs) / total) * 100).toFixed(1)}%`); console.log(`Right extract: ${(((rightExtractMs - rightUnionMs) / total) * 100).toFixed(1)}%`); console.log(`Left aggregate: ${(((leftOperandMs - leftExtractMs) / total) * 100).toFixed(1)}%`); console.log(`Right aggregate: ${(((rightOperandMs - rightExtractMs) / total) * 100).toFixed(1)}%`); console.log(`Compare + meta: ${(((fullComparatorMs - leftOperandMs - rightOperandMs + nodeIdMs) / total) * 100).toFixed(1)}%`); console.log(`AuthChecker overhead: ${(((viaAuthCheckerMs - fullComparatorMs) / total) * 100).toFixed(1)}%`); // Check cost of new Set() creation console.log('\n--- Set Creation Overhead ---'); const setCreation = measure('new Set()', () => new Set()); console.log(`new Set() per iteration: ${(setCreation.ms / ITERATIONS * 1000).toFixed(4)}µs`); console.log(`Set creations per comparator check: ~4-6 (left union, right union, left operand, right operand, authChecker)`); console.log(`Estimated Set overhead: ${(setCreation.ms * 5 / ITERATIONS * 1000).toFixed(2)}µs per check`);