Monitoring and Profiling

Capture JFR recordings, inspect heap dumps, and profile runtime-compiled code with perf

Elide’s native runtime supports standard diagnostic files and tools. Start a capture with a short Elide flag, or use its Native Image VM equivalent. Discover the options with elide help instruments; they are hidden from everyday help.

QuestionCaptureInspect with
What happened during execution?--jfr=run.jfrJDK jfr
What is retained in the managed heap?--heap-dump=heap.hprofVisualVM
What is a running process doing?jcmd PIDJFR, heap dumps, and thread stacks
Where does runtime-compiled code spend CPU time?--jitdump=DIR and perf recordLinux perf inject and perf report

Captures are opt-in. JMX server and native memory tracking (NMT) remain disabled. Heap dumps cover the managed VM heap, not all Rust or native allocations.

Before you start

Use a native Elide executable and put instrument flags before guest arguments introduced by -- or -m module. They apply to run and explicit scripts, snippets, or modules—not arbitrary tool commands. Paths may be relative or absolute; quote paths containing spaces and choose writable output locations.

Download the tools you need:

On Linux/macOS, point these commands at your extracted installations. This makes the short commands used throughout this guide available in the current shell:

export JAVA_HOME=/absolute/path/to/graalvm
export VISUALVM_DIR=/absolute/path/to/visualvm
export PATH="$JAVA_HOME/bin:$VISUALVM_DIR/bin:$PATH"
java -version
jfr --version
jcmd -l
# Linux only, when using jitdump:
perf version

If VisualVM cannot find Java, launch it with visualvm --jdkhome "$JAVA_HOME" --openfile heap.hprof. On Windows, use the download’s bin/visualvm.exe; on macOS, the download page also offers an app bundle.

Live jcmd attach is supported on Linux and macOS, not Windows; run it as the same OS user as Elide. Jitdump requires a Linux PGO release build and a Linux perf installation with JIT injection support.

These native flags cannot reconfigure a hosting JVM that is already running. In JVM mode, pass the JVM’s supported monitoring options to java before startup. See the CLI Reference for exact flag equivalents and scope.

Record execution with JFR

elide --jfr=run.jfr run app.js
jfr summary run.jfr
jfr print --json run.jfr

The JDK jfr command reference describes event filters, summaries, and other file-inspection commands.

--jfr uses the profile settings and saves the recording on normal exit. Choose the default preset or a custom .jfc file with --jfr-settings:

elide --jfr=run.jfr --jfr-settings=default run app.js
# Equivalent VM spelling for the profile preset:
elide -XX:StartFlightRecording=filename=run.jfr,settings=profile,dumponexit=true run app.js

JFR filenames and settings cannot contain commas or double quotes through the alias. Elide emits a dev.elide.Execution event with the invocation’s exit code:

jfr print --json --events dev.elide.Execution run.jfr

Custom events use the dev.elide namespace and the Elide category. Recording does not start an OpenTelemetry exporter; OpenTelemetry integration is future work.

Inspect the managed heap

elide --heap-dump=heap.hprof run app.js
visualvm --openfile heap.hprof

Elide captures the heap before engine teardown on normal exit. In VisualVM, inspect class and instance counts, then explore retained objects and references. You can collect both artifacts in one invocation:

elide --jfr=run.jfr --heap-dump=heap.hprof run app.js

To request a dump on out-of-memory error instead:

elide --heap-dump-on-oom=oom.hprof run app.js
# Equivalent VM spelling:
elide -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=oom.hprof run app.js

The separate VM option -XX:+DumpHeapAndExit captures the initial heap; it does not replace Elide’s exit-time --heap-dump. Modern JDKs do not include the old jhat heap browser: use a dedicated HPROF reader such as VisualVM.

Capture a running process

Find the process with jcmd -l, then substitute its PID below. Run these commands while the workload is still running:

PID=12345
jcmd "$PID" help
jcmd "$PID" JFR.start name=profile settings=profile
jcmd "$PID" JFR.check
jcmd "$PID" JFR.dump name=profile filename=live.jfr
jcmd "$PID" JFR.stop name=profile
jcmd "$PID" GC.heap_dump live.hprof
jcmd "$PID" Thread.print

Use jcmd "$PID" help COMMAND for supported parameters. Prefer absolute output paths when the target has a different working directory. Native Image may write thread stacks to the target’s stderr, so inspect its logs too. The JDK jcmd reference describes the tool; the target’s own help output is authoritative for which commands its Native Image build supports.

Dump a live recording before forcibly stopping a process. SIGKILL and Elide’s hard timeout cannot finalize recordings. Normal-exit JFR/heap finalization failures produce a nonzero status while preserving an existing workload failure.

Profile JIT-compiled code on Linux

Linux PGO release images include runtime debug-info support but do not write jitdump files by default. Enable capture for a workload long enough to compile and execute hot guest functions:

perf record -k 1 --call-graph fp -o perf.data -- \
  elide --jitdump=./jitdump run app.js
perf inject -j -i perf.data -o perf.jit.data
perf report -i perf.jit.data

-k 1 selects the clock required for JIT injection. Keep the jitdump directory and its jit-<pid>.dump file until injection completes. Injection produces synthetic ELF objects for runtime-compiled code; the report should resolve hot guest symbols from jitted- objects. Jitdump is perf metadata, not a JFR recording.

The VM equivalent is -XX:+RuntimeJitdump -XX:RuntimeJitdumpDir=DIR. -XX:-RuntimeJitdump explicitly disables writing. Other builds omit this capability; build it from source on Linux with ./builder build --release --pgo.

For AOT symbolization, make the matching distribution’s internal/elide.debug discoverable to perf, for example through its build-ID cache. Full stack unwinding across Rust/native libraries also depends on their compilation settings.

GraalVM can warn and continue when jitdump initialization or writing fails. A zero workload exit status is not enough: check that the dump exists, injection succeeds, and the report contains the expected guest symbols. If perf recording is denied, check the host’s profiling permissions rather than disabling security controls globally. WSL2 can support this workflow, but profiler availability and kernel support depend on the installation.

Verification and operational limits

The repository’s monitoring smoke suites generate fresh artifacts and open them with their real readers: JDK jfr summary and jfr print --json, VisualVM’s headless heap reader when configured, and perf inject followed by a symbolized perf report. They also check the custom execution event and complete JIT code records. The Linux PGO build passed all these checks on WSL2.

To repeat the checks from a checkout, set absolute paths for the built executable and the installed tools. Both jitdump and perf checks are explicitly opt-in:

export ELIDE_TEST_BINARY=/absolute/path/to/elide
export JAVA_HOME=/absolute/path/to/graalvm
export VISUALVM_HEAP_JAR=/absolute/path/to/visualvm/visualvm/modules/org-graalvm-visualvm-lib-jfluid-heap.jar
bun test --timeout 120000 tools/test/smoke/instruments.test.mts
ELIDE_TEST_JITDUMP=yes ELIDE_TEST_PERF=yes \
  bun test --timeout 180000 tools/test/smoke/jitdump.test.mts

Successful file inspection establishes functional compatibility, not negligible overhead. Default-off jitdump still adds reachable code and metadata; measure equivalent builds on bare-metal Linux before drawing performance conclusions. Heap dumps and recordings can be large and expose application data, executable code, and source metadata. Store them privately and budget capture time and disk space before using them on a production workload.