import { Arbiter } from '../src/core/Arbiter.js'; // Create scenario matching owa_comparator_nested const arbiter = new Arbiter(); const SIZE = 5000; // Build nodes for (let i = 0; i < SIZE; i++) { arbiter.addNode(`user:${i}`, 'user'); arbiter.addNode(`resource:${i}`, 'resource'); } // Build relations (same as owa-comparator-micro-bench) for (let i = 0; i < SIZE; i++) { const isSafe = i % 2 === 0; const score = isSafe ? 20 : 80; const bonus = isSafe ? 5 : 15; const noise = isSafe ? 0 : 10; arbiter.addRelation(`user:${i}`, 'risk_score', `resource:${i}`, 1.0, { value: score }); arbiter.addRelation(`user:${i}`, 'risk_bonus', `resource:${i}`, 1.0, { value: bonus }); arbiter.addRelation(`user:${i}`, 'risk_noise', `resource:${i}`, 1.0, { value: noise }); arbiter.addRelation(`resource:${i}`, 'risk_limit', `resource:${i}`, 1.0, { value: 40 }); arbiter.addRelation(`resource:${i}`, 'risk_cap', `resource:${i}`, 1.0, { value: 45 }); } // Set relation configs arbiter.setRelationConfig('risk_score', { type: 'direct' }); arbiter.setRelationConfig('risk_bonus', { type: 'direct' }); arbiter.setRelationConfig('risk_noise', { type: 'direct' }); arbiter.setRelationConfig('risk_limit', { type: 'direct' }); arbiter.setRelationConfig('risk_cap', { 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 }); } // Profile with timing at each step const ITERATIONS = 1000; let totalMs = 0; // Detailed timing breakdown const timings = { total: 0, nodeIdLookup: 0, configGet: 0, visitedCheck: 0, ruleEval: 0 }; const start = process.hrtime.bigint(); for (let i = 0; i < ITERATIONS; i++) { const idx = i % SIZE; arbiter.check(`user:${idx}`, 'risk_ok_owa', `resource:${idx}`, { fastPath: true }); } totalMs = Number(process.hrtime.bigint() - start) / 1e6; const qps = Math.round((ITERATIONS / totalMs) * 1000); console.log(`Total: ${totalMs.toFixed(2)}ms for ${ITERATIONS} checks = ${qps} QPS`); console.log(`Per check: ${(totalMs / ITERATIONS).toFixed(4)}ms`); // Now let's profile object allocations console.log('\n--- Checking allocation patterns ---'); // Check visited set behavior const visited = new Set(); const visitKey1 = { userKey: 'user:0', relation: 'test', objectKey: 'resource:0' }; const visitKey2 = { userKey: 'user:0', relation: 'test', objectKey: 'resource:0' }; visited.add(visitKey1); console.log(`Same keys, different objects in Set: ${visited.has(visitKey2)}`); // false - reference equality // Count how many allocations happen per check console.log('\n--- Checking comparison: direct vs comparator ---'); // Profile direct check const directStart = process.hrtime.bigint(); for (let i = 0; i < ITERATIONS; i++) { const idx = i % SIZE; arbiter.check(`user:${idx}`, 'risk_score', `resource:${idx}`, { fastPath: true }); } const directMs = Number(process.hrtime.bigint() - directStart) / 1e6; console.log(`Direct: ${directMs.toFixed(2)}ms = ${Math.round((ITERATIONS / directMs) * 1000)} QPS`); // Profile union eval only const unionRule = { 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 unionStart = process.hrtime.bigint(); for (let i = 0; i < ITERATIONS; i++) { const idx = i % SIZE; const userId = arbiter.nodeIdByKey.get(`user:${idx}`); const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); arbiter.authChecker.ruleEvaluator.evaluateRule( userId, `user:${idx}`, objectId, `resource:${idx}`, unionRule, new Set(), 'risk_ok_owa', { fastPath: true } ); } const unionMs = Number(process.hrtime.bigint() - unionStart) / 1e6; console.log(`Union eval: ${unionMs.toFixed(2)}ms = ${Math.round((ITERATIONS / unionMs) * 1000)} QPS`); // What's the overhead of authChecker.check vs direct ruleEvaluator call? const checkStart = process.hrtime.bigint(); for (let i = 0; i < ITERATIONS; i++) { const idx = i % SIZE; arbiter.authChecker.check(`user:${idx}`, 'risk_ok_owa', `resource:${idx}`, { fastPath: true }); } const checkMs = Number(process.hrtime.bigint() - checkStart) / 1e6; console.log(`authChecker.check: ${checkMs.toFixed(2)}ms = ${Math.round((ITERATIONS / checkMs) * 1000)} QPS`); // Direct to evaluateRule for relational_comparator const comparatorRule = arbiter.relationConfigs.get('risk_ok_owa'); const comparatorStart = process.hrtime.bigint(); for (let i = 0; i < ITERATIONS; i++) { const idx = i % SIZE; const userId = arbiter.nodeIdByKey.get(`user:${idx}`); const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`); arbiter.authChecker.ruleEvaluator.evaluateRule( userId, `user:${idx}`, objectId, `resource:${idx}`, comparatorRule, new Set(), 'risk_ok_owa', { fastPath: true } ); } const comparatorMs = Number(process.hrtime.bigint() - comparatorStart) / 1e6; console.log(`ruleEvaluator.evaluateRule (comparator): ${comparatorMs.toFixed(2)}ms = ${Math.round((ITERATIONS / comparatorMs) * 1000)} QPS`); console.log('\n--- Summary ---'); console.log(`Direct check baseline: ${Math.round((ITERATIONS / directMs) * 1000)} QPS`); console.log(`Union eval: ${Math.round((ITERATIONS / unionMs) * 1000)} QPS (${(unionMs/directMs).toFixed(1)}x slower than direct)`); console.log(`Full comparator via ruleEvaluator: ${Math.round((ITERATIONS / comparatorMs) * 1000)} QPS (${(comparatorMs/directMs).toFixed(1)}x slower than direct)`); console.log(`Full comparator via authChecker: ${Math.round((ITERATIONS / checkMs) * 1000)} QPS (${(checkMs/directMs).toFixed(1)}x slower than direct)`);