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 measure(label, iterations, fn) { const start = process.hrtime.bigint(); for (let i = 0; i < iterations; i++) { fn(i); } const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6; return { label, elapsedMs, qps: elapsedMs > 0 ? Math.round((iterations / elapsedMs) * 1000) : 0 }; } function buildNodes(arbiter, size, prefix) { for (let i = 0; i < size; i++) { arbiter.addNode(`${prefix}:${i}`, prefix); } } const args = parseArgs(process.argv); const config = { sizes: parseSizes(args.get('sizes')) || [10000, 40000, 80000], samples: Number(args.get('samples') || 200000), seed: Number(args.get('seed') || 42), maxSeconds: Number(args.get('max-seconds') || 60) }; const rng = createRng(config.seed); const startTime = Date.now(); console.log('edge_mutation_bench'); console.log('size,edges,metric,elapsed_ms,qps'); for (const size of config.sizes) { if ((Date.now() - startTime) / 1000 > config.maxSeconds) break; const arbiter = new Arbiter(); buildNodes(arbiter, size, 'user'); buildNodes(arbiter, size, 'resource'); arbiter.setRelationConfig('link', { type: 'direct' }); arbiter.setRelationConfig('link_add', { type: 'direct' }); const currentDst = new Array(size); for (let i = 0; i < size; i++) { arbiter.addRelation(`user:${i}`, 'link', `resource:${i}`); currentDst[i] = i; } const addCount = Math.min(config.samples, size); const addPairs = new Array(addCount); for (let i = 0; i < addCount; i++) { addPairs[i] = { src: i % size, dst: (i * 7) % size }; } const updateOps = new Array(config.samples); for (let i = 0; i < config.samples; i++) { const src = randInt(rng, size); let dst = randInt(rng, size); if (dst === currentDst[src]) { dst = (dst + 1) % size; } updateOps[i] = { src, dst }; } const addMetric = measure('add_relations', addCount, index => { const pair = addPairs[index]; arbiter.addRelation(`user:${pair.src}`, 'link_add', `resource:${pair.dst}`); }); const removeMetric = measure('remove_relations', addCount, index => { const pair = addPairs[index]; arbiter.removeRelation(`user:${pair.src}`, 'link_add', `resource:${pair.dst}`); }); const updateMetric = measure('update_relations', updateOps.length, index => { const op = updateOps[index]; const srcKey = `user:${op.src}`; const oldDst = currentDst[op.src]; arbiter.removeRelation(srcKey, 'link', `resource:${oldDst}`); arbiter.addRelation(srcKey, 'link', `resource:${op.dst}`); currentDst[op.src] = op.dst; }); const metrics = [ { metric: addMetric, edges: addCount }, { metric: removeMetric, edges: addCount }, { metric: updateMetric, edges: size } ]; for (const entry of metrics) { console.log([ size, entry.edges, entry.metric.label, Math.round(entry.metric.elapsedMs), entry.metric.qps ].join(',')); } }