Skip to content

Commit

Permalink
feat(swingset): add kernel-tracked meters
Browse files Browse the repository at this point in the history
Just the meter allocate/set/deduct/delete methods for now, no code to invoke
them yet.

refs #3308
  • Loading branch information
warner committed Jul 12, 2021
1 parent 467eb9f commit 73484fe
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 0 deletions.
63 changes: 63 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 @@ -53,6 +54,10 @@ const enableKernelGC = true;
// v$NN.t.endPosition = $NN
// v$NN.vs.$key = string
// v$NN.lastSnapshot = JSON({ snapshotID, startPos })
// v$NN.meter = m$NN

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

// d$NN.o.nextID = $NN
// d$NN.c.$kernelSlot = $deviceSlot = o-$NN/d+$NN/d-$NN
Expand Down Expand Up @@ -89,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 @@ -105,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 @@ -227,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 @@ -705,6 +718,51 @@ export default function makeKernelKeeper(
return msg;
}

function allocateMeter(remaining, notifyThreshold) {
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}.notifyThreshold`, `${Nat(notifyThreshold)}`);
return meterID;
}

function setMeter(meterID, values) {
insistMeterID(meterID);
const { remaining, notifyThreshold } = values;
if (remaining !== undefined) {
kvStore.set(`${meterID}.remaining`, `${Nat(remaining)}`);
}
if (notifyThreshold !== undefined) {
kvStore.set(`${meterID}.notifyThreshold`, `${Nat(notifyThreshold)}`);
}
}

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

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

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

allocateMeter,
setMeter,
deductMeter,
deleteMeter,

hasVatWithName,
getVatIDForName,
allocateVatIDForNameIfNeeded,
Expand Down
23 changes: 23 additions & 0 deletions packages/SwingSet/test/test-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,3 +663,26 @@ 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(100n, 10n);
const m2 = k.allocateMeter(200n, 150n);
t.not(m1, m2);
k.deleteMeter(m2);
t.deepEqual(k.deductMeter(m1, 10n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 10n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 70n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 1n), { underflow: false, notify: true });
t.deepEqual(k.deductMeter(m1, 1n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 9n), { underflow: true, notify: false });
k.setMeter(m1, { remaining: 50n });
t.deepEqual(k.deductMeter(m1, 30n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 25n), { underflow: true, notify: true });

k.setMeter(m1, { remaining: 50n, notifyThreshold: 40n });
t.deepEqual(k.deductMeter(m1, 10n), { underflow: false, notify: false });
t.deepEqual(k.deductMeter(m1, 10n), { underflow: false, notify: true });
});

0 comments on commit 73484fe

Please sign in to comment.