A lightweight CPU utilization monitor built with eBPF + libbpf.
It hooks into the kernel scheduler (sched_switch tracepoint) to measure per-CPU
active time with nanosecond precision — more accurate than polling-based tools like
top, and with lower overhead at high process counts.
How it works
Kernel (eBPF) User space (C + libbpf)
----------------------- -------------------------
sched_switch tracepoint poll BPF maps every 1 s
-> record on-CPU time per core -> compute active % and idle %
-> accumulate in BPF Array map print per-core + average
The sched_switch tracepoint context structure is defined manually in
cpu_monitor_bpf.c.
Output
The monitor displays per-core active/idle percentages, with the idle column
matching top's id field for easy comparison:
CPU Core | Active % | Idle % (=top id)
-------------------------------------------
CPU 0 | 0.34% | 99.66%
CPU 1 | 21.89% | 78.11%
CPU 2 | 0.15% | 99.85%
CPU 3 | 0.03% | 99.97%
-------------------------------------------
TOTAL AVG | 5.60% | 94.40%
To compare with top, press 1 in top to show per-core stats, then compare
the id column with the Idle % column above.
Prerequisites
Host (build machine)
| Package |
Purpose |
| clang + llvm version 21 |
Compile BPF C source to BPF ELF |
| bpftool |
Generate BFP skeleton headers |
| aarch64 Poky Toolchain |
Corss-compile the user-space binary |
Install on Ubuntu/Debian:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 21
apt update
apt install -y llvm-21 clang-21 lld-21 lldb-21
update-alternatives --install /usr/bin/llvm-link llvm-link /usr/bin/llvm-link-21 100
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-21 100
update-alternatives --install /usr/bin/llc llc /usr/bin/llc-21 100
llvm-link --version
clang --version
# install bpftool
apt install linux-tools-$(uname -r)
Target board
- Tested Linux kernel == 6.18.20-2.0.0
- Tested image == imx-image-full
- Most BPF configurations are enabled by default. In addition, you need CONFIG_FTRACE=y to enable tracepoint support (required for the sched_switch hook).
Build
Build the project on your host machine:
TOOLCHAIN_PATH=/opt/fsl-imx-internal-wayland/6.18-whinlatter/environment-setup-armv8a-poky-linux ./build_and_deploy.sh
The script will:
- Source the Poky toolchain environment
- Run make clean && make all (compiles BPF object, generates skeleton,
- cross-compiles user-space binary)
- Install the binary to ./board_deploy/cpu_monitor
Deploy and run
# Copy binary to the board
scp board_deploy/cpu_monitor root@<board-ip>:/usr/bin/
# Run on the board (root required for eBPF)
ssh root@<board-ip>
./cpu_monitor
Press Ctrl+C to exit.
Benchmarking: eBPF vs top
A scheduler stress test script is included to compare monitoring overhead
between cpu_monitor and top under high scheduling pressure.
Run the stress test
On the target board, open three terminals:
# Terminal 1: start scheduler pressure (200 threads, 30 seconds)
./stress_sched.sh -t 200 -d 30
# Terminal 2: measure eBPF monitoring overhead
perf stat -e cpu-clock,task-clock,context-switches,cpu-migrations,instructions timeout -s KILL 10 ./cpu_monitor
# Terminal 3: measure top monitoring overhead
perf stat -e cpu-clock,task-clock,context-switches,cpu-migrations,instructions top -b -d 1 -n 10
Compare task-clock, instructions, and context-switches from perf stat
output. Try different thread counts to observe scaling behavior:
./stress_sched.sh -t 50 -d 30 # low pressure
./stress_sched.sh -t 200 -d 30 # medium pressure
./stress_sched.sh -t 500 -d 30 # high pressure
Result
Test environment: i.MX943 (4x Cortex-A55), Linux 6.18.20, 10-second measurement window.
Comparison results in free system load:
| Indicator |
eBPF cpu_monitor |
top |
delta |
| task-clock (CPU timing) |
12.0 ms |
263.9 ms |
22x |
| CPU usage |
0.1% |
2.8% |
28x |
| instructions |
3.38M |
197M |
58x |
| context-switches |
12 |
17 |
|
| sys time (kernel time) |
5.0ms |
199.5ms |
40x |
| user time |
0 |
62.1ms |
|
Comparison results in high scheduling pressure (200 threads):
| Indicator |
eBPF cpu_monitor |
top |
delta |
| task-clock (CPU timing) |
12.6ms |
690ms |
54x |
| CPU usage |
0.1% |
6.1% |
61x |
| instructions |
4.8M |
538M |
112x |
| context-switches |
122 |
9019 |
75x |
| sys time (kernel time) |
4ms |
461ms |
115x |
| user time |
0 |
206ms |
|
Project structure
ebpf_cpu_usage/
├── cpu_monitor_bpf.c # Kernel-side eBPF program (C, compiled to BPF)
├── cpu_monitor.c # User-space program (C, cross-compiled to aarch64)
├── Makefile # Build rules
├── build_and_deploy.sh # One-shot build + package script
├── stress_sched.sh # Scheduler stress test for benchmarking
└── README.md