import { Arbiter } from '../src/core/Arbiter.js'; function parseArgs(argv) { const args = new Map(); for (let i = 2; i < argv.length; i++) { const value = argv[i]; if (!value.startsWith('--')) continue; const [key, inline] = value.slice(2).split('='); if (inline !== undefined) { args.set(key, inline); continue; } const next = argv[i + 1]; if (next && !next.startsWith('--')) { args.set(key, next); i++; } else { args.set(key, true); } } return args; } function parseSizes(value) { if (!value) return null; return value.split(',').map(item => Number(item.trim())).filter(Number.isFinite); } function createRng(seed) { let state = seed >>> 0; return () => { state = (1664525 * state + 1013904223) >>> 0; return state / 0x100000000; }; } function randInt(rng, max) { return Math.floor(rng() * max); } function buildUsers(arbiter, count, prefix) { for (let i = 0; i < count; i++) { arbiter.addNode(`${prefix}:${i}`, prefix); } } function buildScenario(arbiter, size) { const trueIds = []; const falseIds = []; buildUsers(arbiter, size, 'user'); buildUsers(arbiter, size, 'resource'); 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 }); if (isSafe) trueIds.push(i); else falseIds.push(i); } 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] } }); return { trueIds, falseIds }; } function buildQueries(size, rng, samples, trueIds, falseIds) { const queries = []; const half = Math.floor(samples / 2); for (let i = 0; i < half; i++) { const userId = trueIds.length ? trueIds[randInt(rng, trueIds.length)] : randInt(rng, size); queries.push({ userKey: `user:${userId}`, objectKey: `resource:${userId}` }); } for (let i = 0; i < samples - half; i++) { const userId = falseIds.length ? falseIds[randInt(rng, falseIds.length)] : randInt(rng, size); queries.push({ userKey: `user:${userId}`, objectKey: `resource:${userId}` }); } return queries; } function measure(label, iterations, fn) { const start = process.hrtime.bigint(); for (let i = 0; i < iterations; i++) { fn(); } const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6; return { label, elapsedMs, qps: elapsedMs > 0 ? Math.round((iterations / elapsedMs) * 1000) : 0 }; } function measureMedian(label, iterations, fn, warmups, runs) { for (let w = 0; w < warmups; w++) { for (let i = 0; i < iterations; i++) fn(); } const samples = []; for (let r = 0; r < runs; r++) { const result = measure(label, iterations, fn); samples.push(result.qps); } samples.sort((a, b) => a - b); const mid = Math.floor(samples.length / 2); const median = samples.length % 2 === 0 ? Math.round(((samples[mid - 1] + samples[mid]) / 2) * 100) / 100 : samples[mid]; return { label, elapsedMs: null, qps: median }; } const args = parseArgs(process.argv); const config = { sizes: parseSizes(args.get('sizes')) || [5000, 10000, 20000], samples: Number(args.get('samples') || 2000), seed: Number(args.get('seed') || 42), maxSeconds: Number(args.get('max-seconds') || 60), includeMeta: !args.has('no-meta'), collectValues: !args.has('no-values'), warmupRuns: Number(args.get('warmup-runs') || 2), medianRuns: Number(args.get('median-runs') || 5) }; const rng = createRng(config.seed); const startTime = Date.now(); console.log('owa_comparator_micro_bench'); console.log('size,samples,meta,values,metric,elapsed_ms,qps'); for (const size of config.sizes) { if ((Date.now() - startTime) / 1000 > config.maxSeconds) break; const arbiter = new Arbiter(); const { trueIds, falseIds } = buildScenario(arbiter, size); const queries = buildQueries(size, rng, config.samples, trueIds, falseIds); const comparatorRule = arbiter.authChecker.ruleEvaluator.ruleHandlers.relational_comparator.numericRule; const visited = new Set(); const baseOptions = { fastPath: true, includeMeta: config.includeMeta, collectValues: config.collectValues }; const valuesOptions = { fastPath: true, includeMeta: config.includeMeta, collectValues: true }; const leftUnionRule = { 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 rightUnionRule = { union: { rules: [ { type: 'direct', relation: 'risk_limit' }, { type: 'direct', relation: 'risk_cap' } ], aggregator: 'owa', owaWeights: [0.6, 0.4] } }; const fullComparator = measureMedian('comparator_full', queries.length, () => { const q = queries[randInt(rng, queries.length)]; arbiter.check(q.userKey, 'risk_ok_owa', q.objectKey, baseOptions); }, config.warmupRuns, config.medianRuns); const fullComparatorValues = measureMedian('comparator_values_agg', queries.length, () => { const q = queries[randInt(rng, queries.length)]; arbiter.check(q.userKey, 'risk_ok_owa', q.objectKey, valuesOptions); }, config.warmupRuns, config.medianRuns); const leftUnionEval = measureMedian('left_union_eval', queries.length, () => { const q = queries[randInt(rng, queries.length)]; const userId = arbiter.nodeIdByKey.get(q.userKey); const objectId = arbiter.nodeIdByKey.get(q.objectKey); arbiter.authChecker.ruleEvaluator.evaluateRule( userId, q.userKey, objectId, q.objectKey, leftUnionRule, visited, 'risk_ok_owa', baseOptions ); }, config.warmupRuns, config.medianRuns); const rightUnionEval = measureMedian('right_union_eval', queries.length, () => { const q = queries[randInt(rng, queries.length)]; const userId = arbiter.nodeIdByKey.get(q.userKey); const objectId = arbiter.nodeIdByKey.get(q.objectKey); arbiter.authChecker.ruleEvaluator.evaluateRule( objectId, q.objectKey, objectId, q.objectKey, rightUnionRule, visited, 'risk_ok_owa', baseOptions ); }, config.warmupRuns, config.medianRuns); const leftExtract = measureMedian('left_extract', queries.length, () => { const q = queries[randInt(rng, queries.length)]; const userId = arbiter.nodeIdByKey.get(q.userKey); const objectId = arbiter.nodeIdByKey.get(q.objectKey); const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule( userId, q.userKey, objectId, q.objectKey, leftUnionRule, visited, 'risk_ok_owa', baseOptions ); comparatorRule._extractValues( userId, q.userKey, objectId, q.objectKey, leftUnionRule, 'risk_score', ruleResult, 'auto', null, 24 * 60 * 60 * 1000, null, baseOptions ); }, config.warmupRuns, config.medianRuns); const rightExtract = measureMedian('right_extract', queries.length, () => { const q = queries[randInt(rng, queries.length)]; const userId = arbiter.nodeIdByKey.get(q.userKey); const objectId = arbiter.nodeIdByKey.get(q.objectKey); const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule( objectId, q.objectKey, objectId, q.objectKey, rightUnionRule, visited, 'risk_ok_owa', baseOptions ); comparatorRule._extractValues( objectId, q.objectKey, objectId, q.objectKey, rightUnionRule, 'risk_limit', ruleResult, 'object', null, 24 * 60 * 60 * 1000, null, baseOptions ); }, config.warmupRuns, config.medianRuns); const leftAggregate = measureMedian('left_aggregate', queries.length, () => { const q = queries[randInt(rng, queries.length)]; const userId = arbiter.nodeIdByKey.get(q.userKey); const objectId = arbiter.nodeIdByKey.get(q.objectKey); const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule( userId, q.userKey, objectId, q.objectKey, leftUnionRule, visited, 'risk_ok_owa', baseOptions ); const values = comparatorRule._extractValues( userId, q.userKey, objectId, q.objectKey, leftUnionRule, 'risk_score', ruleResult, 'auto', null, 24 * 60 * 60 * 1000, null, baseOptions ); comparatorRule._aggregateCrispValues(values, 'owa', [0.5, 0.3, 0.2], null); }, config.warmupRuns, config.medianRuns); const rightAggregate = measureMedian('right_aggregate', queries.length, () => { const q = queries[randInt(rng, queries.length)]; const objectId = arbiter.nodeIdByKey.get(q.objectKey); const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule( objectId, q.objectKey, objectId, q.objectKey, rightUnionRule, visited, 'risk_ok_owa', baseOptions ); const values = comparatorRule._extractValues( objectId, q.objectKey, objectId, q.objectKey, rightUnionRule, 'risk_limit', ruleResult, 'object', null, 24 * 60 * 60 * 1000, null, baseOptions ); comparatorRule._aggregateCrispValues(values, 'owa', [0.6, 0.4], null); }, config.warmupRuns, config.medianRuns); const compareOnly = measureMedian('compare_only', queries.length, () => { const q = queries[randInt(rng, queries.length)]; const userId = arbiter.nodeIdByKey.get(q.userKey); const objectId = arbiter.nodeIdByKey.get(q.objectKey); const leftValues = comparatorRule._extractValues( userId, q.userKey, objectId, q.objectKey, leftUnionRule, 'risk_score', { collectedValues: [] }, 'auto', null, 24 * 60 * 60 * 1000, null, baseOptions ); const rightValues = comparatorRule._extractValues( objectId, q.objectKey, objectId, q.objectKey, rightUnionRule, 'risk_limit', { collectedValues: [] }, 'object', null, 24 * 60 * 60 * 1000, null, baseOptions ); const leftAgg = comparatorRule._aggregateCrispValues(leftValues, 'owa', [0.5, 0.3, 0.2], null); const rightAgg = comparatorRule._aggregateCrispValues(rightValues, 'owa', [0.6, 0.4], null); comparatorRule._compareBlurredValues( { hasValue: !!leftAgg.interval, valueInterval: leftAgg.interval, operandPossibility: leftAgg.possibility, reliability: leftAgg.reliability }, { hasValue: !!rightAgg.interval, valueInterval: rightAgg.interval, operandPossibility: rightAgg.possibility, reliability: rightAgg.reliability }, '<=', 'deny', {}, {}, {} ); }, config.warmupRuns, config.medianRuns); for (const metric of [ fullComparator, fullComparatorValues, leftUnionEval, rightUnionEval, leftExtract, rightExtract, leftAggregate, rightAggregate, compareOnly ]) { console.log([ size, queries.length, config.includeMeta ? 'on' : 'off', config.collectValues ? 'on' : 'off', metric.label, metric.elapsedMs === null ? 'median' : Math.round(metric.elapsedMs), metric.qps ].join(',')); } }