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 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 buildComparatorScenario(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); } ['risk_score', 'risk_bonus', 'risk_noise', 'risk_limit', 'risk_cap'].forEach(relation => { arbiter.setRelationConfig(relation, { 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 buildFeatureFlagScenario(arbiter, size) { const flagCount = Math.max(1, Math.floor(size / 10)); buildUsers(arbiter, size, 'user'); buildUsers(arbiter, size, 'resource'); buildUsers(arbiter, flagCount, 'flag'); for (let i = 0; i < size; i++) { const flagId = i % flagCount; arbiter.addRelation(`user:${i}`, 'has_flag', `flag:${flagId}`); if (i % 2 === 0) { arbiter.addRelation(`resource:${i}`, 'feature_flag', `flag:${flagId}`); } } arbiter.setRelationConfig('has_flag', { type: 'direct' }); arbiter.setRelationConfig('feature_flag', { type: 'direct' }); arbiter.setRelationConfig('can_feature', { type: 'tuple_to_userset', tuplesetRelation: 'feature_flag', tuplesetDirection: 'out', computedRelation: 'has_flag' }); } function buildComparatorQueries(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 buildFeatureFlagQueries(size, rng, samples) { const queries = []; const half = Math.floor(samples / 2); const flagCount = Math.max(1, Math.floor(size / 10)); for (let i = 0; i < half; i++) { const userId = randInt(rng, Math.floor(size / 2)) * 2; queries.push({ userKey: `user:${userId}`, objectKey: `resource:${userId}`, flagId: userId % flagCount }); } for (let i = 0; i < samples - half; i++) { const userId = randInt(rng, Math.floor(size / 2)) * 2 + 1; queries.push({ userKey: `user:${userId}`, objectKey: `resource:${userId}`, flagId: userId % flagCount }); } return queries; } const args = parseArgs(process.argv); const scenario = String(args.get('scenario') || 'comparator'); const size = Number(args.get('size') || 5000); const samples = Number(args.get('samples') || 2000); const iterations = Number(args.get('iterations') || 20000); const seed = Number(args.get('seed') || 42); const includeMeta = !args.has('no-meta'); const collectValues = args.has('values-agg') ? true : !args.has('no-values'); const arbiter = new Arbiter(); const rng = createRng(seed); const options = { fastPath: true, includeMeta, collectValues }; let queries = []; if (scenario === 'feature-flag') { buildFeatureFlagScenario(arbiter, size); queries = buildFeatureFlagQueries(size, rng, samples); } else { const { trueIds, falseIds } = buildComparatorScenario(arbiter, size); queries = buildComparatorQueries(size, rng, samples, trueIds, falseIds); } for (let i = 0; i < Math.min(200, samples); i++) { const q = queries[i]; if (scenario === 'feature-flag') { arbiter.check(q.userKey, 'can_feature', q.objectKey, options); } else { arbiter.check(q.userKey, 'risk_ok_owa', q.objectKey, options); } } const start = process.hrtime.bigint(); for (let i = 0; i < iterations; i++) { const q = queries[randInt(rng, queries.length)]; if (scenario === 'feature-flag') { arbiter.check(q.userKey, 'can_feature', q.objectKey, options); } else { arbiter.check(q.userKey, 'risk_ok_owa', q.objectKey, options); } } const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6; const qps = elapsedMs > 0 ? Math.round((iterations / elapsedMs) * 1000) : 0; console.log('profile_targeted_bench'); console.log(`scenario=${scenario}`); console.log(`size=${size}`); console.log(`samples=${samples}`); console.log(`iterations=${iterations}`); console.log(`meta=${includeMeta ? 'on' : 'off'}`); console.log(`values=${collectValues ? 'on' : 'off'}`); console.log(`elapsed_ms=${Math.round(elapsedMs)}`); console.log(`qps=${qps}`);