diff --git a/packages/swingset-runner/README.md b/packages/swingset-runner/README.md new file mode 100644 index 00000000000..b4de6d81ea0 --- /dev/null +++ b/packages/swingset-runner/README.md @@ -0,0 +1,60 @@ +
+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. +diff --git a/packages/swingset-runner/src/main.js b/packages/swingset-runner/src/main.js index 4da28ae2681..f5c272b326a 100644 --- a/packages/swingset-runner/src/main.js +++ b/packages/swingset-runner/src/main.js @@ -3,7 +3,6 @@ import fs from 'fs'; import process from 'process'; import repl from 'repl'; import util from 'util'; - import { makeStatLogger } from '@agoric/stat-logger'; import { buildTimer, @@ -45,47 +44,50 @@ 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 + --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. + 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 @@ -144,9 +146,7 @@ function generateSwingsetBenchmarkConfig( // eslint-disable-next-line prefer-const let { benchmarkDriver, ...baseConfigOptions } = baseConfig; if (!benchmarkDriver) { - benchmarkDriver = { - sourceSpec: swingsetBenchmarkDriverPath, - }; + benchmarkDriver = { sourceSpec: swingsetBenchmarkDriverPath }; } const config = { ...baseConfigOptions, @@ -156,11 +156,7 @@ function generateSwingsetBenchmarkConfig( benchmarkBootstrap: { sourceSpec: new URL('vat-benchmarkBootstrap.js', import.meta.url) .pathname, - parameters: { - config: { - bootstrap: baseConfig.bootstrap, - }, - }, + parameters: { config: { bootstrap: baseConfig.bootstrap } }, }, benchmarkDriver, ...baseConfig.vats, @@ -180,12 +176,7 @@ function generateIndirectConfig(baseConfig) { vats: { launcher: { sourceSpec: new URL('vat-launcher.js', import.meta.url).pathname, - parameters: { - config: { - bootstrap: baseConfig.bootstrap, - vats: {}, - }, - }, + parameters: { config: { bootstrap: baseConfig.bootstrap, vats: {} } }, }, }, }; @@ -236,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; @@ -250,13 +241,31 @@ 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; let activityHash = false; let emulateChain = false; let swingsetBenchmarkDriverPath = null; + /** @type {string | undefined} */ + let cpuProfileFilePath; + /** @type {string | undefined} */ + let heapProfileFilePath; + /** @type {number} */ + let heapSamplingInterval = 1024; + /** @type {import('inspector').Session | undefined} */ + let inspectorSession; + /** @type {string | undefined} */ + let nextArg; + + const initializeInspectorSession = () => + import('inspector').then(({ Session }) => { + inspectorSession = new Session(); + inspectorSession.connect(); + }); + + await null; while (argv[0] && argv[0].startsWith('-')) { const flag = argv.shift(); @@ -294,7 +303,9 @@ export async function main() { logStats = true; break; case '--logtag': - logTag = argv.shift(); + nextArg = argv.shift(); + if (nextArg) logTag = nextArg; + else throw Error('Missing value for --logtag'); break; case '--slog': slogFile = argv.shift(); @@ -324,11 +335,15 @@ export async function main() { doDumps = true; break; case '--dumpdir': - dumpDir = argv.shift(); + nextArg = argv.shift(); + if (nextArg) dumpDir = nextArg; + else throw Error('Missing value for --dumpdir'); doDumps = true; break; case '--dumptag': - dumpTag = argv.shift(); + nextArg = argv.shift(); + if (nextArg) dumpTag = nextArg; + else throw Error('Missing value for --dumptag'); doDumps = true; break; case '--dbdir': @@ -368,6 +383,44 @@ export async function main() { case '--sbench': swingsetBenchmarkDriverPath = argv.shift(); break; + case '--cpu-profile-output': + 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 (!inspectorSession) await initializeInspectorSession(); + break; + case '--heap-profile-output': + 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 (!inspectorSession) await initializeInspectorSession(); + break; + case '--heap-sampling-interval': + nextArg = argv.shift(); + heapSamplingInterval = Number(nextArg); + if (Number.isNaN(heapSamplingInterval) || heapSamplingInterval < 1) + throw Error( + `'${nextArg}' is not a valid value for sampling interval`, + ); + break; case '-v': case '--verbose': verbose = true; @@ -404,11 +457,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 @@ -431,30 +484,20 @@ export async function main() { } const timer = buildTimer(); - config.devices = { - timer: { - sourceSpec: timer.srcPath, - }, - }; - const deviceEndowments = { - timer: { ...timer.endowments }, - }; + config.devices = { timer: { sourceSpec: timer.srcPath } }; + const deviceEndowments = { timer: { ...timer.endowments } }; if (config.loopboxSenders) { const { loopboxSrcPath, loopboxEndowments } = buildLoopbox('immediate'); config.devices.loopbox = { sourceSpec: loopboxSrcPath, - parameters: { - senders: config.loopboxSenders, - }, + parameters: { senders: config.loopboxSenders }, }; delete config.loopboxSenders; deviceEndowments.loopbox = { ...loopboxEndowments }; } if (emulateChain) { const bridge = await initEmulatedChain(config, configPath); - config.devices.bridge = { - sourceSpec: bridge.srcPath, - }; + config.devices.bridge = { sourceSpec: bridge.srcPath }; deviceEndowments.bridge = { ...bridge.endowments }; } if (!config.defaultManagerType) { @@ -499,7 +542,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 = {}; @@ -520,14 +563,9 @@ export async function main() { } let slogSender; if (teleslog || slogFile) { - const slogEnv = { - ...process.env, - SLOGFILE: slogFile, - }; - const slogOpts = { - stateDir: dbDir, - env: slogEnv, - }; + /** @type {typeof process.env} */ + const slogEnv = { ...process.env, SLOGFILE: slogFile }; + const slogOpts = { stateDir: dbDir, env: slogEnv }; if (slogFile) { slogEnv.SLOGSENDER = ''; } @@ -626,9 +664,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', @@ -654,7 +690,7 @@ export async function main() { cli.defineCommand('block', { help: 'Execute a block of