A live eBPF-based CPU profiler designed as an educational tool for understanding what the operating system is really doing while your computer runs. Samples all CPU cores at 99hz, captures kernel and userspace call stacks, and presents the data through a real-time TUI.
Built as a learning exercise for eBPF, Linux internals, C++, and systems performance.
A suite of eBPF-based OS observability tools — starting with CPU, with memory profiling to follow. Each instrument uses the same pattern: eBPF collection, real-time aggregation, and a TUI that makes OS internals visible and intuitive.
- BPF program sampling all CPUs at 99hz via perf events
- Kernel stack resolution via
/proc/kallsyms - Userspace stack resolution via
/proc/PID/maps(shows file+offset, not yet symbol names) - CMake build system with FTXUI via FetchContent
- Collector running on background thread
- Double-buffered event pipeline with 3.4s snapshot heartbeat
- TUI with thread activity view (per-CPU kernel/user/idle bar chart)
- Kernel dictionary view: live top-N symbols ranked by CPU%, with descriptions and category color coding
- Symbol descriptions stored in
data/descriptions.json(483 symbols with categories) -
collect_symbolstool to discover new kernel symbols on a running system - Call stack view: top kernel call chains as proportional colored bars, ranked by frequency
- Lock contention view: futex holder-waiter pairs grouped by call stack, with waiter and holder frames side by side
While the profiler is running, data/snapshot.json is updated after every heartbeat (every ~3.4s) with the last 60 seconds of resolved events. Inspect it with:
jq '.[0]' data/snapshot.json # first event
jq '[.[].comm] | unique' data/snapshot.json # all process names seen
jq '[.[].kernel_syms[]] | group_by(.) | map({sym: .[0], count: length}) | sort_by(-.count) | .[0:10]' data/snapshot.json # top kernel symbols- ELF symbol table parsing for real userspace function names
sudo apt install clang libbpf-dev libelf-dev libdw-dev zlib1g-dev bpftool cmakemkdir build && cd build
cmake ..
makesudo ./build/profilerThe TUI refreshes every 3.4 seconds. Terminal size determines how much data is shown — a larger window reveals more threads and stack depth, a smaller one filters to only the hottest activity.
- Thread activity: per-thread breakdown of time spent in kernel, userspace, and idle
- Kernel dictionary: top-20 kernel symbols ranked by CPU utilization, with descriptions and category color coding
- Call stacks: top kernel call chains rendered as proportional colored bars, ranked by sample frequency
- Lock contention: futex holder-waiter pairs grouped by call stack, sorted by total wait time; shows waiter (yellow ←) and holder (cyan →) frames side by side
bpf/ # BPF kernel program
profiler.bpf.c # Samples CPU stacks via perf events at 99hz
profiler.h # Shared structs between kernel and userspace
src/ # Userspace C++
main.cpp # Entry point, TUI shell, double-buffer heartbeat
collector.cpp/h # Loads BPF, manages perf events and ring buffer
buffer.h # Thread-safe double buffer for event snapshots
symbols.cpp/h # Resolves kernel and userspace addresses to function names
output.cpp/h # Legacy per-event stdout output (to be removed)
aggregators/
aggregator.h # Abstract base class for all views
thread_activity.cpp/h # Per-CPU kernel/user/idle bar chart
kernel_dictionary.cpp/h # Top kernel symbols ranked by CPU%, with descriptions
call_stack.cpp/h # Top kernel call chains as proportional colored bars
data/
descriptions.json # 483 kernel symbols with descriptions and categories
tools/
collect_symbols.cpp # Samples for 2 minutes and adds new symbols to descriptions.json
CMakeLists.txt # Build system (CMake + FTXUI + nlohmann/json via FetchContent)
screenshots/ # Screenshots for README
See CLAUDE.md for instructions to future Claude sessions on how to collaborate on this project.



