diff --git a/packages/SwingSet/src/controller.js b/packages/SwingSet/src/controller.js index b178950ce08..6f01ec534b1 100644 --- a/packages/SwingSet/src/controller.js +++ b/packages/SwingSet/src/controller.js @@ -361,6 +361,10 @@ export async function buildVatController(config, withSES = true, argv = []) { return kernel.step(); }, + getStats() { + return kernel.getStats(); + }, + // these are for tests vatNameToID(vatName) { diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 257e29b654b..b3bf57837b3 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -420,6 +420,7 @@ export default function buildKernel(kernelEndowments) { throw Error(`unable to process message.type ${message.type}`); } kernelKeeper.purgeDeadKernelPromises(); + kernelKeeper.saveStats(); commitCrank(); kernelKeeper.incrementCrankNumber(); } finally { @@ -748,9 +749,11 @@ export default function buildKernel(kernelEndowments) { `replayTranscript added run-queue entries, wasn't supposed to`, ); } + kernelKeeper.loadStats(); } kernelKeeper.setInitialized(); + kernelKeeper.saveStats(); commitCrank(); // commit "crank 0" kernelKeeper.incrementCrankNumber(); } @@ -796,6 +799,9 @@ export default function buildKernel(kernelEndowments) { ephemeral.log.push(`${str}`); }, + getStats() { + return kernelKeeper.getStats(); + }, dump() { // note: dump().log is not deterministic, since log() does not go // through the syscall interface (and we replay transcripts one vat at diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index 8885ca8023d..59d58a2e6e2 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -101,6 +101,68 @@ export default function makeKernelKeeper(storage) { return storage.get(key); } + // These are the defined kernel statistics counters. For any counter 'foo' + // that is defined here, if there is also a defined counter named 'fooMax', + // the stats collection machinery will automatically track the latter as a + // high-water mark for the former. + let kernelStats = { + kernelObjects: 0, + kernelDevices: 0, + kernelPromises: 0, + kernelPromisesMax: 0, + kpUnresolved: 0, + kpUnresolvedMax: 0, + kpFulfilledToPresence: 0, + kpFulfilledToPresenceMax: 0, + kpFulfilledToData: 0, + kpFulfilledToDataMax: 0, + kpRejected: 0, + kpRejectedMax: 0, + runQueueLength: 0, + runQueueLengthMax: 0, + syscalls: 0, + syscallSend: 0, + syscallSubscribe: 0, + syscallFulfillToData: 0, + syscallFulfillToPresence: 0, + syscallReject: 0, + syscallCallNow: 0, + dispatches: 0, + dispatchDeliver: 0, + dispatchNotifyFulfillToData: 0, + dispatchNotifyFulfillToPresence: 0, + dispatchReject: 0, + clistEntries: 0, + clistEntriesMax: 0, + }; + + function incStat(stat) { + kernelStats[stat] += 1; + const maxStat = `${stat}Max`; + if ( + kernelStats[maxStat] !== undefined && + kernelStats[stat] > kernelStats[maxStat] + ) { + kernelStats[maxStat] = kernelStats[stat]; + } + } + + function decStat(stat) { + kernelStats[stat] -= 1; + } + + function saveStats() { + storage.set('kernelStats', JSON.stringify(kernelStats)); + } + + function loadStats() { + kernelStats = JSON.parse(getRequired('kernelStats')); + } + + function getStats() { + return { ...kernelStats }; + } + const ephemeral = harden({ vatKeepers: new Map(), // vatID -> vatKeeper deviceKeepers: new Map(), // deviceID -> deviceKeeper @@ -142,6 +204,7 @@ export default function makeKernelKeeper(storage) { storage.set('ko.nextID', `${id + 1}`); const s = makeKernelSlot('object', id); storage.set(`${s}.owner`, ownerID); + incStat('kernelObjects'); return s; } @@ -159,6 +222,7 @@ export default function makeKernelKeeper(storage) { storage.set('kd.nextID', `${id + 1}`); const s = makeKernelSlot('device', id); storage.set(`${s}.owner`, deviceID); + incStat('kernelDevices'); return s; } @@ -181,6 +245,8 @@ export default function makeKernelKeeper(storage) { storage.set(`${s}.queue.nextID`, `0`); storage.set(`${s}.refCount`, `0`); // queue is empty, so no state[kp$NN.queue.$NN] keys yet + incStat('kernelPromises'); + incStat('kpUnresolved'); return s; } @@ -252,6 +318,8 @@ export default function makeKernelKeeper(storage) { function fulfillKernelPromiseToPresence(kernelSlot, targetSlot) { insistKernelType('promise', kernelSlot); deleteKernelPromiseState(kernelSlot); + decStat('kpUnresolved'); + incStat('kpFulfilledToPresence'); storage.set(`${kernelSlot}.state`, 'fulfilledToPresence'); storage.set(`${kernelSlot}.slot`, targetSlot); } @@ -260,6 +328,8 @@ export default function makeKernelKeeper(storage) { insistKernelType('promise', kernelSlot); insistCapData(capdata); deleteKernelPromiseState(kernelSlot); + decStat('kpUnresolved'); + incStat('kpFulfilledToData'); storage.set(`${kernelSlot}.state`, 'fulfilledToData'); storage.set(`${kernelSlot}.data.body`, capdata.body); storage.set(`${kernelSlot}.data.slots`, capdata.slots.join(',')); @@ -269,6 +339,8 @@ export default function makeKernelKeeper(storage) { insistKernelType('promise', kernelSlot); insistCapData(capdata); deleteKernelPromiseState(kernelSlot); + decStat('kpUnresolved'); + incStat('kpRejected'); storage.set(`${kernelSlot}.state`, 'rejected'); storage.set(`${kernelSlot}.data.body`, capdata.body); storage.set(`${kernelSlot}.data.slots`, capdata.slots.join(',')); @@ -320,6 +392,7 @@ export default function makeKernelKeeper(storage) { const queue = JSON.parse(getRequired('runQueue')); queue.push(msg); storage.set('runQueue', JSON.stringify(queue)); + incStat('runQueueLength'); } function isRunQueueEmpty() { @@ -336,6 +409,7 @@ export default function makeKernelKeeper(storage) { const queue = JSON.parse(getRequired('runQueue')); const msg = queue.shift(); storage.set('runQueue', JSON.stringify(queue)); + decStat('runQueueLength'); return msg; } @@ -446,6 +520,8 @@ export default function makeKernelKeeper(storage) { addKernelPromise, incrementRefCount, decrementRefCount, + incStat, + decStat, ); ephemeral.vatKeepers.set(vatID, vk); } @@ -602,6 +678,12 @@ export default function makeKernelKeeper(storage) { incrementCrankNumber, purgeDeadKernelPromises, + incStat, + decStat, + saveStats, + loadStats, + getStats, + ownerOfKernelObject, ownerOfKernelDevice, diff --git a/packages/SwingSet/src/kernel/state/vatKeeper.js b/packages/SwingSet/src/kernel/state/vatKeeper.js index e641b22b9cc..7e9145b385d 100644 --- a/packages/SwingSet/src/kernel/state/vatKeeper.js +++ b/packages/SwingSet/src/kernel/state/vatKeeper.js @@ -52,6 +52,8 @@ export function makeVatKeeper( addKernelPromise, incrementRefCount, decrementRefCount, + incStat, + decStat, ) { insistVatID(vatID); @@ -85,6 +87,7 @@ export function makeVatKeeper( } incrementRefCount(kernelSlot, `${vatID}|vk|clist`); const kernelKey = `${vatID}.c.${kernelSlot}`; + incStat('clistEntries'); storage.set(kernelKey, vatSlot); storage.set(vatKey, kernelSlot); kdebug(`Add mapping v->k ${kernelKey}<=>${vatKey}`); @@ -132,6 +135,7 @@ export function makeVatKeeper( const vatSlot = makeVatSlot(type, false, id); const vatKey = `${vatID}.c.${vatSlot}`; + incStat('clistEntries'); storage.set(vatKey, kernelSlot); storage.set(kernelKey, vatSlot); kdebug(`Add mapping k->v ${kernelKey}<=>${vatKey}`); @@ -154,6 +158,7 @@ export function makeVatKeeper( kdebug(`Delete mapping ${kernelKey}<=>${vatKey}`); if (storage.has(kernelKey)) { decrementRefCount(kernelSlot, `${vatID}|del|clist`); + decStat('clistEntries'); storage.delete(kernelKey); storage.delete(vatKey); } diff --git a/packages/SwingSet/src/kernel/vatManager.js b/packages/SwingSet/src/kernel/vatManager.js index bfe9718d9c9..7fd31333b07 100644 --- a/packages/SwingSet/src/kernel/vatManager.js +++ b/packages/SwingSet/src/kernel/vatManager.js @@ -128,6 +128,8 @@ export default function makeVatManager( function doSend(targetSlot, method, args, resultSlot) { assert(`${targetSlot}` === targetSlot, 'non-string targetSlot'); insistCapData(args); + kernelKeeper.incStat('syscalls'); + kernelKeeper.incStat('syscallSend'); // TODO: disable send-to-self for now, qv issue #43 const target = mapVatSlotToKernelSlot(targetSlot); const argList = legibilizeMessageArgs(args).join(', '); @@ -172,11 +174,15 @@ export default function makeVatManager( if (!kernelKeeper.hasKernelPromise(id)) { throw new Error(`unknown kernelPromise id '${id}'`); } + kernelKeeper.incStat('syscalls'); + kernelKeeper.incStat('syscallSubscribe'); syscallManager.subscribe(vatID, id); } function doFulfillToPresence(promiseID, slot) { insistVatType('promise', promiseID); + kernelKeeper.incStat('syscalls'); + kernelKeeper.incStat('syscallFulfillToPresence'); const kpid = mapVatSlotToKernelSlot(promiseID); const targetSlot = mapVatSlotToKernelSlot(slot); kdebug( @@ -189,6 +195,8 @@ export default function makeVatManager( function doFulfillToData(promiseID, data) { insistVatType('promise', promiseID); insistCapData(data); + kernelKeeper.incStat('syscalls'); + kernelKeeper.incStat('syscallFulfillToData'); const kpid = mapVatSlotToKernelSlot(promiseID); const kernelSlots = data.slots.map(slot => mapVatSlotToKernelSlot(slot)); const kernelData = harden({ ...data, slots: kernelSlots }); @@ -204,6 +212,8 @@ export default function makeVatManager( function doReject(promiseID, data) { insistVatType('promise', promiseID); insistCapData(data); + kernelKeeper.incStat('syscalls'); + kernelKeeper.incStat('syscallReject'); const kpid = mapVatSlotToKernelSlot(promiseID); const kernelSlots = data.slots.map(slot => mapVatSlotToKernelSlot(slot)); const kernelData = harden({ ...data, slots: kernelSlots }); @@ -223,6 +233,8 @@ export default function makeVatManager( if (type !== 'device') { throw new Error(`doCallNow must target a device, not ${dev}`); } + kernelKeeper.incStat('syscalls'); + kernelKeeper.incStat('syscallCallNow'); const kernelSlots = args.slots.map(slot => mapVatSlotToKernelSlot(slot)); const kernelData = harden({ ...args, slots: kernelSlots }); // prettier-ignore @@ -371,6 +383,8 @@ export default function makeVatManager( insistVatType('promise', resultSlot); kernelKeeper.setDecider(msg.result, vatID); } + kernelKeeper.incStat('dispatches'); + kernelKeeper.incStat('dispatchDeliver'); await doProcess( [ 'deliver', @@ -389,6 +403,8 @@ export default function makeVatManager( const vpid = mapKernelSlotToVatSlot(kpid); const slot = mapKernelSlotToVatSlot(kp.slot); vatKeeper.deleteCListEntry(kpid, vpid); + kernelKeeper.incStat('dispatches'); + kernelKeeper.incStat('dispatchNotifyFulfillToPresence'); await doProcess( ['notifyFulfillToPresence', vpid, slot], `vat[${vatID}].promise[${vpid}] fulfillToPresence failed`, @@ -402,6 +418,8 @@ export default function makeVatManager( slots: kp.data.slots.map(slot => mapKernelSlotToVatSlot(slot)), }); deleteCListEntryIfEasy(kpid, vpid, kp.data); + kernelKeeper.incStat('dispatches'); + kernelKeeper.incStat('dispatchNotifyFulfillToData'); await doProcess( ['notifyFulfillToData', vpid, vatData], `vat[${vatID}].promise[${vpid}] fulfillToData failed`, @@ -413,6 +431,8 @@ export default function makeVatManager( slots: kp.data.slots.map(slot => mapKernelSlotToVatSlot(slot)), }); deleteCListEntryIfEasy(kpid, vpid, kp.data); + kernelKeeper.incStat('dispatches'); + kernelKeeper.incStat('dispatchReject'); await doProcess( ['notifyReject', vpid, vatData], `vat[${vatID}].promise[${vpid}] reject failed`,