cloudflare/ebpf_exporter: custom eBPF metrics as Prometheus series
Prometheus exporter for custom eBPF metrics
At a glance
- What is it?
- A libbpf-based Prometheus exporter that turns your own eBPF programs into metrics and OpenTelemetry traces. It is a config-driven tool for kernel engineers who already write BPF C, not a turnkey dashboard.
- Who is it for?
- Adopt ebpf_exporter if you already write BPF C and want those probes scraped by Prometheus without hand-rolling an exporter for each one. Do not adopt it if you want prebuilt metrics for a standard workload; the config format and libbpf toolchain assume you can compile and attach your own programs.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 18 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What ebpf_exporter is for, and who it is not for
The README states the motivation directly: the exporter exists "to allow you to write eBPF code and export metrics that are not otherwise accessible from the Linux kernel." That is the whole scope. You write a BPF C program, describe how its maps should be read, and the exporter exposes the result on a Prometheus endpoint. The README frames it as "bcc tools as prometheus metrics," but built on libbpf rather than the legacy bcc runtime, closer in spirit to the libbpf-tools collection than to the bcc Python front end.
That framing tells you who this is for. If you have a question the kernel can answer only through a probe (block I/O latency distributions, cgroup throttling, accept queue backlog), and no existing exporter surfaces it, this project is the plumbing between your probe and your scrape config. If you want a ready-made set of host metrics, this is the wrong tool: there is no default metric set beyond the examples directory, and the README's examples are written as demonstrations, not as a supported product surface.
The project also produces OpenTelemetry-compatible traces, documented under the tracing directory. Metrics remain the primary path; tracing is the second output, not a replacement.
How a config file becomes a Prometheus series
The mechanism is config-driven and runs in three stages. First, the exporter loads a YAML config that names BPF programs, their attach points, and the maps to read. Second, it compiles and attaches those programs using libbpf, via the libbpfgo binding listed in go.mod. Third, it reads the maps on each scrape and applies decoders to turn raw map keys and values into labeled Prometheus metrics.
The repository layout reflects this split: config/ holds the config parsing, decoder/ holds the code that converts raw map data into metric labels and values, exporter/ holds the scrape and registration logic, and examples/ holds paired .bpf.c and .yaml files. The examples directory is the real documentation of the config format, because each pair shows a program and the YAML that reads it. Decoders matter more than they first appear: a raw map key is often a struct or a numeric index, and the decoder is what turns it into something a PromQL query can group by. The README points at a ksym decoder that needs /proc/kallsyms access, which is a good illustration of how a decoder can pull in a capability requirement of its own.
Because the config names the programs and the maps, the exporter does not decide what to collect. You do. That is the design's main strength and its main cost.
Building ebpf_exporter and running the biolatency example
The README's build path is a Makefile target. Clone the repository and run:
make buildThe default target produces a static binary; a build-dynamic target produces a dynamically linked one. In both cases libbpf is built from source, and you can override that with BUILD_LIBBPF=0 to use the system libbpf instead. If the host toolchain is a problem, the README offers a Docker escape hatch that builds the image and copies the binary out:
docker build --tag ebpf_exporter --target ebpf_exporter .
docker cp $(docker create ebpf_exporter):/ebpf_exporter ./Examples are built separately, and this step needs clang. The Dockerfile pins clang-16 for the examples stage, so the version is not incidental:
make -C examples clean buildOnce the binary and examples exist, the README's first real run uses the biolatency config. It needs sudo because attaching probes needs capabilities:
sudo ./ebpf_exporter --config.dir=examples --config.names=biolatencyTwo flags carry the whole configuration story. --config.dir points at the directory holding YAML files, and --config.names selects which of them to load, by filename without the extension. You should see the exporter start and serve metrics on its HTTP endpoint; adding --debug also exposes raw maps at the /maps endpoint and turns on libbpf debug output, which is the practical way to find out why a map is empty.
Running the official image with the timers example
The README documents a prebuilt image on GitHub Container Registry, which avoids the libbpf build entirely. The published invocation mounts the examples directory read-only and the cgroup filesystem, then runs the timers config:
docker run --rm -it --privileged -p 9435:9435 \
-v $(pwd)/examples:/examples \
-v /sys/fs/cgroup:/sys/fs/cgroup:ro \
ebpf_exporter --config.dir=examples --config.names=timersThe port mapping is 9435, and the container runs privileged. The README is explicit that bind mounts beyond these two depend on what your programs touch, and that simple kprobe examples may need no mounts at all. For development without host tooling, the ebpf_exporter_with_examples build target bakes the compiled examples into the image, so the same run command works without the examples mount. The README also notes the equivalent prebuilt invocation against ghcr.io/cloudflare/ebpf_exporter.
For production the README's guidance is to bind-mount your own config and compiled BPF programs, or build a derived image with your config baked in. Treat the examples image as a diagnostic tool, not a deployment artifact.
Capabilities: the part that decides your deployment shape
The README is unusually precise here, and it is the section worth reading twice. Running as root is possible but not required. Normal operation needs CAP_BPF for privileged BPF operations and memory reads, and CAP_PERFMON to attach programs to kprobes and tracepoints. On kernels older than Linux v5.8 those two capabilities do not exist separately, so CAP_SYS_ADMIN substitutes for both.
The interesting option is --capabilities.keep=none, which drops all capabilities after attaching probes and leaves the process fully unprivileged. That is a meaningfully smaller attack surface than a long-lived root exporter, and it is the strongest argument for this project's privilege model.
The complication is that some decoders and map types pull capabilities back in, and those cannot be dropped the same way. The README lists CAP_SYSLOG for the ksym decoder reading /proc/kallsyms, CAP_IPC_LOCK for perf_event_array reads, CAP_SYS_ADMIN for BTF information from modules, CAP_NET_ADMIN for net admin programs such as XDP, CAP_SYS_RESOURCE on pre-v5.11 kernels without memcg accounting for BPF memory, and CAP_DAC_READ_SEARCH for fanotify-based cgroup monitoring, which the README calls the preferred method but notes is only available since Linux v6.6. Several of these must be kept explicitly through --capabilities.keep, which means your config choices determine your privilege set. For systemd deployments the README gives a unit fragment using DynamicUser=true with AmbientCapabilities and CapabilityBoundingSet set to CAP_BPF CAP_PERFMON.
Where it breaks: BTF, kernels and config drift
The clearest failure mode is documented rather than hidden. Executing eBPF programs requires kernel data types normally available at /sys/kernel/btf/vmlinux, a file created during the kernel build. On some older kernel configurations that file is missing, and the README addresses external BTF support for exactly that case. If your fleet spans kernel versions, this is the first thing to check, not the last: a missing BTF file turns into an attach failure, not a degraded metric.
The Makefile shows the second class of problem. Its check target excludes specific configs from CI for concrete reasons: cachestat-pre-kernel-5.16 fails to attach on newer kernels, llcstat requires real hardware to attach perf events, and cgroup-rstat-flushing depends on kernel v6.10. On aarch64, unix-socket-backlog is also excluded because it depends on kernel v6.16. These are named exceptions in the build logic, which tells you the examples are not portable across every kernel, and by extension neither are configs derived from them.
The third problem is operational. A config file names programs and maps; if the compiled BPF object and the YAML drift apart, you get attach errors or empty maps rather than a clear message. The --debug flag with its /maps endpoint exists for this reason, and it is the tool you will actually reach for when a series stops appearing.
How it differs from bcc and from generic host exporters
The README's own comparison is bcc, and the difference is architectural. bcc ships a Python front end that compiles BPF C at runtime through LLVM, which is convenient for interactive use and awkward to embed in a long-running service. This project uses libbpf, the same direction as the libbpf-tools collection, which means the BPF side is compiled ahead of time and the exporter is a Go binary with a C dependency on libbpf. The practical consequence is a smaller runtime footprint and no Python or LLVM in the container, at the cost of a build step for every program you add.
The second comparison is against generic exporters such as node_exporter or process-exporter. Those report what the kernel already exposes through /proc and /sys. This project exists for the cases where the kernel does not expose it, which is why the README's motivation sentence is about metrics "not otherwise accessible from the Linux kernel." If node_exporter already answers your question, adding a BPF program and a YAML decoder is work with no payoff.
A third option is writing your own exporter against libbpfgo. That is the honest alternative if your needs are narrow: the dependency is public, and the value this project adds is the config format, the decoder library and the Prometheus and OpenTelemetry wiring around it. If you only ever need one probe and one metric, that wiring is not a large amount of code.
Editorial conclusion
Adopt ebpf_exporter if you already write BPF C and want those probes scraped by Prometheus without hand-rolling an exporter for each one. Do not adopt it if you want prebuilt metrics for a standard workload; the config format and libbpf toolchain assume you can compile and attach your own programs. Before committing, verify that your kernel exposes /sys/kernel/btf/vmlinux, that CAP_BPF and CAP_PERFMON are available to the service user, and that the config names you pass to --config.names match files in --config.dir.
Frequently asked questions
What is cloudflare/ebpf_exporter used for?
It exports custom eBPF metrics as Prometheus series and can also produce OpenTelemetry-compatible traces. The README states its motivation is to let you write eBPF code and export metrics that are not otherwise accessible from the Linux kernel.
How do I install cloudflare/ebpf_exporter?
Clone the repository and run make build for a static binary, or use the build-dynamic target for a dynamically linked one. A Docker build target and a prebuilt image on GitHub Container Registry are also documented in the README.
What capabilities does cloudflare/ebpf_exporter need?
Normal operation needs CAP_BPF and CAP_PERFMON, or CAP_SYS_ADMIN on kernels older than Linux v5.8. Additional capabilities such as CAP_SYSLOG, CAP_IPC_LOCK, CAP_NET_ADMIN and CAP_DAC_READ_SEARCH may be required depending on the decoders and program types you use.
Can cloudflare/ebpf_exporter run as a non-root user?
Yes. The README gives a systemd configuration using DynamicUser=true with CAP_BPF and CAP_PERFMON as ambient capabilities, and notes that the --capabilities.keep=none flag drops all capabilities after attaching probes.
What is eBPF used for?
The README quotes ebpf.io describing eBPF as a way to run sandboxed programs in a privileged context such as the kernel, extending kernel capabilities without changing kernel source or loading modules. This exporter uses it to collect metrics the kernel does not otherwise expose.
Is eBPF safe?
The README does not make a safety claim of its own; it quotes ebpf.io describing eBPF as running sandboxed programs safely and efficiently. On the exporter side, the documented control is dropping capabilities after attaching probes with --capabilities.keep=none.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cloudflare-ebpf-exporter)
Community notes