Practical Latency Observability with eBPF
Measuring CPU, memory, and I/O latency inside the Linux kernel
01Problem
The delays that decide end-to-end performance are usually invisible from user space. A process waits in the run queue, stalls on a page fault, or blocks on a device, and from the outside all three look the same: the program is slow while the CPU looks fine. Answering which of the three it is needs timestamps taken inside the kernel, on both sides of the event. This toolkit takes those timestamps for scheduling, memory, and block I/O, and reports each as a latency distribution rather than an average.
02What I built
- Three eBPF tools — runqlat, memlat, iolat — that time run-queue wait, page-fault handling, and block I/O inside the kernel and export log2 latency histograms.
- A shared command-line interface (--duration, --out) and a one-click collection script, so every tool is run the same way and two runs can be compared.
- CSV histograms with a matching .summary.json per run, stored next to the environment metadata for that run.
03How it works
Each tool is an eBPF program attached to a pair of kernel tracepoints plus a small user-space control program. The kernel side stores an entry timestamp in a BPF hash map, computes the delta at the exit tracepoint, and increments a log2 bucket in a histogram map. Nothing is sent to user space per event. The Go collector reads the histogram map once the measurement window closes and serialises it. Nothing in the observed application is modified or recompiled.
04Results
- Scheduling latency shows a long tail under load even when CPU utilisation looks normal, which is exactly the case an average hides.
- Memory access latency stays flat until page faults are induced; then the tail buckets spike while the median barely moves.
- Block I/O latency clusters by request size and device behaviour, so the slow requests are identifiable rather than merely present.
05What went wrong
06Engineering decisions
- Compute deltas in the kernel and export only histograms. Sending one record per event to user space would cost more than the latency being measured.
- Bucket by log2 rather than report an average. The tail is the part users feel, and a mean over a long-tailed distribution reports neither the common case nor the bad one.
- Give all three tools the same flags and the same output format. Comparing a CPU histogram with an I/O histogram is the whole point, and that only works if they are shaped alike.