Skip to content

Commit

Permalink
feat(swingset): add Meters to kernel state
Browse files Browse the repository at this point in the history
This introduces the "meterID" and the "meter record": a pair of
Nats (`remaining` and `threshold`). kernelKeeper functions are added to
create and manipulate them, and test-state.js is enhanced to exercise these.

refs #3308
  • Loading branch information
warner committed Jul 22, 2021
1 parent 278d79c commit 7c0c0dd
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 0 deletions.
71 changes: 71 additions & 0 deletions packages/SwingSet/src/kernel/state/kernelKeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const enableKernelGC = true;
// device.names = JSON([names..])
// device.name.$NAME = $deviceID = d$NN
// device.nextID = $NN
// meter.nextID = $NN // used to make m$NN

// kernelBundle = JSON(bundle)
// bundle.$NAME = JSON(bundle)
Expand All @@ -54,6 +55,9 @@ const enableKernelGC = true;
// v$NN.vs.$key = string
// v$NN.lastSnapshot = JSON({ snapshotID, startPos })

// m$NN.remaining = $NN // non-negative remaining capacity (in computrons)
// m$NN.threshold = $NN // notify when .remaining first drops below this

// d$NN.o.nextID = $NN
// d$NN.c.$kernelSlot = $deviceSlot = o-$NN/d+$NN/d-$NN
// d$NN.c.$deviceSlot = $kernelSlot = ko$NN/kd$NN
Expand Down Expand Up @@ -90,6 +94,12 @@ export function commaSplit(s) {
return s.split(',');
}

function insistMeterID(m) {
assert.typeof(m, 'string');
assert.equal(m[0], 'm');
Nat(BigInt(m.slice(1)));
}

// we use different starting index values for the various vNN/koNN/kdNN/kpNN
// slots, to reduce confusing overlap when looking at debug messages (e.g.
// seeing both kp1 and ko1, which are completely unrelated despite having the
Expand All @@ -106,6 +116,7 @@ const FIRST_OBJECT_ID = 20n;
const FIRST_DEVNODE_ID = 30n;
const FIRST_PROMISE_ID = 40n;
const FIRST_CRANK_NUMBER = 0n;
const FIRST_METER_ID = 1n;

