From b3d456c001c14fa684a7422d56316e374a7dfc8f Mon Sep 17 00:00:00 2001 From: usmanmani1122 Date: Fri, 16 May 2025 09:27:55 +0000 Subject: [PATCH 1/8] feat: support cpu profile generation --- packages/swingset-runner/src/main.js | 121 +++++++++++++++++---------- 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/packages/swingset-runner/src/main.js b/packages/swingset-runner/src/main.js index 4da28ae2681..8d742abbe57 100644 --- a/packages/swingset-runner/src/main.js +++ b/packages/swingset-runner/src/main.js @@ -3,7 +3,7 @@ import fs from 'fs'; import process from 'process'; import repl from 'repl'; import util from 'util'; - +import inspector from 'inspector'; import { makeStatLogger } from '@agoric/stat-logger'; import { buildTimer, @@ -144,9 +144,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 +154,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 +174,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: {} } }, }, }, }; @@ -257,6 +246,12 @@ export async function main() { let activityHash = false; let emulateChain = false; let swingsetBenchmarkDriverPath = null; + /** @type {string | undefined} */ + let profileFilePath; + /** @type {inspector.Session} */ + let inspectorSession; + + await null; while (argv[0] && argv[0].startsWith('-')) { const flag = argv.shift(); @@ -368,6 +363,24 @@ export async function main() { case '--sbench': swingsetBenchmarkDriverPath = argv.shift(); break; + case '--profile': + profileFilePath = path.resolve(String(argv.shift())); + await new Promise(resolve => { + const parentDirectory = path.dirname(String(profileFilePath)); + fs.access(parentDirectory, fs.constants.F_OK, err => { + if (err) { + console.warn( + `Directory "${parentDirectory}" does not exist, not initiating profiling`, + ); + profileFilePath = undefined; + } else { + inspectorSession = new inspector.Session(); + inspectorSession.connect(); + } + resolve(null); + }); + }); + break; case '-v': case '--verbose': verbose = true; @@ -431,30 +444,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) { @@ -520,14 +523,8 @@ export async function main() { } let slogSender; if (teleslog || slogFile) { - const slogEnv = { - ...process.env, - SLOGFILE: slogFile, - }; - const slogOpts = { - stateDir: dbDir, - env: slogEnv, - }; + const slogEnv = { ...process.env, SLOGFILE: slogFile }; + const slogOpts = { stateDir: dbDir, env: slogEnv }; if (slogFile) { slogEnv.SLOGSENDER = ''; } @@ -654,7 +651,7 @@ export async function main() { cli.defineCommand('block', { help: 'Execute a block of cranks, without commit', action: async requestedSteps => { - const steps = await runBlock(requestedSteps, false); + const steps = await runBlock(Number(requestedSteps), false); log(`executed ${steps} cranks in block`); cli.displayPrompt(); }, @@ -662,7 +659,7 @@ export async function main() { cli.defineCommand('benchmark', { help: 'Run rounds of the benchmark protocol', action: async rounds => { - const [steps, deltaT] = await runBenchmark(rounds); + const [steps, deltaT] = await runBenchmark(Number(rounds)); log(`benchmark ${rounds} rounds, ${steps} cranks in ${deltaT} ns`); cli.displayPrompt(); }, @@ -696,9 +693,7 @@ export async function main() { if (statLogger) { statLogger.close(); } - if (slogSender) { - await slogSender.forceFlush(); - } + if (slogSender) await slogSender.forceFlush?.(); await controller.shutdown(); function getCrankNumber() { @@ -710,12 +705,35 @@ export async function main() { dumpStore(kernelStorage, dumpPath, rawMode); } + /** + * @param {string} method + * @returns {object} + */ + function postToInspector(method) { + return new Promise((resolve, reject) => { + inspectorSession.post(method, (err, result) => + err ? reject(err) : resolve(result), + ); + }); + } + + /** + * @param {number} rounds + * @returns {Promise<[number, bigint]>} + */ async function runBenchmark(rounds) { const cranksPre = getCrankNumber(); const rawStatsPre = controller.getStats(); let totalSteps = 0; let totalDeltaT = 0n; + await null; + + if (profileFilePath) { + await postToInspector('Profiler.enable'); + await postToInspector('Profiler.start'); + } + for (let i = 0; i < rounds; i += 1) { const roundResult = controller.queueToVatRoot( config.bootstrap, @@ -734,6 +752,11 @@ export async function main() { totalSteps += steps; totalDeltaT += deltaT; } + + if (profileFilePath) { + const { profile } = await postToInspector('Profiler.stop'); + fs.writeFileSync(profileFilePath, JSON.stringify(profile)); + } const cranksPost = getCrankNumber(); const rawStatsPost = controller.getStats(); benchmarkStats = organizeBenchmarkStats( @@ -746,6 +769,10 @@ export async function main() { return [totalSteps, totalDeltaT]; } + /** + * @param {number} requestedSteps + * @param {boolean} doCommit + */ async function runBlock(requestedSteps, doCommit) { const blockStartTime = readClock(); let actualSteps = 0; @@ -802,6 +829,7 @@ export async function main() { } if (statLogger) { blockNumber += 1; + /** @type {Array} */ let data = [blockNumber, actualSteps]; if (logTimes) { data.push(blockEndTime - blockStartTime); @@ -831,6 +859,11 @@ export async function main() { return actualSteps; } + /** + * @param {number} stepLimit + * @param {boolean} doCommit + * @returns {Promise<[number, bigint]>} + */ async function runBatch(stepLimit, doCommit) { const startTime = readClock(); let totalSteps = 0; @@ -850,6 +883,10 @@ export async function main() { process.exit(1); } + /** + * @param {number} stepLimit + * @param {boolean} runInBlockMode + */ async function commandRun(stepLimit, runInBlockMode) { if (doDumps) { kernelStateDump(); From f5c92837195c78c4a842078016653545443c4475 Mon Sep 17 00:00:00 2001 From: usmanmani1122 Date: Sun, 18 May 2025 11:56:50 +0000 Subject: [PATCH 2/8] memory profiling flag support --- packages/swingset-runner/src/main.js | 72 ++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/packages/swingset-runner/src/main.js b/packages/swingset-runner/src/main.js index 8d742abbe57..d26621db4bd 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 inspector from 'inspector'; import { makeStatLogger } from '@agoric/stat-logger'; import { buildTimer, @@ -247,10 +246,22 @@ export async function main() { let emulateChain = false; let swingsetBenchmarkDriverPath = null; /** @type {string | undefined} */ - let profileFilePath; - /** @type {inspector.Session} */ + let cpuProfileFilePath; + /** @type {string | undefined} */ + let heapProfileFilePath; + /** @type {import('inspector').Session | undefined} */ let inspectorSession; + const initializeInspectorSession = () => + import('inspector') + .then(({ Session }) => { + inspectorSession = new Session(); + inspectorSession.connect(); + }) + .catch(err => + console.error('Inspector initializtion failed due to error: ', err), + ); + await null; while (argv[0] && argv[0].startsWith('-')) { @@ -363,23 +374,41 @@ export async function main() { case '--sbench': swingsetBenchmarkDriverPath = argv.shift(); break; - case '--profile': - profileFilePath = path.resolve(String(argv.shift())); + 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); + }); + }); + + if (cpuProfileFilePath && !inspectorSession) + await initializeInspectorSession(); + break; + case '--heap-profile-output': + heapProfileFilePath = path.resolve(String(argv.shift())); await new Promise(resolve => { - const parentDirectory = path.dirname(String(profileFilePath)); + 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 profiling`, + `Directory "${parentDirectory}" does not exist, not initiating heap profiling`, ); - profileFilePath = undefined; - } else { - inspectorSession = new inspector.Session(); - inspectorSession.connect(); + heapProfileFilePath = undefined; } resolve(null); }); }); + + if (heapProfileFilePath && !inspectorSession) + await initializeInspectorSession(); break; case '-v': case '--verbose': @@ -711,7 +740,7 @@ export async function main() { */ function postToInspector(method) { return new Promise((resolve, reject) => { - inspectorSession.post(method, (err, result) => + inspectorSession?.post(method, (err, result) => err ? reject(err) : resolve(result), ); }); @@ -729,11 +758,16 @@ export async function main() { await null; - if (profileFilePath) { + if (cpuProfileFilePath) { await postToInspector('Profiler.enable'); await postToInspector('Profiler.start'); } + if (heapProfileFilePath) { + await postToInspector('HeapProfiler.enable'); + await postToInspector('HeapProfiler.startSampling'); + } + for (let i = 0; i < rounds; i += 1) { const roundResult = controller.queueToVatRoot( config.bootstrap, @@ -753,10 +787,18 @@ export async function main() { totalDeltaT += deltaT; } - if (profileFilePath) { + if (cpuProfileFilePath) { const { profile } = await postToInspector('Profiler.stop'); - fs.writeFileSync(profileFilePath, JSON.stringify(profile)); + fs.writeFileSync(cpuProfileFilePath, JSON.stringify(profile)); + await postToInspector('Profiler.disable'); } + + if (heapProfileFilePath) { + const { profile } = await postToInspector('HeapProfiler.stopSampling'); + fs.writeFileSync(heapProfileFilePath, JSON.stringify(profile)); + await postToInspector('HeapProfiler.disable'); + } + const cranksPost = getCrankNumber(); const rawStatsPost = controller.getStats(); benchmarkStats = organizeBenchmarkStats( From b72f307e408543099ff6b9b80ace09a080f913da Mon Sep 17 00:00:00 2001 From: usmanmani1122 Date: Mon, 19 May 2025 07:58:33 +0000 Subject: [PATCH 3/8] custom sample interval --- packages/swingset-runner/src/main.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/swingset-runner/src/main.js b/packages/swingset-runner/src/main.js index d26621db4bd..083d8642d0a 100644 --- a/packages/swingset-runner/src/main.js +++ b/packages/swingset-runner/src/main.js @@ -736,11 +736,12 @@ export async function main() { /** * @param {string} method + * @param {Partial<{samplingInterval: number}>} [parameters] * @returns {object} */ - function postToInspector(method) { + function postToInspector(method, parameters = {}) { return new Promise((resolve, reject) => { - inspectorSession?.post(method, (err, result) => + inspectorSession?.post(method, parameters, (err, result) => err ? reject(err) : resolve(result), ); }); @@ -765,7 +766,9 @@ export async function main() { if (heapProfileFilePath) { await postToInspector('HeapProfiler.enable'); - await postToInspector('HeapProfiler.startSampling'); + await postToInspector('HeapProfiler.startSampling', { + samplingInterval: 1024, + }); } for (let i = 0; i < rounds; i += 1) { From d7e8eb325e75d4c7bf2e6ed5206a6d4bf10277a2 Mon Sep 17 00:00:00 2001 From: usmanmani1122 Date: Thu, 22 May 2025 07:29:00 +0000 Subject: [PATCH 4/8] allow custom sampling + add help message --- packages/swingset-runner/src/main.js | 95 ++++++++++++++++------------ 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/packages/swingset-runner/src/main.js b/packages/swingset-runner/src/main.js index 083d8642d0a..434211d66cc 100644 --- a/packages/swingset-runner/src/main.js +++ b/packages/swingset-runner/src/main.js @@ -44,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 @@ -249,8 +252,12 @@ export async function main() { let cpuProfileFilePath; /** @type {string | undefined} */ let heapProfileFilePath; + /** @type {number} */ + let heapSamplingInterval = 1024; /** @type {import('inspector').Session | undefined} */ let inspectorSession; + /** @type {number | undefined} */ + let samplingInterval; const initializeInspectorSession = () => import('inspector') @@ -410,6 +417,14 @@ export async function main() { if (heapProfileFilePath && !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}'`, + ); + else heapSamplingInterval = samplingInterval; + break; case '-v': case '--verbose': verbose = true; @@ -767,7 +782,7 @@ export async function main() { if (heapProfileFilePath) { await postToInspector('HeapProfiler.enable'); await postToInspector('HeapProfiler.startSampling', { - samplingInterval: 1024, + samplingInterval: heapSamplingInterval, }); } From dafa47655fabf7e0a810871f6be17b0b303bb2e9 Mon Sep 17 00:00:00 2001 From: usmanmani1122 Date: Thu, 22 May 2025 07:40:45 +0000 Subject: [PATCH 5/8] error handling --- packages/swingset-runner/src/main.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/swingset-runner/src/main.js b/packages/swingset-runner/src/main.js index 434211d66cc..f3e21d143b8 100644 --- a/packages/swingset-runner/src/main.js +++ b/packages/swingset-runner/src/main.js @@ -260,14 +260,10 @@ export async function main() { let samplingInterval; const initializeInspectorSession = () => - import('inspector') - .then(({ Session }) => { - inspectorSession = new Session(); - inspectorSession.connect(); - }) - .catch(err => - console.error('Inspector initializtion failed due to error: ', err), - ); + import('inspector').then(({ Session }) => { + inspectorSession = new Session(); + inspectorSession.connect(); + }); await null; @@ -397,7 +393,10 @@ export async function main() { }); if (cpuProfileFilePath && !inspectorSession) - await initializeInspectorSession(); + await initializeInspectorSession().catch(err => { + console.error('Inspector initializtion failed due to error: ', err); + cpuProfileFilePath = undefined; + }); break; case '--heap-profile-output': heapProfileFilePath = path.resolve(String(argv.shift())); @@ -415,7 +414,10 @@ export async function main() { }); if (heapProfileFilePath && !inspectorSession) - await initializeInspectorSession(); + await initializeInspectorSession().catch(err => { + console.error('Inspector initializtion failed due to error: ', err); + heapProfileFilePath = undefined; + }); break; case '--heap-sampling-interval': samplingInterval = Number(argv.shift()); @@ -755,11 +757,11 @@ export async function main() { * @returns {object} */ function postToInspector(method, parameters = {}) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => inspectorSession?.post(method, parameters, (err, result) => err ? reject(err) : resolve(result), - ); - }); + ), + ); } /** From e5a1be662c20aca226e82b13941d4caad4e5c4f4 Mon Sep 17 00:00:00 2001 From: usmanmani1122 Date: Fri, 23 May 2025 12:41:37 +0000 Subject: [PATCH 6/8] address @gibson042 comments --- packages/swingset-runner/README.md | 58 ++++++++++++++++ packages/swingset-runner/src/main.js | 99 +++++++++++++--------------- 2 files changed, 103 insertions(+), 54 deletions(-) create mode 100644 packages/swingset-runner/README.md diff --git a/packages/swingset-runner/README.md b/packages/swingset-runner/README.md new file mode 100644 index 00000000000..13bcdc15587 --- /dev/null +++ b/packages/swingset-runner/README.md @@ -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. diff --git a/packages/swingset-runner/src/main.js b/packages/swingset-runner/src/main.js index f3e21d143b8..0f3db67f2ed 100644 --- a/packages/swingset-runner/src/main.js +++ b/packages/swingset-runner/src/main.js @@ -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; @@ -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; @@ -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 }) => { @@ -303,7 +303,8 @@ export async function main() { logStats = true; break; case '--logtag': - logTag = argv.shift(); + nextArg = argv.shift(); + nextArg && (logTag = nextArg); break; case '--slog': slogFile = argv.shift(); @@ -333,11 +334,13 @@ export async function main() { doDumps = true; break; case '--dumpdir': - dumpDir = argv.shift(); + nextArg = argv.shift(); + nextArg && (dumpDir = nextArg); doDumps = true; break; case '--dumptag': - dumpTag = argv.shift(); + nextArg = argv.shift(); + nextArg && (dumpTag = nextArg); doDumps = true; break; case '--dbdir': @@ -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': @@ -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 @@ -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 = {}; @@ -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) { @@ -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', @@ -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) { From 9f989d90d6e53f5abcf7abbcd329b5f846c6f905 Mon Sep 17 00:00:00 2001 From: Usman Saleem <57341641+usmanmani1122@users.noreply.github.com> Date: Fri, 23 May 2025 18:31:59 +0500 Subject: [PATCH 7/8] fix README.md formatting --- packages/swingset-runner/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/swingset-runner/README.md b/packages/swingset-runner/README.md index 13bcdc15587..b4de6d81ea0 100644 --- a/packages/swingset-runner/README.md +++ b/packages/swingset-runner/README.md @@ -1,3 +1,4 @@ +
 Command line:
   runner [FLAGS...] CMD [{BASEDIR|--} [ARGS...]]
 
@@ -56,3 +57,4 @@ BASEDIR is the base directory for locating the swingset's vat definitions and
 
 Any remaining command line args are passed to the swingset's bootstrap vat in
 the 'argv' vat parameter.
+
From a459385ee8aef475ae0623032dfe2ffd7684e214 Mon Sep 17 00:00:00 2001 From: usmanmani1122 Date: Mon, 26 May 2025 09:35:08 +0000 Subject: [PATCH 8/8] address @gibson042 comments --- packages/swingset-runner/src/main.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/swingset-runner/src/main.js b/packages/swingset-runner/src/main.js index 0f3db67f2ed..f5c272b326a 100644 --- a/packages/swingset-runner/src/main.js +++ b/packages/swingset-runner/src/main.js @@ -304,7 +304,8 @@ export async function main() { break; case '--logtag': nextArg = argv.shift(); - nextArg && (logTag = nextArg); + if (nextArg) logTag = nextArg; + else throw Error('Missing value for --logtag'); break; case '--slog': slogFile = argv.shift(); @@ -335,12 +336,14 @@ export async function main() { break; case '--dumpdir': nextArg = argv.shift(); - nextArg && (dumpDir = nextArg); + if (nextArg) dumpDir = nextArg; + else throw Error('Missing value for --dumpdir'); doDumps = true; break; case '--dumptag': nextArg = argv.shift(); - nextArg && (dumpTag = nextArg); + if (nextArg) dumpTag = nextArg; + else throw Error('Missing value for --dumptag'); doDumps = true; break; case '--dbdir':