Process and external memory
Allocated bytes, live bytes and resident memory describe different stages of an object's life. Keep them apart.
Allocated bytes — memory obtained during the operation.
Live bytes — memory still retained afterwards.
Resident memory (RSS) — the part of the process currently in physical RAM.
Private memory — memory private to the process, by the OS definition below.
A peak counter can cover the whole process lifetime, not just the last workload.
For Bibliography, reducing temporary export buffers can lower Julia allocation counts without lowering resident memory: the allocator may keep capacity for reuse. Conversely, a native dependency can grow process memory without appearing in Julia allocation counters.
PerfChecker keeps three questions separate:
How many Julia allocations does the operation perform?
How does the worker's resident/private memory change?
How many bytes does a package own outside Julia's heap?
No counter is a proxy for another.
Process envelope
Enable :process_resources on a feature to snapshot the isolated worker immediately before and after its backend collection.
FeatureSpec(:export_bibtex;
entrypoint = "perf/features/export_bibtex.jl",
options = Dict(:samples => 20, :process_resources => true))Windows —
WorkingSetSize, lifetimePeakWorkingSetSize,PrivateUsage.Linux —
VmRSS,VmHWM,smaps_rollupprivate mappings.Other platforms — all three
unavailable.An unsupported or inaccessible counter stays absent. A platform failure produces
resource.process.unavailable, never a zero.The envelope surrounds the whole collection, not each sample.
Backend warmup or compilation can still occur inside the envelope; the bundle marks this boundary.
A process peak is never reset:
new_lifetime_peakrecords only how much the high-water mark advanced.
process_memory_snapshot() exposes the same adapter for diagnostics; process_memory_capabilities() lists the available counters.
External memory
A package with native handles, pools, mapped arenas or device staging memory can name a callback in the feature options:
FeatureSpec(:step_native;
entrypoint = "perf/features/step_native.jl",
options = Dict(
:process_resources => true,
:external_memory_probe => :perf_external_memory,
:external_memory_required => true,
:resource_upper_limits => Dict(:external_live_delta_bytes => 0),
:require_external_balance => true,
))The zero-argument callback implements perfchecker-external-memory/1:
function perf_external_memory()
return (
schema_version = "perfchecker-external-memory/1",
status = "observed",
live_bytes = ledger.live_bytes,
reserved_bytes = ledger.reserved_bytes,
allocated_bytes_total = ledger.allocated_bytes_total,
freed_bytes_total = ledger.freed_bytes_total,
provider = "my-native-ledger",
)
endAll byte values are non-negative integers.
live_bytesandreserved_bytesare gauges; their signed deltas may be negative.allocated_bytes_totalandfreed_bytes_totalare monotonic; a decreasing value invalidates the envelope.Fields may be omitted only when the owner genuinely cannot observe them; at least one is required for
observed.With
:external_memory_required => false(default), a missing callback becomes informational. Withtrue, it fails the feature.
resource_upper_limits turns selected envelope metrics into absolute per-run gates. require_external_balance also requires monotonic allocated/freed deltas to match and forbids growth in live external bytes. A violation marks the feature invalid while keeping the envelope for diagnosis.
Bundle metrics
process.rss.before/v1,after/v1,delta/v1— resident memory around the collection.process.rss.peak_lifetime_after/v1,new_lifetime_peak/v1— lifetime high-water mark.process.private.delta/v1— private memory change.external.live.after/v1,external.live.delta/v1— native live bytes.external.reserved.delta/v1— native reserved change.external.allocated.delta/v1,external.freed.delta/v1— native monotonic deltas.
This turns a vague claim such as "memory is fine" into three independent assertions: zero Julia allocations, zero external live-byte growth, stable RSS.