/**
* @param {KVStorePlus} kvStore
Expand Down Expand Up @@ -228,6 +239,7 @@ export default function makeKernelKeeper(
kvStore.set('ko.nextID', `${FIRST_OBJECT_ID}`);
kvStore.set('kd.nextID', `${FIRST_DEVNODE_ID}`);
kvStore.set('kp.nextID', `${FIRST_PROMISE_ID}`);
kvStore.set('meter.nextID', `${FIRST_METER_ID}`);
kvStore.set('gcActions', '[]');
kvStore.set('runQueue', JSON.stringify([]));
kvStore.set('crankNumber', `${FIRST_CRANK_NUMBER}`);
Expand Down Expand Up @@ -706,6 +718,58 @@ export default function makeKernelKeeper(
return msg;
}

function allocateMeter(remaining, threshold) {
const nextID = Nat(BigInt(getRequired('meter.nextID')));
kvStore.set('meter.nextID', `${nextID + 1n}`);
const meterID = `m${nextID}`;
kvStore.set(`${meterID}.remaining`, `${Nat(remaining)}`);
kvStore.set(`${meterID}.threshold`, `${Nat(threshold)}`);
return meterID;
}

function addMeterRemaining(meterID, delta) {
insistMeterID(meterID);
const remaining = parseInt(getRequired(`${meterID}.remaining`), 10);
kvStore.set(`${meterID}.remaining`, `${Nat(remaining) + Nat(delta)}`);
}

function setMeterThreshold(meterID, threshold) {
insistMeterID(meterID);
kvStore.set(`${meterID}.threshold`, `${Nat(threshold)}`);
}

function getMeter(meterID) {
insistMeterID(meterID);
const remaining = parseInt(getRequired(`${meterID}.remaining`), 10);
const threshold = parseInt(getRequired(`${meterID}.threshold`), 10);
return harden({ remaining, threshold });
}

function deductMeter(meterID, spent) {
insistMeterID(meterID);
Nat(spent);
const oldRemaining = parseInt(getRequired(`${meterID}.remaining`), 10);
const threshold = parseInt(getRequired(`${meterID}.threshold`), 10);
let underflow = false;
let notify = false;
let remaining = oldRemaining - spent;
if (remaining < 0) {
underflow = true;
remaining = 0;
}
if (remaining < threshold && oldRemaining >= threshold) {
notify = true;
}
kvStore.set(`${meterID}.remaining`, `${Nat(remaining)}`);
return harden({ underflow, notify });
}

function deleteMeter(meterID) {
insistMeterID(meterID);
kvStore.delete(`${meterID}.remaining`);
kvStore.delete(`${meterID}.threshold`);
}

function hasVatWithName(name) {
return kvStore.has(`vat.name.${name}`);
}
Expand Down Expand Up @@ -1205,6 +1269,13 @@ export default function makeKernelKeeper(
getRunQueueLength,
getNextMsg,

allocateMeter,
addMeterRemaining,
setMeterThreshold,
getMeter,
deductMeter,
deleteMeter,

hasVatWithName,
getVatIDForName,
allocateVatIDForNameIfNeeded,
Expand Down
38 changes: 38 additions & 0 deletions packages/SwingSet/test/test-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ test('kernel state', async t => {
['kd.nextID', '30'],
['kp.nextID', '40'],
['kernel.defaultManagerType', 'local'],
['meter.nextID', '1'],
]);
});

Expand Down Expand Up @@ -321,6 +322,7 @@ test('kernelKeeper vat names', async t => {
['vat.name.vatname5', 'v1'],
['vat.name.Frank', 'v2'],
['kernel.defaultManagerType', 'local'],
['meter.nextID', '1'],
]);
t.deepEqual(k.getStaticVats(), [
['Frank', 'v2'],
Expand Down Expand Up @@ -369,6 +371,7 @@ test('kernelKeeper device names', async t => {
['device.name.devicename5', 'd7'],
['device.name.Frank', 'd8'],
['kernel.defaultManagerType', 'local'],
['meter.nextID', '1'],
]);
t.deepEqual(k.getDevices(), [
['Frank', 'd8'],
Expand Down Expand Up @@ -550,6 +553,7 @@ test('kernelKeeper promises', async t => {
[`${ko}.owner`, 'v1'],
[`${ko}.refCount`, '1,1'],
['kernel.defaultManagerType', 'local'],
['meter.nextID', '1'],
]);
});

Expand Down Expand Up @@ -663,3 +667,37 @@ test('XS vatKeeper defaultManagerType', async t => {
k.createStartingKernelState('xs-worker');
t.is(k.getDefaultManagerType(), 'xs-worker');
});

test('meters', async t => {
const { kvStore, streamStore } = buildKeeperStorageInMemory();
const k = makeKernelKeeper(kvStore, streamStore);
k.createStartingKernelState('local');
const m1 = k.allocateMeter(100, 10);
const m2 = k.allocateMeter(200, 150);
t.not(m1, m2);
k.deleteMeter(m2);
t.deepEqual(k.getMeter(m1), { remaining: 100, threshold: 10 });
t.deepEqual(k.deductMeter(m1, 10), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 10), { underflow: false, notify: false });
t.deepEqual(k.getMeter(m1), { remaining: 80, threshold: 10 });
t.deepEqual(k.deductMeter(m1, 70), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 1), { underflow: false, notify: true });
t.deepEqual(k.getMeter(m1), { remaining: 9, threshold: 10 });
t.deepEqual(k.deductMeter(m1, 1), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 9), { underflow: true, notify: false });
t.deepEqual(k.getMeter(m1), { remaining: 0, threshold: 10 });
t.deepEqual(k.deductMeter(m1, 2), { underflow: true, notify: false });
t.deepEqual(k.getMeter(m1), { remaining: 0, threshold: 10 });
k.addMeterRemaining(m1, 50n);
t.deepEqual(k.getMeter(m1), { remaining: 50, threshold: 10 });
t.deepEqual(k.deductMeter(m1, 30), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 25), { underflow: true, notify: true });
t.deepEqual(k.getMeter(m1), { remaining: 0, threshold: 10 });

k.addMeterRemaining(m1, 50n);
k.setMeterThreshold(m1, 40);
t.deepEqual(k.getMeter(m1), { remaining: 50, threshold: 40 });
t.deepEqual(k.deductMeter(m1, 10), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 10), { underflow: false, notify: true });
t.deepEqual(k.getMeter(m1), { remaining: 30, threshold: 40 });
});

0 comments on commit 7c0c0dd

Please sign in to comment.