Skip to content

feat(swingset-runner): Add cpu/heap profiling support #11400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
58 changes: 58 additions & 0 deletions packages/swingset-runner/README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not render well; the line breaks are all collapsed.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
Command line:
runner [FLAGS...] CMD [{BASEDIR|--} [ARGS...]]

FLAGS may be:
--resume - resume execution using existing saved state
--initonly - initialize the swingset but exit without running it
--sqlite - runs using Sqlite3 as the data store (default)
--memdb - runs using the non-persistent in-memory data store
--usexs - run vats using the XS engine
--usebundlecache - cache bundles created by swingset loader
--dbdir DIR - specify where the data store should go (default BASEDIR)
--blockmode - run in block mode (checkpoint every BLOCKSIZE blocks)
--blocksize N - set BLOCKSIZE to N cranks (default 200)
--logtimes - log block execution time stats while running
--logmem - log memory usage stats after each block
--logdisk - log disk space usage stats after each block
--logstats - log kernel stats after each block
--logall - log kernel stats, block times, memory use, and disk space
--logtag STR - tag for stats log file (default "runner")
--slog FILE - write swingset slog to FILE
--teleslog - transmit a slog feed to the local otel collector
--forcegc - run garbage collector after each block
--batchsize N - set BATCHSIZE to N cranks (default 200)
--verbose - output verbose debugging messages as it runs
--activityhash - print out the current activity hash after each crank
--audit - audit kernel promise reference counts after each crank
--dump - dump a kernel state store snapshot after each crank
--dumpdir DIR - place kernel state dumps in directory DIR (default ".")
--dumptag STR - prefix kernel state dump filenames with STR (default "t")
--raw - perform kernel state dumps in raw mode
--stats - print a performance stats report at the end of a run
--statsfile FILE - output performance stats to FILE as a JSON object
--benchmark N - perform an N round benchmark after the initial run
--sbench FILE - run a whole-swingset benchmark with the driver vat in FILE
--chain - emulate the behavior of the Cosmos-based chain
--indirect - launch swingset from a vat instead of launching directly
--config FILE - read swingset config from FILE instead of inferring it
--cpu-profile-output - specify where the cpu profile will be dumped (generated using engine's native inspector)
--heap-profile-output - specify where the heap profile will be dumped (generated using engine's native inspector sampler)
--heap-sampling-interval - override the sampling heap inteval (default value is 1024)

CMD is one of:
help - print this helpful usage information
run - launches or resumes the configured vats, which run to completion.
batch - launch or resume, then run BATCHSIZE cranks or until completion
step - steps the configured swingset one crank.
shell - starts a simple CLI allowing the swingset to be run or stepped or
interrogated interactively.

BASEDIR is the base directory for locating the swingset's vat definitions and
for storing the swingset's state. If BASEDIR is omitted or '--' it defaults
to, in order of preference:
- the directory of the benchmark driver, if there is one
- the directory of the config file, if there is one
- the current working directory

Any remaining command line args are passed to the swingset's bootstrap vat in
the 'argv' vat parameter.
99 changes: 45 additions & 54 deletions packages/swingset-runner/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export async function main() {
let logDisk = false;
let logStats = false;
let logTag = 'runner';
let slogFile = null;
let slogFile;
let teleslog = false;
let forceGC = false;
let verbose = false;
Expand All @@ -241,7 +241,7 @@ export async function main() {
let benchmarkRounds = 0;
let configPath = null;
let statsFile = null;
let dbDir = null;
let dbDir;
let initOnly = false;
let useXS = false;
let useBundleCache = false;
Expand All @@ -256,8 +256,8 @@ export async function main() {
let heapSamplingInterval = 1024;
/** @type {import('inspector').Session | undefined} */
let inspectorSession;
/** @type {number | undefined} */
let samplingInterval;
/** @type {string | undefined} */
let nextArg;

const initializeInspectorSession = () =>
import('inspector').then(({ Session }) => {
Expand Down Expand Up @@ -303,7 +303,8 @@ export async function main() {
logStats = true;
break;
case '--logtag':
logTag = argv.shift();
nextArg = argv.shift();
nextArg && (logTag = nextArg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If --logtag '', is invalid, it should be rejected with an error rather than silently ignored.

break;
case '--slog':
slogFile = argv.shift();
Expand Down Expand Up @@ -333,11 +334,13 @@ export async function main() {
doDumps = true;
break;
case '--dumpdir':
dumpDir = argv.shift();
nextArg = argv.shift();
nextArg && (dumpDir = nextArg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here.

doDumps = true;
break;
case '--dumptag':
dumpTag = argv.shift();
nextArg = argv.shift();
nextArg && (dumpTag = nextArg);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

doDumps = true;
break;
case '--dbdir':
Expand Down Expand Up @@ -378,54 +381,42 @@ export async function main() {
swingsetBenchmarkDriverPath = argv.shift();
break;
case '--cpu-profile-output':
cpuProfileFilePath = path.resolve(String(argv.shift()));
await new Promise(resolve => {
const parentDirectory = path.dirname(String(cpuProfileFilePath));
fs.access(parentDirectory, fs.constants.F_OK, err => {
if (err) {
console.warn(
`Directory "${parentDirectory}" does not exist, not initiating cpu profiling`,
);
cpuProfileFilePath = undefined;
}
resolve(null);
nextArg = argv.shift();
if (!nextArg) throw Error('Need a file path for cpu profile');

cpuProfileFilePath = path.resolve(nextArg);
try {
await fs.promises.access(path.dirname(cpuProfileFilePath));
} catch (cause) {
throw Error(`Can't record CPU profile at ${cpuProfileFilePath}`, {
cause,
});
});
}

if (cpuProfileFilePath && !inspectorSession)
await initializeInspectorSession().catch(err => {
console.error('Inspector initializtion failed due to error: ', err);
cpuProfileFilePath = undefined;
});
if (!inspectorSession) await initializeInspectorSession();
break;
case '--heap-profile-output':
heapProfileFilePath = path.resolve(String(argv.shift()));
await new Promise(resolve => {
const parentDirectory = path.dirname(String(heapProfileFilePath));
fs.access(parentDirectory, fs.constants.F_OK, err => {
if (err) {
console.warn(
`Directory "${parentDirectory}" does not exist, not initiating heap profiling`,
);
heapProfileFilePath = undefined;
}
resolve(null);
nextArg = argv.shift();
if (!nextArg) throw Error('Need a file path for heap profile');

heapProfileFilePath = path.resolve(nextArg);
try {
await fs.promises.access(path.dirname(heapProfileFilePath));
} catch (cause) {
throw Error(`Can't record heap profile at ${heapProfileFilePath}`, {
cause,
});
});
}

if (heapProfileFilePath && !inspectorSession)
await initializeInspectorSession().catch(err => {
console.error('Inspector initializtion failed due to error: ', err);
heapProfileFilePath = undefined;
});
if (!inspectorSession) await initializeInspectorSession();
break;
case '--heap-sampling-interval':
samplingInterval = Number(argv.shift());
if (Number.isNaN(samplingInterval) || samplingInterval < 1)
console.warn(
`'${samplingInterval}' is not a valid value, using the fallback value '${heapSamplingInterval}'`,
nextArg = argv.shift();
heapSamplingInterval = Number(nextArg);
if (Number.isNaN(heapSamplingInterval) || heapSamplingInterval < 1)
throw Error(
`'${nextArg}' is not a valid value for sampling interval`,
);
else heapSamplingInterval = samplingInterval;
break;
case '-v':
case '--verbose':
Expand Down Expand Up @@ -463,11 +454,11 @@ export async function main() {
const bootstrapArgv = argv[0] === '--' ? argv.slice(1) : argv;

let config;
await null;

if (configPath) {
config = await loadSwingsetConfigFile(configPath);
if (config === null) {
fail(`config file ${configPath} not found`);
if (!config) {
return fail(`config file ${configPath} not found`);
}
if (!basedir) {
basedir = swingsetBenchmarkDriverPath
Expand Down Expand Up @@ -548,7 +539,7 @@ export async function main() {
break;
}
default:
fail(`invalid database mode ${dbMode}`, true);
return fail(`invalid database mode ${dbMode}`, true);
}
const { kernelStorage, hostStorage } = swingStore;
const runtimeOptions = {};
Expand All @@ -569,6 +560,7 @@ export async function main() {
}
let slogSender;
if (teleslog || slogFile) {
/** @type {typeof process.env} */
const slogEnv = { ...process.env, SLOGFILE: slogFile };
const slogOpts = { stateDir: dbDir, env: slogEnv };
if (slogFile) {
Expand Down Expand Up @@ -669,9 +661,7 @@ export async function main() {
prompt: 'runner> ',
replMode: repl.REPL_MODE_STRICT,
});
cli.on('exit', () => {
hostStorage.close();
});
cli.on('exit', hostStorage.close);
cli.context.dump2 = () => controller.dump();
cli.defineCommand('commit', {
help: 'Commit current kernel state to persistent storage',
Expand Down Expand Up @@ -907,7 +897,8 @@ export async function main() {
]);
}
if (logDisk) {
const diskUsage = dbMode === '--sqlite' ? hostStorage.diskUsage() : 0;
const diskUsage =
(dbMode === '--sqlite' && hostStorage.diskUsage?.()) || 0;
data.push(diskUsage);
}
if (logStats) {
Expand Down
Loading