208 lines
6.8 KiB
JavaScript
208 lines
6.8 KiB
JavaScript
|
|
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}`);
|