cloudflare/ebpf_exporter
GitHub: cloudflare/ebpf_exporter
一个基于 libbpf 的 Prometheus exporter,用于将自定义的 eBPF 内核指标和 OpenTelemetry 链路数据导出,实现对 Linux 系统底层的深度可观测。
Stars: 2588 | Forks: 274
# ebpf_exporter
用于自定义 eBPF 指标和 OpenTelemetry 链路的 Prometheus exporter。
* 指标:

* [链路](./tracing):

该 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 绘制出漂亮的图表:

## 配置概念
`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:
标签:API集成, bcc工具, Docker镜像, eBPF监控工具, EVTX分析, GET参数, Golang, libbpf, Linux内核, OpenTelemetry, Prometheus Exporter, 云原生监控, 内核级可观测性, 分布式追踪, 可观测性, 块设备延迟, 子域名突变, 安全编程, 性能分析, 指标采集, 日志审计, 流量嗅探, 用户代理, 监控导出器, 系统性能监控, 系统调用追踪, 网络安全审计, 自定义请求头, 请求拦截