-
Notifications
You must be signed in to change notification settings - Fork 245
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
base: master
Are you sure you want to change the base?
Changes from 2 commits
b3d456c
f5c9283
b72f307
355167d
b6ba772
d7e8eb3
dafa476
e043b10
e5a1be6
9f989d9
a9a16ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If |
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And here. |
||
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) { | ||
|
There was a problem hiding this comment.
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.