RFC: add optional DWARF user stack unwinding for BCC tools#5519
Open
XinShuichen wants to merge 4 commits into
Open
RFC: add optional DWARF user stack unwinding for BCC tools#5519XinShuichen wants to merge 4 commits into
XinShuichen wants to merge 4 commits into
Conversation
Add optional libgunwinder discovery and keep the dependency disabled by default. When enabled, libbcc exposes a small C ABI for creating an unwinder context, sampling caller-provided register and stack snapshots, copying frame metadata, and releasing results. The adapter initializes libgunwinder only for enabled builds and preserves default builds with a stub implementation. Add focused C++ tests for unsupported builds, result ownership, truncation, and enabled adapter behavior. Test: pass Signed-off-by: Zhang Yuchen <zhangyuchen.lcr@bytedance.com>
Add ctypes bindings for the DWARF C ABI and expose a bcc.dwarf wrapper that copies native results before freeing libbcc-owned memory. The wrapper keeps context close idempotent and reports errno-backed failures to callers. Also add reusable BPF snippet builders for bounded DWARF user-stack samples. These helpers keep tool scripts thin while centralizing register validation, bpf_task_pt_regs probing, and sample decoding. Test: pass Signed-off-by: Zhang Yuchen <zhangyuchen.lcr@bytedance.com>
Add an opt-in --dwarf -U path for user stack profiling. The BPF side captures bounded register and stack snapshots through the reusable DWARF provider, while Python unwinds samples with libgunwinder and aggregates the formatted user stacks. Keep the existing stack-id path unchanged for default profiling. DWARF mode rejects unsupported stack selections, requires bpf_task_pt_regs, and reports perf-buffer loss separately from missed unwinds. Test: pass Signed-off-by: Zhang Yuchen <zhangyuchen.lcr@bytedance.com>
Add --dwarf -U support for capable.py using the shared hook-sample DWARF provider. Kernel stacks continue to use BPF_STACK_TRACE, while user stacks are decoded from bounded per-event samples in Python. Reject --dwarf without -U and reject --dwarf with --unique for now, because unique filtering is currently performed in BPF with stack IDs and DWARF frame identity is only available after userspace unwinding. Test: pass Signed-off-by: Zhang Yuchen <zhangyuchen.lcr@bytedance.com>
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.
Description
This PR is posted as an RFC to discuss the API shape and DWARF unwind model
before expanding the feature to more BCC tools.
BCC user-stack collection today mostly depends on kernel stack maps walking
frame-pointer chains. That works well when user binaries preserve frame
pointers, but it misses or truncates stacks for common optimized userspace
binaries and libraries. This is the long-standing problem described in #1234.
This series adds an optional DWARF user-stack provider backed by libgunwinder.
The default behavior is unchanged. DWARF unwinding is opt-in.
This PR adds:
ENABLE_DWARF_UNWINDER, defaultOFFbcc.dwarfprofile.py --dwarf -Ucapable.py --dwarf -UThe current BCC provider is intentionally conservative:
BPF_STACK_TRACEbehavior remains unchangedbpf_task_pt_regscapable.py --dwarf --uniqueis rejected for nowThe x86_64-only BCC provider is not a libgunwinder limitation. libgunwinder and
continue-profiling-agent have x86_64 and arm64 support. arm64 BCC support can be
added later by extending the BPF register mapping and validation.
Related work:
are available.
Why this approach
DWARF user-stack unwinding from BPF samples is not just symbolization. The BPF
side captures a bounded user register and stack snapshot, and userspace needs to
unwind that snapshot repeatedly across many processes and shared libraries.
libgunwinder is designed for this model:
perf.data-style raw snapshots for offline DWARFprocessing
This differs from a libunwind-ptrace style integration. libunwind is useful for
traditional process-oriented unwinding, but whole-machine profiling needs a
global cache model for repeated samples across many PIDs. Without that, common
ELFs such as libc, libstdc++, runtimes, and service libraries are likely to be
loaded and indexed repeatedly per process.
libgunwinder is used by Volcengine continue-profiling-agent (CPA):
CPA is a whole-machine continuous profiling agent. It is designed to run
persistently on production hosts, collect CPU/off-CPU/profile-style stack data
across processes, and keep overhead low enough for fleet-wide deployment. The
same libgunwinder-based DWARF unwind path is used there for repeated global
user-stack unwinding.
The ByteDance internal version of the CPA stack has also been exercised at very
large fleet scale, close to one million machines. This is mentioned only as
engineering stability background for the unwinder design. It is not a substitute
for BCC CI, upstream review, or BCC-side test coverage.
Public CPA benchmark data for the same unwinder design includes:
11,998,307 frames/s, avg83.35 ns/frame4,227,484 frames/s, avg236.55 ns/frame1,353,664 frames/s, avg738.74 ns/frame8.8k to 11.3k unwinds/s189 us to 213 us4.41k samples/s, median4.39k samples/s,P95
4.57k samples/s13.1 us, median12.5 us, P9520.1 us, P9922.8 us1.39 millionstable unwinds,98.23%completed within64 usThese numbers are workload-dependent and are not used as BCC CI assertions.
They are included to explain why libgunwinder is a better fit than a generic
per-process unwinder for repeated whole-machine profiling.
There are still API design questions for BCC:
userspace stack IDs after unwinding.
decode helper.
separate design because DWARF frame identity is known only after userspace
unwinding.
This PR keeps the first integration small so the dependency, event shape,
helper requirements, and tool semantics can be reviewed before changing more
tools.
Tests run locally:
git diff --check upstream/master..HEADpython3 -m py_compilefor affected Python filesctest -R '^test_dwarf_unwind$'with DWARF enabledctest -R '^test_dwarf_unwind$'with DWARF disabledtests/python/test_dwarf_unwind.pywith DWARF enabledtests/python/test_dwarf_unwind.pywith DWARF disabledtest_tools_smoke.pysubsetprofile.py --dwarf -U -F 1 1 -flive smokeprofile.py 1live smokecapable.py --dwarf -U -vlive smokesym_cworkload withprofile.py --dwarf -U, producing expectedc_path_a_*/c_path_b_*user framesLive DWARF stack output is not made a mandatory CI assertion because it depends
on kernel BPF capabilities, target workload, debug/unwind metadata, and perf
buffer pressure.
Checklist
tools/toolname:,libbpf-tools/toolname:,src/cc:,docs:,build:,tests/python:)For new tools only
N/A. This PR does not add a new tool. It extends existing
profile.pyandcapable.py, and updates their existing man pages, example files, and smoketests.