├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md └── src ├── count.c ├── go.sum ├── main.go └── vmlinux.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | RUN apt-get update 3 | RUN apt-get install -y clang llvm libbpf-dev libelf-dev libpcap-dev build-essential make 4 | RUN apt-get install -y linux-tools-common 5 | 6 | # Install dependencies 7 | RUN apt-get install -y curl git wget 8 | 9 | # Set up Go version and architecture dynamically based on the target architecture 10 | ARG GO_VERSION=1.23.1 11 | ARG TARGETARCH 12 | 13 | # Download and install Go based on the target architecture 14 | RUN case "$TARGETARCH" in \ 15 | "amd64") GO_ARCH="amd64" ;; \ 16 | "arm64") GO_ARCH="arm64" ;; \ 17 | "arm") GO_ARCH="armv6l" ;; \ 18 | *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \ 19 | esac && \ 20 | wget https://go.dev/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \ 21 | tar -C /usr/local -xzf go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \ 22 | rm go${GO_VERSION}.linux-${GO_ARCH}.tar.gz 23 | 24 | # Set Go environment variables 25 | ENV PATH="/usr/local/go/bin:${PATH}" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2024, Teodor Janez Podobnik 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eBPF Map Metrics Prometheus Exporter 2 | 3 | ## Development Status 4 | 5 | This project is currently under development. 6 | 7 | It requires `6.6+` Linux Kernel, due to `bpf_map_sum_elem_count` kfunc. 8 | 9 | ![Infra-9](https://github.com/user-attachments/assets/de0a70c1-1fbb-498c-b3de-80c1d1c0bf7b) 10 | 11 | ## How to Run 12 | 13 | To run the program, follow these steps: 14 | 15 | - First build and run the docker container with all the dependencies: 16 | ``` 17 | docker buildx create --name mybuilder --bootstrap --use 18 | docker buildx build --push --platform linux/arm64,linux/amd64 --tag dorkamotorka/ubuntu-ebpf -f Dockerfile . 19 | docker run --rm -it -v ~/ebpf-map-metrics/src:/ebpf-map-metrics --privileged -h test --name test --env TERM=xterm-color dorkamotorka/ubuntu-ebpf 20 | ``` 21 | 22 | - Exec into the container: 23 | ``` 24 | cd ebpf-map-metrics 25 | go generate 26 | go build 27 | sudo ./map-exporter 28 | ``` 29 | 30 | - You can then test trigger actions on eBPF map using: 31 | 32 | ``` 33 | sudo bpftool map 34 | sudo bpftool map update id key 0 0 0 0 value 1 0 0 0 35 | sudo bpftool map delete id key 0 0 0 0 36 | sudo bpftool map lookup id key 0 0 0 0 37 | ``` 38 | 39 | ## eBPF Iterators 40 | 41 | eBPF Iterators are a powerful feature that allows developers to iterate over kernel data structures efficiently. 42 | They facilitate detailed inspection and analysis by enabling safe traversal of complex data structures, 43 | making it easier to collect metrics, debug, and perform various monitoring tasks within the kernel space (and send it back to the user space). 44 | -------------------------------------------------------------------------------- /src/count.c: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | #include "vmlinux.h" 3 | #include 4 | 5 | __s64 bpf_map_sum_elem_count(struct bpf_map *map) __ksym; 6 | 7 | SEC("iter/bpf_map") 8 | int dump_bpf_map(struct bpf_iter__bpf_map *ctx) { 9 | struct seq_file *seq = ctx->meta->seq; 10 | __u64 seq_num = ctx->meta->seq_num; 11 | struct bpf_map *map = ctx->map; 12 | 13 | if (!map) { 14 | return 0; 15 | } 16 | 17 | BPF_SEQ_PRINTF(seq, "%4u %-16s %10d %10lld\n", map->id, map->name, 18 | map->max_entries, bpf_map_sum_elem_count(map)); 19 | 20 | return 0; 21 | } 22 | 23 | char _license[] SEC("license") = "GPL"; 24 | -------------------------------------------------------------------------------- /src/go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= 6 | github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= 7 | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= 8 | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 9 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 10 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 11 | github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= 12 | github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 13 | github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= 14 | github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= 15 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 16 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 17 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 18 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 19 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 20 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 21 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 22 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 23 | github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= 24 | github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= 25 | github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= 26 | github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 27 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 28 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 29 | github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= 30 | github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 31 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 32 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 33 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 34 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 35 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 36 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 37 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 38 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 39 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= 40 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 41 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 42 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 43 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 44 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 45 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 46 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 47 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 48 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 49 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go count count.c 4 | 5 | import ( 6 | "bufio" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/cilium/ebpf/link" 13 | "github.com/cilium/ebpf/rlimit" 14 | "github.com/prometheus/client_golang/prometheus" 15 | "github.com/prometheus/client_golang/prometheus/promhttp" 16 | ) 17 | 18 | const ( 19 | UPDATE_INTERVAL = 1 // sec 20 | ) 21 | 22 | var ( 23 | mapElemCountGauge = prometheus.NewGaugeVec( 24 | prometheus.GaugeOpts{ 25 | Name: "ebpf_map_curr_elem_count", 26 | Help: "Current number of elements in eBPF maps, labeled by map ID and name", 27 | }, 28 | []string{"id", "name"}, 29 | ) 30 | 31 | mapPressureGauge = prometheus.NewGaugeVec( 32 | prometheus.GaugeOpts{ 33 | Name: "ebpf_map_pressure", 34 | Help: "Current pressure of eBPF maps (currElements / maxElements), labeled by map ID and name", 35 | }, 36 | []string{"id", "name"}, 37 | ) 38 | ) 39 | 40 | func main() { 41 | reg := prometheus.NewRegistry() 42 | reg.MustRegister(mapElemCountGauge) 43 | reg.MustRegister(mapPressureGauge) 44 | 45 | if err := rlimit.RemoveMemlock(); err != nil { 46 | log.Fatalf("Failed to remove memlock limit: %v", err) 47 | } 48 | 49 | objs := countObjects{} 50 | if err := loadCountObjects(&objs, nil); err != nil { 51 | log.Fatalf("Failed to load objects: %v", err) 52 | } 53 | defer objs.Close() 54 | 55 | // Attach the program to the Iterator hook. 56 | iterLink, err := link.AttachIter(link.IterOptions{ 57 | Program: objs.DumpBpfMap, 58 | }) 59 | if err != nil { 60 | log.Fatalf("Failed to attach eBPF program: %v", err) 61 | } 62 | defer iterLink.Close() 63 | log.Println("eBPF program attached successfully.") 64 | 65 | // Start HTTP server for Prometheus metrics 66 | handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) 67 | http.Handle("/metrics", handler) 68 | go func() { 69 | log.Fatal(http.ListenAndServe(":2112", nil)) 70 | }() 71 | log.Println("Prometheus HTTP server started on :2112") 72 | 73 | // Keep the program running. 74 | for { 75 | time.Sleep(UPDATE_INTERVAL * time.Second) 76 | reader, err := iterLink.Open() 77 | if err != nil { 78 | log.Fatalf("Failed to open BPF iterator: %v", err) 79 | } 80 | defer reader.Close() 81 | 82 | scanner := bufio.NewScanner(reader) 83 | for scanner.Scan() { 84 | // Variables to store the parsed values 85 | var id int 86 | var name string 87 | var maxElements int 88 | var currElements int64 89 | 90 | // Parse the line 91 | line := scanner.Text() 92 | length, err := fmt.Sscanf(line, "%4d %s %10d %10d", &id, &name, &maxElements, &currElements) 93 | if err != nil || length != 4 { 94 | log.Fatal(err) 95 | } 96 | 97 | // Update the metrics 98 | idStr := fmt.Sprintf("%d", id) 99 | mapElemCountGauge.WithLabelValues(idStr, name).Set(float64(currElements)) 100 | mapPressureGauge.WithLabelValues(idStr, name).Set(float64(currElements) / float64(maxElements)) 101 | } 102 | 103 | if err := scanner.Err(); err != nil { 104 | log.Fatal(err) 105 | } 106 | } 107 | } 108 | --------------------------------------------------------------------------------