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 buildOWAUnionScenario(arbiter, size) { buildUsers(arbiter, size, 'user'); buildUsers(arbiter, size, 'resource'); for (let i = 0; i < size; i++) { if (i % 3 === 0) { arbiter.addRelation(`user:${i}`, 'viewer', `resource:${i}`, 0.6); } if (i % 5 === 0) { arbiter.addRelation(`user:${i}`, 'owner', `resource:${i}`, 0.9); } } arbiter.setRelationConfig('viewer', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); arbiter.setRelationConfig('can_view_owa', { union: { rules: [ { type: 'direct', relation: 'viewer' }, { type: 'direct', relation: 'owner' } ], aggregator: 'owa', owaWeights: [0.7, 0.3] } }); } function buildQueries(size, rng, samples) { const queries = []; const half = Math.floor(samples / 2); for (let i = 0; i < half; i++) { const userId = randInt(rng, Math.floor(size / 2)) * 3; queries.push({ userKey: `user:${userId}`, objectKey: `resource:${userId}` }); } for (let i = 0; i < samples - half; i++) { const userId = randInt(rng, Math.floor(size / 2)) * 3 + 1; 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 }; } const args = parseArgs(process.argv); const config = { sizes: parseSizes(args.get('sizes')) || [5000, 10000, 20000, 40000], samples: Number(args.get('samples') || 2000), seed: Number(args.get('seed') || 42), maxSeconds: Number(args.get('max-seconds') || 60) }; const rng = createRng(config.seed); const startTime = Date.now(); console.log('owa_union_micro_bench'); console.log('size,samples,metric,elapsed_ms,qps'); for (const size of config.sizes) { if ((Date.now() - startTime) / 1000 > config.maxSeconds) break; const arbiter = new Arbiter(); buildOWAUnionScenario(arbiter, size); const queries = buildQueries(size, rng, config.samples); const directViewer = measure('direct_viewer', queries.length, () => { const q = queries[randInt(rng, queries.length)]; arbiter.authChecker.check(q.userKey, 'viewer', q.objectKey, { fastPath: true }); }); const directOwner = measure('direct_owner', queries.length, () => { const q = queries[randInt(rng, queries.length)]; arbiter.authChecker.check(q.userKey, 'owner', q.objectKey, { fastPath: true }); }); const owaUnion = measure('owa_union', queries.length, () => { const q = queries[randInt(rng, queries.length)]; arbiter.check(q.userKey, 'can_view_owa', q.objectKey, { fastPath: true }); }); for (const metric of [directViewer, directOwner, owaUnion]) { console.log([size, queries.length, metric.label, Math.round(metric.elapsedMs), metric.qps].join(',')); } }