Jemalloc Heap Profiling
This document records the maintainer workflow for diagnosing Rust heap growth in AsterDrive with jemalloc profiling. It is intended for development and incident investigation, not as a default production setting.
When to use it
Section titled “When to use it”Use this workflow when:
Private_Dirty/Anonymouskeeps growing during a soak test- allocator metrics exposed through
/healthgrow with traffic and do not settle - RSS stays high after load stops and you need to separate Rust heap, allocator retention, mmap, thread stacks, and other sources
- you need the call stacks that hold heap memory instead of only aggregate
/procnumbers
If Private_Dirty / Anonymous stays mostly flat over 1 to 2 hours, do not enable heap profiling first. jemalloc profiling adds overhead and continuously writes .heap files.
Existing wiring
Section titled “Existing wiring”The repository already has the required wiring:
Cargo.tomldefinesjemalloc,jemalloc-stats, andjemalloc-profilingjemalloc-profilingenablestikv-jemallocator/profilingand includesjemalloc-statssrc/main.rsinstallstikv_jemallocator::Jemallocas the global allocator under thejemallocfeaturesrc/main.rsalso embeds low-memory defaults through_rjem_malloc_conf:narenas:1,dirty_decay_ms:1000,muzzy_decay_ms:1000,background_thread:trueaster_forge_alloc::stats()readsstats::allocatedandstats::residentunderjemalloc-stats
This means the first investigation usually does not need code changes. Build a profiling binary and enable profile dumps at runtime.
Build the profiling binary
Section titled “Build the profiling binary”Use the repository’s existing profiling profile. It is close to release mode, but keeps symbols and disables LTO so jeprof can unwind stacks more reliably.
RUSTFLAGS="-C force-frame-pointers=yes" \cargo build --profile profiling --features jemalloc-profilingThe binary is written to:
target/profiling/aster_driveFor production investigation, give the binary a separate name instead of replacing the stable binary:
cp target/profiling/aster_drive ./aster_drive-jprofIf you copy heap profiles back to a development machine, keep the matching binary too. jeprof needs the same binary that generated the profile for symbolization.
Enable profiling at startup
Section titled “Enable profiling at startup”Create a dump directory first:
sudo mkdir -p /var/log/asterdrive/jemallocsudo chown -R asterdrive:asterdrive /var/log/asterdrive/jemallocAsterDrive currently uses the default prefixed tikv-jemallocator build, so the runtime configuration environment variable is normally _RJEM_MALLOC_CONF, not plain MALLOC_CONF. If you are not sure whether a deployment package was built unprefixed, set both variables to the same value.
Recommended low-overhead starting point:
export ASTER_JEMALLOC_PROF='prof:true,prof_active:true,prof_prefix:/var/log/asterdrive/jemalloc/asterdrive,lg_prof_sample:19,lg_prof_interval:26,prof_final:true'
_RJEM_MALLOC_CONF="$ASTER_JEMALLOC_PROF" \MALLOC_CONF="$ASTER_JEMALLOC_PROF" \./aster_drive-jprof serveOption meanings:
prof:true: enables heap profiling. The binary must be built with--features jemalloc-profiling.prof_active:true: starts sampling immediately after process startup.prof_prefix:...: sets the heap dump file prefix.lg_prof_sample:19: samples roughly every2^19 = 512 KiBof allocation. Start here for a service using tens of MiB.lg_prof_interval:26: dumps a profile after roughly2^26 = 64 MiBof cumulative allocation. This produces samples sooner on low-traffic services than28.prof_final:true: writes a final profile when the process exits.
If samples are too sparse, use:
lg_prof_sample:18That changes the sampling interval to roughly 256 KiB, which is more detailed and more expensive.
If too many dump files are produced, increase the interval:
lg_prof_interval:28That changes the dump interval to roughly 256 MiB of cumulative allocation.
systemd setup
Section titled “systemd setup”For a systemd-managed service, create an override:
sudo systemctl edit asterdriveAdd:
[Service]Environment="ASTER_JEMALLOC_PROF=prof:true,prof_active:true,prof_prefix:/var/log/asterdrive/jemalloc/asterdrive,lg_prof_sample:19,lg_prof_interval:26,prof_final:true"Environment="_RJEM_MALLOC_CONF=prof:true,prof_active:true,prof_prefix:/var/log/asterdrive/jemalloc/asterdrive,lg_prof_sample:19,lg_prof_interval:26,prof_final:true"Environment="MALLOC_CONF=prof:true,prof_active:true,prof_prefix:/var/log/asterdrive/jemalloc/asterdrive,lg_prof_sample:19,lg_prof_interval:26,prof_final:true"Then reload and restart:
sudo mkdir -p /var/log/asterdrive/jemallocsudo chown -R asterdrive:asterdrive /var/log/asterdrive/jemallocsudo systemctl daemon-reloadsudo systemctl restart asterdriveVerify startup:
sudo journalctl -u asterdrive -n 200 --no-pagersudo ls -lh /var/log/asterdrive/jemallocIf the journal contains <jemalloc>: unknown option or malformed conf messages, fix the configuration string first. Do not include spaces in the option string.
Recommended sampling workflow
Section titled “Recommended sampling workflow”For suspected leaks, use this sequence:
- Start the service with the profiling binary.
- Wait 3 to 5 minutes after startup while the service is idle.
- Keep the earliest
.heapfile as the baseline. - Run real traffic, a k6 soak, or let the service run for 12 to 24 hours.
- Stop the service so
prof_final:truewrites the final profile. - Use
jeprof --base baseline finalto inspect net growth call stacks.
The diff is the important part. A single profile tells you who currently holds heap memory; a diff tells you who grew during the observation window.
Analyze with jeprof
Section titled “Analyze with jeprof”Check that jeprof is available:
which jeprofIf it is missing, install the jemalloc tools package or copy jeprof from the matching jemalloc build. Distribution package names differ; on Debian / Ubuntu it may be jemalloc-bin.
Inspect one profile:
jeprof --text ./aster_drive-jprof \ /var/log/asterdrive/jemalloc/asterdrive.<pid>.<seq>.heap \ | head -80Compare a baseline profile and a final profile:
jeprof --text \ --base /var/log/asterdrive/jemalloc/asterdrive.<pid>.0001.i0001.heap \ ./aster_drive-jprof \ /var/log/asterdrive/jemalloc/asterdrive.<pid>.0010.f.heap \ | head -80Export an SVG if needed:
jeprof --svg ./aster_drive-jprof \ /var/log/asterdrive/jemalloc/asterdrive.<pid>.<seq>.heap \ > /tmp/asterdrive-heap.svgInterpreting results
Section titled “Interpreting results”Common cases:
/procis stable andjeprof --baseshows no meaningful net growth: this does not look like a leak./prockeeps growing andjeprof --basepoints to specific Rust stacks: inspect the related business cache, task queue, buffer, connection pool, or retained object path./procgrows butjeprofdoes not show meaningful growth: check non-Rust-heap sources such as mmap, file mappings, thread stacks, C library allocations, or kernel page-cache accounting.allocateddrops butresidentdoes not drop immediately: this can be allocator retention or decay behavior, not necessarily a leak.Pss_Filechanges whileAnonymousdoes not grow: this is usually not the main Rust heap leak signal.
Troubleshooting checklist
Section titled “Troubleshooting checklist”If no .heap files are produced, check:
- Is the running binary really built with
cargo build --profile profiling --features jemalloc-profiling? - Did systemd or the shell actually pass
_RJEM_MALLOC_CONFto the process? - Does the
prof_prefixdirectory exist, and can the service user write to it? - Is
lg_prof_intervaltoo large for the current low-traffic workload? - Does journal / stderr contain
<jemalloc>:configuration errors? - Did the option string include spaces, for example
prof:true, prof_active:true? Do not do that.
If stacks only show addresses or poor symbols, check:
- Are you analyzing the
.heapfile with the sameaster_drive-jprofbinary? - Did the binary come from
profile.profilinginstead of a stripped release build? - Was it built with
RUSTFLAGS="-C force-frame-pointers=yes"? - Are you analyzing on another machine where paths, debug info, or build IDs no longer match?
Future improvement
Section titled “Future improvement”If precise dump timing becomes necessary, add an internal diagnostics entry point gated behind jemalloc-profiling, for example:
POST /api/v1/admin/diagnostics/jemalloc/dumpor a CLI command:
aster_drive diagnostics jemalloc-dumpThe implementation should call mallctl("prof.dump"). This capability must be feature-gated and restricted to administrators or local operations. Do not expose it as a default public route, because it is a disk-write and performance-pressure switch.