Skip to content

Commit

Permalink
feat: audit object refcounts
Browse files Browse the repository at this point in the history
closes: #3445
  • Loading branch information
FUDCo committed Jul 13, 2021
1 parent 886b361 commit d7c9792
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 22 deletions.
2 changes: 1 addition & 1 deletion packages/SwingSet/test/test-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ async function simpleCall(t) {
const vatAdminRoot = ['ko20', adminVatID, 'o+0'];
t.deepEqual(data.kernelTable, [vatAdminRoot]);

// vat1:o+1 will map to ko21
// vat1:o+0 will map to ko21
controller.queueToVatRoot('vat1', 'foo', capdata('args'));
t.deepEqual(controller.dump().runQueue, [
{
Expand Down
1 change: 1 addition & 0 deletions packages/swingset-runner/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*.sss
*.jsonlines
*.naive
*.sqlite
slog
vslog
vlog
Expand Down
86 changes: 67 additions & 19 deletions packages/swingset-runner/src/auditstore.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { assert, details as X } from '@agoric/assert';
import { parseReachableAndVatSlot } from '@agoric/swingset-vat/src/kernel/state/reachable';
import { parseVatSlot } from '@agoric/swingset-vat/src/parseVatSlots';

/* eslint-disable no-use-before-define */
export function auditRefCounts(store) {
export function auditRefCounts(store, doDump, printPrefix) {
const refCounts = new Map();
const refSites = new Map();

Expand All @@ -12,6 +14,24 @@ export function auditRefCounts(store) {
const val = store.get(key);
incRefCount(store.get(`${v}.c.${val}`), `clist.${v}`);
}
for (const key of store.getKeys(`${v}.c.ko`, `${v}.c.ko~`)) {
const val = store.get(key);
const { isReachable, vatSlot } = parseReachableAndVatSlot(val);
const { allocatedByVat } = parseVatSlot(vatSlot);
const tag = isReachable ? 'R' : '_';
if (!allocatedByVat) {
incRefCount(
store.get(`${v}.c.${vatSlot}`),
`clist.${v}.${tag}`,
!isReachable,
);
}
}
}

const pinnedObjects = commaSplit(store.get('pinnedObjects'));
for (const pinned of pinnedObjects) {
incRefCount(pinned, 'pin');
}

const runQueue = JSON.parse(store.get('runQueue'));
Expand All @@ -37,10 +57,7 @@ export function auditRefCounts(store) {
}
break;
}
case 'fulfilledToPresence':
incRefCount(store.get(`${kpid}.slot`), `${kpid}.slot`);
break;
case 'fulfilledToData':
case 'fulfilled':
case 'rejected': {
let slotNum = 0;
for (const slot of commaSplit(store.get(`${kpid}.data.slots`))) {
Expand All @@ -54,14 +71,38 @@ export function auditRefCounts(store) {
}
}

for (const kpid of refCounts.keys()) {
const stored = store.get(`${kpid}.refCount`);
const computed = `${refCounts.get(kpid)}`;
if (stored !== computed) {
for (const kref of Array.from(refCounts.keys()).sort()) {
let storedReach;
let storedRecog;
const [computedReach, computedRecog] = refCounts.get(kref);
if (kref.startsWith('kp')) {
storedReach = Number(store.get(`${kref}.refCount`));
storedRecog = storedReach;
} else {
[storedReach, storedRecog] = commaSplit(
store.get(`${kref}.refCount`),
).map(Number);
}
let dumpIt = false;
if (storedReach !== computedReach || storedRecog !== computedRecog) {
if (printPrefix) {
console.log('\nRefCount audit failures:');
printPrefix = false;
}
console.log(
`refCount mismatch ${kpid} stored=${stored} computed=${computed}`,
`refCount mismatch ${kref} stored=${storedReach},${storedRecog} computed=${computedReach},${computedRecog}`,
);
for (const site of refSites.get(kpid)) {
dumpIt = true;
} else if (doDump) {
if (printPrefix) {
console.log('\nRefCount dump:');
printPrefix = false;
}
console.log(`refCount ${kref} ${storedReach},${storedRecog}`);
dumpIt = true;
}
if (dumpIt) {
for (const site of refSites.get(kref)) {
console.log(` ${site}`);
}
}
Expand All @@ -83,17 +124,24 @@ export function auditRefCounts(store) {
}
}

function incRefCount(kpid, site) {
if (kpid && kpid.startsWith('kp')) {
let refCount = refCounts.get(kpid);
function incRefCount(kref, site, reachableOnly) {
if (kref && (kref.startsWith('kp') || kref.startsWith('ko'))) {
let refCount = refCounts.get(kref);
if (refCount) {
refCount += 1;
refSites.get(kpid).push(site);
refCount[1] += 1;
if (!reachableOnly) {
refCount[0] += 1;
}
refSites.get(kref).push(site);
} else {
refCount = 1;
refSites.set(kpid, [site]);
if (reachableOnly) {
refCount = [0, 1];
} else {
refCount = [1, 1];
}
refSites.set(kref, [site]);
}
refCounts.set(kpid, refCount);
refCounts.set(kref, refCount);
}
}

Expand Down
14 changes: 13 additions & 1 deletion packages/swingset-runner/src/dumpstore.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function dumpStore(swingStore, outfile, rawMode) {

popt('crankNumber');
popt('kernel.defaultManagerType');
popt('pinnedObjects');
popt('vatAdminRootKref');
popt('gcActions');
popt('kernelStats');
Expand Down Expand Up @@ -77,15 +78,26 @@ export function dumpStore(swingStore, outfile, rawMode) {
pgroup('kd');
gap();

let starting = true;
for (const [dn, d] of devs.entries()) {
if (starting) {
starting = false;
} else {
gap();
}
p(`// device ${d} (${dn})`);
popt(`${d}.options`);
poptBig('bundle', `${d}.source`);
popt(`${d}.o.nextID`);
popt(`${d}.deviceState`);
for (const key of groupKeys(`${d}.c.kd`)) {
const val = popt(key);
popt(`${d}.c.${val}`);
}
for (const key of groupKeys(`${d}.c.ko`)) {
const val = popt(key);
popt(`${d}.c.${val}`);
}
}
gap();

Expand Down Expand Up @@ -121,7 +133,7 @@ export function dumpStore(swingStore, outfile, rawMode) {
gap();

const vatInfo = [];
let starting = true;
starting = true;
for (const [vn, v] of vats.entries()) {
if (starting) {
starting = false;
Expand Down
10 changes: 9 additions & 1 deletion packages/swingset-runner/src/kerneldump.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ FLAGS may be:
--raw - dump the kernel state database as key/value pairs,
alphabetically without annotation
--refcounts - audit kernel promise reference counts
--refdump - dump reference count analysis
--auditonly - only audit, don't dump
--help - print this helpful usage information
--out PATH - output dump to PATH ("-" indicates stdout, the default)
Expand Down Expand Up @@ -56,6 +57,7 @@ export function main() {
let refCounts = false;
let justStats = false;
let doDump = true;
let refDump = false;
let outfile;
while (argv[0] && argv[0].startsWith('-')) {
const flag = argv.shift();
Expand All @@ -68,7 +70,13 @@ export function main() {
refCounts = true;
break;
case '--auditonly':
refCounts = true;
doDump = false;
break;
case '--refdump':
refCounts = true;
doDump = false;
refDump = true;
break;
case '--stats':
justStats = true;
Expand Down Expand Up @@ -116,7 +124,7 @@ export function main() {
dumpStore(swingStore, outfile, rawMode);
}
if (refCounts) {
auditRefCounts(swingStore.kvStore);
auditRefCounts(swingStore.kvStore, refDump, doDump);
}
}
}

0 comments on commit d7c9792

Please sign in to comment.