feat(memtrack): delta-encode stack captures in eBPF - #547
Draft
not-matthias wants to merge 4 commits into
Draft
not-matthias wants to merge 4 commits into
not-matthias wants to merge 4 commits into
Conversation
Exact-hash dedup removes almost no stack records: consecutive captures of one thread differ in a handful of inner-frame words, so every 8 KiB copy reaches the ring. Keep the previous record per tid in a BPF map, XOR the new copy against it aligned by absolute address, and emit only a bitmap per 512-byte group plus the nonzero words, skipping unchanged groups. Registers are XOR-encoded the same way. A thread without a reference encodes against the zeroed slot, marked by ref_hash 0 (a keyframe). Offline this yields ~20x smaller ring records on real runs. The record is emitted with bpf_ringbuf_output at its exact size. The encoder is branch-free per word and merges verifier states before the loop, so the verifier walks it once: 56K instructions at 8 KiB, 217K at the 32 KiB maximum.
Mirror of the kernel encoder: per-tid references, reference-hash check, and an FNV check of the reconstructed bytes so a desynchronised reference is detected instead of producing a wrong stack. The encoder half is a byte-exact mirror used by the tests and by the stack_codec_ratio example, which replays recorded stack dumps through both halves.
Raw records are parsed in the ring callback; delta records are copied once and decoded on the resolver thread. A decode failure drops the record and deletes the kernel reference and the stack's dedup marker, so the next capture of that thread is a keyframe and the lost stack is emitted again. The decoder logs its failure count at shutdown. CODSPEED_MEMTRACK_STACK_COMPRESSION=0 keeps the raw records.
The load test now enables capture: with it frozen off the verifier prunes the capture path and proves nothing about the budget. The new suite runs a fixture with thousands of near-identical stacks and requires every allocation hash to resolve to a decoded record whose bytes hash back to the kernel's value.
Merging this PR will not alter performance
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Exact-hash dedup removes almost no stack records: consecutive captures of the same thread differ in a few words near
sp, so nearly every 8 KiB copy reaches the stack ring. This PR delta-encodes the captures in eBPF and decodes them in userspace.How it works
stack_refs). A new copy is XORed against it, aligned by absolute address. The record carries a word bitmap per 512 B group plus the nonzero words, and unchanged groups are omitted. Registers are XOR-encoded the same way.ref_hash == 0marks this keyframe.bpf_ringbuf_outputsucceeds, so it always matches the last record userspace got.stack_codec): the decoder checksref_hashand then the FNV hash of the rebuilt bytes. On a mismatch it drops the record, and the poller deletesstack_refs[tid]and the stack's dedup marker. The next capture is a keyframe and the lost stack is emitted again.CODSPEED_MEMTRACK_STACK_COMPRESSION=0stay as a kill switch.stack_refsis shrunk to one entry when compression is off.Cost
32-core x86_64, 5 runs per arm, medians. Python allocation loop, ~1.07M stack records:
Known limits
stack_refspreallocates 512 × ~100 KB ≈ 51 MB while compression is on.Testing
stack_compression_tests(new fixturestack_churn.c) at 8 KiB and 32 KiB: 8002 delta records each, every allocation hash resolves to a decoded record whose bytes hash back to the kernel value.stack_budget_testsnow loads both BPF variants with capture and compression enabled;stack_testssnapshots are unchanged.