cloudflare/ebpf_exporter

GitHub: cloudflare/ebpf_exporter

一个基于 libbpf 的 Prometheus exporter,用于将自定义的 eBPF 内核指标和 OpenTelemetry 链路数据导出,实现对 Linux 系统底层的深度可观测。

Stars: 2588 | Forks: 274

# ebpf_exporter 用于自定义 eBPF 指标和 OpenTelemetry 链路的 Prometheus exporter。 * 指标: ![metrics](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/179575077b195604.png) * [链路](./tracing): ![tracing](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/76523755e8195616.png) 该 exporter 的初衷是允许你编写 eBPF 代码,并导出那些通常无法从 Linux 内核获取的指标。 [ebpf.io](https://ebpf.io/what-is-ebpf/) 对 eBPF 进行了描述: 理解这个 exporter 的简单方式是将其看作 bcc 工具集的 Prometheus 指标版本: * https://iovisor.github.io/bcc 我们使用 libbpf 而不是传统的 bcc 驱动代码,因此它更像 libbpf-tools: * https://github.com/iovisor/bcc/tree/master/libbpf-tools 同时也支持生成兼容 [OpenTelemetry](https://opentelemetry.io/) 的链路,详情请参阅[链路文档](./tracing/)。 ## 阅读材料 * https://www.brendangregg.com/ebpf.html * https://nakryiko.com/posts/bpf-core-reference-guide/ * https://nakryiko.com/posts/bpf-portability-and-co-re/ * https://nakryiko.com/posts/bcc-to-libbpf-howto-guide/ * https://libbpf.readthedocs.io/en/latest/program_types.html ## 构建与运行 ### 实际构建 要构建二进制文件,请克隆仓库并运行: ``` make build ``` 默认的 `build` 目标会生成一个静态二进制文件,但如果你希望生成动态链接的二进制文件,也可以使用 `build-dynamic` 目标。 无论哪种情况,`libbpf` 都是从源码构建的,但如果你想使用系统的 `libbpf`,可以通过设置 `BUILD_LIBBPF=0` 来覆盖此行为。 如果你在主机上构建时遇到问题,可以尝试在 Docker 中构建: ``` docker build --tag ebpf_exporter --target ebpf_exporter . docker cp $(docker create ebpf_exporter):/ebpf_exporter ./ ``` 要构建示例(请参阅[构建示例部分](#building-examples)): ``` make -C examples clean build ``` 使用 [`biolatency`](examples/biolatency.yaml) 配置运行: ``` sudo ./ebpf_exporter --config.dir=examples --config.names=biolatency ``` 如果你传入 `--debug` 参数,你可以在 `/maps` 端点看到原始 maps,并看到 `libbpf` 自身的调试输出。 ### Docker 镜像 可以从本仓库构建 Docker 镜像。GitHub Container Registry 也提供了包含示例的预构建镜像以供下载: * https://github.com/cloudflare/ebpf_exporter/pkgs/container/ebpf_exporter 要仅构建包含 exporter 二进制文件的镜像,请运行以下命令: ``` docker build --tag ebpf_exporter --target ebpf_exporter . ``` 要带示例运行它,你需要先构建它们(见上文)。 然后你可以通过运行一个特权容器并进行绑定挂载来执行: * `$(pwd)/examples:/examples:ro` 以允许访问主机上的示例 * `/sys/fs/cgroup:/sys/fs/cgroup:ro` 以允许解析 cgroup 根据你的需求,你可能需要绑定挂载额外的目录。 对于简单的 kprobe 示例,你可能也不需要绑定挂载任何东西。 运行 docker 容器的实际命令(从仓库目录执行): ``` 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=timers ``` 在生产环境中,你要么绑定挂载自己的配置和相应的已编译 bpf 程序,要么基于我们的镜像构建一个内嵌你自己配置的镜像。 在开发环境中,当主机上没有或不想要任何开发工具时,你可以构建捆绑了示例的 docker 镜像: ``` docker build --tag ebpf_exporter --target ebpf_exporter_with_examples . ``` 这样一些示例就可以在没有任何绑定挂载的情况下运行: ``` docker run --rm -it --privileged -p 9435:9435 \ ebpf_exporter --config.dir=examples --config.names=timers ``` 或者使用公开可用的预构建镜像: ``` docker run --rm -it --privileged -p 9435:9435 \ ghcr.io/cloudflare/ebpf_exporter --config.dir=examples --config.names=timers ``` ## Kubernetes Helm chart 这里有一个第三方 helm chart 可供使用: * https://github.com/kubeservice-stack/kubservice-charts/tree/master/charts/kubeservice-ebpf-exporter 请注意,该 helm chart 不是由 Cloudflare 提供或支持的,因此请自行尽职调查并自担风险使用。 ## 基准测试开销 请参阅 [benchmark](benchmark) 目录,以了解 ebpf 的开销有多低。 ## 所需 capabilities 虽然你可以以 `root` 身份运行 `ebpf_exporter`,但这并不是严格必须的。 正常运行只需以下两个 capabilities: * `CAP_BPF`:用于特权 bpf 操作和读取内存 * `CAP_PERFMON`:用于将 bpf 程序附加到 kprobes 和 tracepoints 如果你使用的是 `systemd`,则可以使用以下配置以拥有所需 capabilities 的非特权动态用户身份运行: ``` DynamicUser=true AmbientCapabilities=CAP_BPF CAP_PERFMON CapabilityBoundingSet=CAP_BPF CAP_PERFMON ``` 在 Linux v5.8 之前,没有专门的 `CAP_BPF` 和 `CAP_PERFMON`,但如果你的内核较旧,可以使用 `CAP_SYS_ADMIN` 代替。 如果你将 `--capabilities.keep=none` 标志传递给 `ebpf_expoter`,它会在附加探针后丢弃所有 capabilities,使其完全无特权。 可能还需要以下额外的 capabilities: * `CAP_SYSLOG`:如果你使用 `ksym` 解码器来访问 `/proc/kallsyms`。 请注意,你必须保留此 capability:`--capabilities.keep=cap_syslog`。 参见:https://elixir.bootlin.com/linux/v6.4/source/kernel/kallsyms.c#L982 * `CAP_IPC_LOCK`:如果你使用 `perf_event_array` 从内核读取数据。 请注意,你必须保留它:`--capabilities.keep=cap_perfmon,cap_ipc_lock`。 * `CAP_SYS_ADMIN`:如果你想从模块中获取 BTF 信息。 参见:https://github.com/libbpf/libbpf/blob/v1.2.0/src/libbpf.c#L8654-L8666 和 https://elixir.bootlin.com/linux/v6.5-rc1/source/kernel/bpf/syscall.c#L3789 * `CAP_NET_ADMIN`:如果你使用与网络管理相关的程序,例如 xdp。 参见:https://elixir.bootlin.com/linux/v6.4/source/kernel/bpf/syscall.c#L3787 * `CAP_SYS_RESOURCE`:如果你在缺少 bpf 内存 memcg 记账功能的较旧内核上运行。上游 Linux 内核在 v5.11 中添加了对它的支持。 参见:https://github.com/libbpf/libbpf/blob/v1.2.0/src/bpf.c#L98-L106 * `CAP_DAC_READ_SEARCH`:如果你想使用 `fanotify` 监控 cgroup 的变更,这是首选的方式,但仅在 Linux v6.6 之后可用。 参见:https://github.com/torvalds/linux/commit/0ce7c12e88cf ## 外部 BTF 支持 eBPF 程序的执行需要通常在 `/sys/kernel/btf/vmlinux` 中可用的内核数据类型,该文件是在内核构建过程中创建的。 然而,在某些较旧的内核配置中,此文件可能不可用。 如果遇到这种情况,可以通过 `--btf.path` 提供外部 BTF 文件。 可以在[此处](https://github.com/aquasecurity/btfhub-archive)找到一些旧发行版和内核版本的 BTF 归档。 ## 支持的场景 目前,从内核获取数据的唯一支持方式是通过 maps。 请参阅[示例](#examples)部分获取真实示例。 如果你有想要分享的示例,请随时发起 PR。 ## 配置 跳至[格式](#configuration-file-format)以查看完整规范。 ### 示例 你可以在 [examples](examples) 目录中找到更多示例。 除非另有说明,否则所有示例均预期可在 Linux 5.15 上运行,这是在撰写本文时最新的 LTS 版本。得益于 CO-RE,这些示例也应当能在任何启用了 BTF 的现代内核上运行。 你可以在 `libbpf` README 中找到受支持发行版的列表: * https://github.com/libbpf/libbpf#bpf-co-re-compile-once--run-everywhere #### 构建示例 要构建示例,请运行: ``` make -C examples clean build ``` 这将使用 `clang` 和我们在本仓库中提供的 `vmlinux.h` 构建示例(有关 `vmlinux.h` 的更多信息,请参阅 [include](include/README.md))。 示例在使用前必须经过编译。 请注意,编译后的示例可以直接在任何启用了 BTF 的内核上使用,没有运行时依赖。大多数现代 Linux 发行版都默认启用了它。 #### 通过 tracepoints 的计时器(计数器) 此配置附加到计时器子系统的内核 tracepoints,并统计按计时器名称细分的触发次数。 结果指标: ``` # HELP ebpf_exporter_timer_starts_total 内核中触发的计时器 # TYPE ebpf_exporter_timer_starts_total counter ebpf_exporter_timer_starts_total{function="blk_stat_timer_fn"} 10 ebpf_exporter_timer_starts_total{function="commit_timeout [jbd2]"} 1 ebpf_exporter_timer_starts_total{function="delayed_work_timer_fn"} 25 ebpf_exporter_timer_starts_total{function="dev_watchdog"} 1 ebpf_exporter_timer_starts_total{function="mix_interrupt_randomness"} 3 ebpf_exporter_timer_starts_total{function="neigh_timer_handler"} 1 ebpf_exporter_timer_starts_total{function="process_timeout"} 49 ebpf_exporter_timer_starts_total{function="reqsk_timer_handler"} 2 ebpf_exporter_timer_starts_total{function="tcp_delack_timer"} 5 ebpf_exporter_timer_starts_total{function="tcp_keepalive_timer"} 6 ebpf_exporter_timer_starts_total{function="tcp_orphan_update"} 16 ebpf_exporter_timer_starts_total{function="tcp_write_timer"} 12 ebpf_exporter_timer_starts_total{function="tw_timer_handler"} 1 ebpf_exporter_timer_starts_total{function="writeout_period"} 5 ``` 对应的配置文件: ``` metrics: counters: - name: timer_starts_total help: Timers fired in the kernel labels: - name: function size: 8 decoders: - name: ksym ``` 以及相应的、会被编译为包含 eBPF 字节码的 ELF 文件的 C 代码: ``` #include #include #include "maps.bpf.h" struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 1024); __type(key, u64); __type(value, u64); } timer_starts_total SEC(".maps"); SEC("tp_btf/timer_start") int BPF_PROG(timer_start, struct timer_list *timer) { u64 function = (u64) timer->function; increment_map(&timer_starts_total, &function, 1); return 0; } char LICENSE[] SEC("license") = "GPL"; ``` #### 块 IO 直方图(直方图) 此配置附加到块 IO 子系统,并将磁盘延迟作为 prometheus 直方图报告,允许你计算百分位数。 以下工具采用了类似的概念: * https://github.com/iovisor/bcc/blob/master/tools/biosnoop_example.txt * https://github.com/iovisor/bcc/blob/master/tools/biolatency_example.txt * https://github.com/iovisor/bcc/blob/master/tools/bitesize_example.txt 这个程序是开发该 exporter 的最初动机,并深受 Daniel Swarbrick 的实验性 exporter 的影响: * https://github.com/dswarbrick/ebpf_exporter 结果指标: ``` # HELP ebpf_exporter_bio_latency_seconds 块 IO 延迟直方图 # TYPE ebpf_exporter_bio_latency_seconds histogram ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="1e-06"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="2e-06"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="4e-06"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="8e-06"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="1.6e-05"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="3.2e-05"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="6.4e-05"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.000128"} 22 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.000256"} 36 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.000512"} 40 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.001024"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.002048"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.004096"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.008192"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.016384"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.032768"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.065536"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.131072"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.262144"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="0.524288"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="1.048576"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="2.097152"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="4.194304"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="8.388608"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="16.777216"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="33.554432"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="67.108864"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="134.217728"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme0n1",operation="write",le="+Inf"} 48 ebpf_exporter_bio_latency_seconds_sum{device="nvme0n1",operation="write"} 0.021772 ebpf_exporter_bio_latency_seconds_count{device="nvme0n1",operation="write"} 48 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="1e-06"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="2e-06"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="4e-06"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="8e-06"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="1.6e-05"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="3.2e-05"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="6.4e-05"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.000128"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.000256"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.000512"} 0 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.001024"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.002048"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.004096"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.008192"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.016384"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.032768"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.065536"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.131072"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.262144"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="0.524288"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="1.048576"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="2.097152"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="4.194304"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="8.388608"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="16.777216"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="33.554432"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="67.108864"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="134.217728"} 1 ebpf_exporter_bio_latency_seconds_bucket{device="nvme1n1",operation="write",le="+Inf"} 1 ebpf_exporter_bio_latency_seconds_sum{device="nvme1n1",operation="write"} 0.0018239999999999999 ebpf_exporter_bio_latency_seconds_count{device="nvme1n1",operation="write"} 1 ``` 你可以用 Grafana 绘制出漂亮的图表: ![Histogram](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/179575077b195604.png) ## 配置概念 `ebpf_exporter` 中存在以下概念。 ### 配置项 配置项描述了如何从内核中提取指标。每个配置项都有相应的在内核中运行的 eBPF 代码来生成这些指标。 可以同时加载多个配置项。 ### 指标 指标定义了我们从运行在内核中的 eBPF 程序获取的值。 #### 计数器 来自 maps 的计数器是直接转换:你从内核中提取数据,将 map 键转换为一组标签,并将它们作为 prometheus 计数器导出。 #### 直方图 来自 maps 的直方图比计数器稍微复杂一些。内核中的 maps 不能嵌套,因此我们需要在内核中打包键,并在用户空间中解包。 我们从这个: ``` sda, read, 1ms -> 10 ops sda, read, 2ms -> 25 ops sda, read, 4ms -> 51 ops ``` 变成这个: ``` sda, read -> [1ms -> 10 ops, 2ms -> 25 ops, 4ms -> 51 ops] ``` Prometheus 直方图期望在报告指标时包含所有桶,但内核在事件发生时创建键,这意味着我们需要回填缺失的数据。 这就是为什么在直方图配置中我们有以下键: * `bucket_type`:可以是 `exp2`、`exp2zero`、`linear` 或 `fixed` * `bucket_min`:最小桶键(仅限 `exp2`、`exp2zero` 和 `linear`) * `bucket_max`:最大桶键(仅限 `exp2`、`exp2zero` 和 `linear`) * `bucket_keys`:最大桶键(仅限 `fixed`) * `bucket_multiplier`:桶键的乘数(默认为 `1`) ##### `exp2` 直方图 对于 `exp2` 直方图,我们期望内核提供一个具有线性键的 map,这些键是实际值的以 2 为底的对数。然后我们在用户空间中从 `bucket_min` 遍历到 `bucket_max`,并通过指数运算重新映射键: ``` count = 0 for i = bucket_min; i < bucket_max; i++ { count += map.get(i, 0) result[exp2(i) * bucket_multiplier] = count } ``` 这里的 `map` 是来自内核的 map,`result` 是传递给 prometheus 的数据。 在 ebpf 中使用 `increment_exp2_histogram` 来观测值。 ##### `exp2zero` 直方图 这些与 `exp2` 直方图相同,除了: * 第一个键对应值 `0` * 所有其他键比预期值大 `1` 在 ebpf 中使用 `increment_exp2zero_histogram` 来观测值。 ##### `linear` 直方图 对于 `linear` 直方图,我们期望内核提供一个具有线性键的 map,这些键是原始值除以 `bucket_multiplier` 的整数除法结果。 为了在用户空间中重建直方图,我们执行以下操作: ``` count = 0 for i = bucket_min; i < bucket_max; i++ { count += map.get(i, 0) result[i * bucket_multiplier] = count } ``` ##### `fixed` 直方图 对于 `fixed` 直方图,我们期望内核提供一个由用户定义固定键的 map。 ``` count = 0 for i = 0; i < len(bucket_keys); i++ { count += map.get(bucket_keys[i], 0) result[bucket_keys[i] * multiplier] = count } ``` ##### `sum` 键 对于 `exp2` 和 `linear` 直方图,如果 `bucket_max + 1` 包含非零值,它将被用作直方图中的 `sum` 键,从而提供额外信息并允许更丰富的指标。 对于 `fixed` 直方图,如果 `buckets_keys[len(bucket_keys) - 1 ] + 1` 包含非零值,它将被用作 `sum` 键。 ### 标签 标签将内核 map 键转换为 prometheus 标签。 来自内核的 maps 是二进制编码的。值始终是 `u64`,但键可以是像 `u64` 这样的原始类型或复杂的 `struct`。 每个标签都可以根据指标配置使用解码器(见下文)进行转换。通常,标签的数量与内核 map 键中的元素数量相匹配。 对于表示为 `struct` 的 map 键,适用对齐规则: * `u64` 必须在 8 字节边界上对齐 * `u32` 必须在 4节边界上对齐 * `u16` 必须在 2 字节边界上对齐 这意味着以下结构体: ``` struct disk_latency_key_t { u32 dev; u8 op; u64 slot; }; ``` 表示为: * 4 字节的 `dev` 整数 * 1 字节的 `op` 整数 * 3 字节用于对齐 `slot` 的填充 * 8 字节的 `slot` 整数 解码时,要么使用键 `padding` 显式指定填充,要么将其包含在标签大小中: * `dev` 为 4 * `op` 为 4(1 字节值 + 3 字节填充) * `slot` 为 8 字节 ### 解码器 解码器接收请求长度的字节切片输入,并将其转换为表示字符串的字节切片。该字节切片可以被另一个解码器(例如 `string` -> `regexp`)使用,或者用作导出到 Prometheus 的最终标签值。 以下是我们内置的解码器。 #### `cgroup` 使用 cgroup 解码器,你可以将来自 `bpf_get_current_cgroup_id` 的 u64 转换为人类可读的表示 cgroup 路径的字符串,例如: * `/sys/fs/cgroup/system.slice/ssh.service` #### ifname Ifname 解码器接收网络接口索引并将其转换为名称,例如 `eth0`。 #### `dname` Dname 解码器从 wire 格式的字符串中读取 DNS qname,然后将其解码为 '.' 符号格式。可以在 `string` 解码器之后使用。 例如:`\x07example\03com\x00` 将变为 `example.com`。这个解码器可以在 `string` 解码之后使用,如下例所示: ``` - name: qname decoders: - name: string - name: dname ``` #### `errno` Errno 解码器将 `errno` 编号转换为字符串表示形式,例如 `EPIPE`。它通常以 `unit` 解码器作为第一步与之配对使用。 ### `hex` Hex 解码器将字节转换为其十六进制表示形式。 #### `inet_ip` 网络 IP 解码器可以将内核操作的、以字节编码的 IPv4 和 IPv6 地址转换为人类可读的形式,例如 `1.1.1.1`。 #### `ksym` KSym 解码器接收内核地址并将其转换为函数名称。 在你的 eBPF 程序中,你可以使用 `PT_REGS_IP_CORE(ctx)` 将你附加到的函数的地址作为 `u64` 变量获取。请注意,对于 kprobes,你需要使用来自 `regs-ip.bpf.h` 的 `KPROBE_REGS_IP_FIX()` 对其进行包装。 #### majorminor 通过 major-minor 解码器,你可以将内核中主次设备号的组合 u32 视图转换为 `/dev` 中的设备名称。 ### pci_vendor 通过 `pci_vendor` 解码器,你可以将 PCI 厂商 ID(如 0x8086)转换为人类可读的厂商名称(如 `Intel Corporation`)。 ### pci_device 通过 `pci_vendor` 解码器,你可以将 PCI 厂商 ID(如 0x80861000)转换为人类可读的名称(如 `82542 Gigabit Ethernet Controller (Fiber)`)。 请注意,你需要为此将厂商 ID 和设备 ID 拼接在一起。 ### pci_class 通过 `pci_class` 解码器,你可以将 PCI 类 ID(最低字节)转换为类名称,如 `Network controller`。 ### pci_subclass 通过 `pci_subclass` 解码器,你可以将 PCI 子类(最低的两个字节)转换为子类名称,如 `Ethernet controller`。 #### `regexp` Regexp 解码器接收解码器的 `regexp` 配置键中的字符串列表,并尝试将每个字符串作为 `golang.org/pkg/regexp` 中的模式使用: * https://golang.org/pkg/regexp 如果解码器输入与任何模式匹配,则允许通过。 否则,整个指标标签集将被丢弃。 一个仅针对 `systemd-journal` 和 `syslog-ng` 报告指标的示例: ``` - name: command decoders: - name: string - name: regexp regexps: - ^(kswapd).*$ # if sub-matches are present, the first one is used for the value - ^systemd-journal$ - ^syslog-ng$ ``` #### `static_map` Static map 解码器接收输入,并通过解码器的 `static_map` 配置键将其映射到另一个值。值应为字符串。 一个将 `1` 映射为 `read`,`2` 映射为 `write` 的示例: ``` - name: operation decoders: - name:static_map static_map: 1: read 2: write ``` 未知键将被替换为 `"unknown:key_name"`,除非在解码器中指定了 `allow_unknown: true`。例如,上面的配置会将 `3` 解码为 `unknown:3`,而下面的示例会将 `3` 解码为 `3`: ``` - name: operation decoders: - name:static_map allow_unknown: true static_map: 1: read 2: write ``` #### `string` String 解码器将来自内核的、可能以 null 终止的字符串转换为可用于 prometheus 指标的字符串。 ### `syscall` Syscall 解码器将系统调用号转换为系统调用名称。 这些表可以通过 `make syscalls` 重新生成。参见 `scripts/mksyscalls`。 #### `uint` UInt 解码器将来自内核的十六进制编码的 `uint` 值转换为常规的十进制数字。例如:`0xe -> 14`。 ## Per CPU map 支持 完全支持读取 Per CPU map。如果 percpu map 的最后一个解码器名为 `cpu`(使用 2 字节的 `uint` 解码器),则会自动添加 `cpu` 标签。如果不存在,则 percpu 计数器将聚合为一个全局计数器。 示例中提供了 [percpu-softirq](examples/percpu-softirq.bpf.c)。 参见 #226 以了解其不同运行模式的示例。 ### 配置文件格式 配置文件定义如下: ``` # 附加到程序的指标 [ metrics: metrics ] # 从 /proc/kallsyms 定义为 kaddr_{symbol} 的内核符号地址 (考虑 CONFIG_KALLSYMS_ALL) kaddrs: [ - symbol_to_resolve ] ``` #### `metrics` 有关更多详细信息,请参阅[指标](#metrics)部分。 ``` counters: [ - counter ] histograms: [ - histogram ] ``` #### `counter` 有关更多详细信息,请参阅[计数器](#counters)部分。 ``` name: help: perf_event_array: flush_interval: labels: [ - label ] ``` 可以在[此处](examples/oomkill.yaml)找到 `perf_map` 的示例。 #### `histogram` 有关更多详细信息,请参阅[直方图](#histograms)部分。 ``` name: help: bucket_type: bucket_multiplier: bucket_min: bucket_max: labels: [ - label ] ``` #### `label` 有关更多详细信息,请参阅[标签](#labels)部分。 ``` name: size: padding: decoders: [ - decoder ] ``` #### `decoder` 有关更多详细信息,请参阅[解码器](#decoders)部分。 ``` name: # ... decoder 特定配置 ``` ## 内置指标 ### `ebpf_exporter_enabled_configs` 此 gauge 为每个已加载的配置报告一个时间序列: ``` # HELP ebpf_exporter_enabled_configs 已启用的配置集合 # TYPE ebpf_exporter_enabled_configs gauge ebpf_exporter_enabled_configs{name="cachestat"} 1 ``` ### `ebpf_exporter_ebpf_program_info` 此 gauge 报告每个 ebpf 程序可用的信息: ``` # HELP ebpf_exporter_ebpf_programs 有关 ebpf 程序的信息 # TYPE ebpf_exporter_ebpf_programs gauge ebpf_exporter_ebpf_program_info{config="cachestat",id="545",program="add_to_page_cache_lru",tag="6c007da3187b5b32"} 1 ebpf_exporter_ebpf_program_info{config="cachestat",id="546",program="mark_page_accessed",tag="6c007da3187b5b32"} 1 ebpf_exporter_ebpf_program_info{config="cachestat",id="547",program="folio_account_dirtied",tag="6c007da3187b5b32"} 1 ebpf_exporter_ebpf_program_info{config="cachestat",id="548",program="mark_buffer_dirty",tag="6c007da3187b5b32"} 1 ``` 这里的 `tag` 可用于链路追踪和性能分析,但需满足两个条件: * 设置了 `net.core.bpf_jit_kallsyms=1` sysctl * 向 `perf record` 传递了 `--kallsyms=/proc/kallsyms` 参数 较新的内核也允许向 `perf top` 传递 `--kallsyms`,未来可能完全不再需要此参数: * https://github.com/torvalds/linux/commit/1b3aae90c6abd ### `ebpf_exporter_ebpf_program_attached` 此 gauge 报告各个程序是否成功附加。 ``` # HELP ebpf_exporter_ebpf_program_attached 程序是否已附加 # TYPE ebpf_exporter_ebpf_program_attached gauge ebpf_exporter_ebpf_program_attached{id="247"} 1 ebpf_exporter_ebpf_program_attached{id="248"} 1 ebpf_exporter_ebpf_program_attached{id="249"} 0 ebpf_exporter_ebpf_program_attached{id="250"} 1 ``` 它需要通过 `id` 标签与 `ebpf_exporter_ebpf_program_info` 进行联接,以获取有关该程序的更多信息。 ### `ebpf_exporter_ebpf_program_run_time_seconds` 此 counter 报告单个程序的运行耗时。 ``` # HELP ebpf_exporter_ebpf_program_run_time_seconds 程序已执行多长时间 # TYPE ebpf_exporter_ebpf_program_run_time_seconds counter ebpf_exporter_ebpf_program_run_time_seconds{id="247"} 0 ebpf_exporter_ebpf_program_run_time_seconds{id="248"} 0.001252621 ebpf_exporter_ebpf_program_run_time_seconds{id="249"} 0 ebpf_exporter_ebpf_program_run_time_seconds{id="250"} 3.6668e-05 ``` 它需要启用 `kernel.bpf_stats_enabled` sysctl。 它需要通过 `id` 标签与 `ebpf_exporter_ebpf_program_info` 进行联接,以获取有关该程序的更多信息。 ### `ebpf_exporter_ebpf_program_run_count_total` 此 counter 报告单个程序的运行次数。 ``` # HELP ebpf_exporter_ebpf_program_run_count_total 程序已执行的次数 # TYPE ebpf_exporter_ebpf_program_run_count_total counter ebpf_exporter_ebpf_program_run_count_total{id="247"} 0 ebpf_exporter_ebpf_program_run_count_total{id="248"} 11336 ebpf_exporter_ebpf_program_run_count_total{id="249"} 0 ebpf_exporter_ebpf_program_run_count_total{id="250"} 69 ``` 它需要启用 `kernel.bpf_stats_enabled` sysctl。 它需要通过 `id` 标签与 `ebpf_exporter_ebpf_program_info` 进行联接,以获取有关该程序的更多信息。 ### `ebpf_exporter_decoder_errors_total` 此 counter 报告按配置划分的标签解码失败次数。 ``` # HELP ebpf_exporter_decoder_errors_total decoders 遇到错误的次数 # TYPE ebpf_exporter_decoder_errors_total counter ebpf_exporter_decoder_errors_total{config="kstack"} 0 ebpf_exporter_decoder_errors_total{config="sock-trace"} 4 ``` ## 许可证 MIT
标签:API集成, bcc工具, Docker镜像, eBPF监控工具, EVTX分析, GET参数, Golang, libbpf, Linux内核, OpenTelemetry, Prometheus Exporter, 云原生监控, 内核级可观测性, 分布式追踪, 可观测性, 块设备延迟, 子域名突变, 安全编程, 性能分析, 指标采集, 日志审计, 流量嗅探, 用户代理, 监控导出器, 系统性能监控, 系统调用追踪, 网络安全审计, 自定义请求头, 请求拦截