在 bpf prog 里进行统计,该怎么做呢?

PERCPU bpf map

在 bpf prog 里使用 PERCPU bpf map 进行统计,在用户态应用程序里就可以读取该 bpf map 获取从而能够计算出最终的统计数据。

使用 PERCPU bpf map 时,因为从 bpf map 里拿到的是当前 CPU 局部的一段内存,所以进行统计时不需要加锁、也不需要使用原子操作。

示例 eBPF 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct stat_item {
    u64 pkt_cnt;
    u64 pkt_byte;
};

struct {
    __int(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __type(key, u32);
    __type(value, struct stat_item);
    __int(max_entries, 1);
} stats SEC(".maps");

SEC("xdp")
int xdp_fn(struct xdp_md *ctx)
{
    struct stat_item *stat;
    stat = (typeof(stat))bpf_map_lookup_elem(&stats, 0);
    if (stat) {
        stat->pkt_cnt++;
        stat->pkt_byte += (u64)(ctx->data_end - ctx->data);
    }

    return XDP_PASS;
}

示例 Go 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
type statItem struct {
    PktCnt uint64
    PktByte uint64
}

func getStat(m *ebpf.Map) (statItem, error) {
    val := make([]statItem, runtime.NumCPU())
    err := m.Lookup(uint32(0), &val)
    if err != nil {
        return statItem{}, err
    }

    var stat statItem
    for i := range val {
        stat.PktCnt += val[i].PktCnt
        stat.PktByte += val[i].PktByte
    }

    return stat, nil
}

全局变量

通过学习 eBPF Talk: 全局变量实战指南,eBPF 里的全局变量跟普通 C 程序里的全局变量没有什么差异了,可以直接读写全局变量。

意即,可以使用全局变量来做统计。

不过,如果 bpf prog 会被并发运行,就需要在做统计时使用原子性操作。

示例 eBPF 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct stat_item {
    u64 pkt_cnt;
    u64 pkt_byte;
};

struct stat_item g_stat;

SEC("xdp")
int xdp_fn(struct xdp_md *ctx)
{
    __sync_fetch_and_add(&g_stat.pkt_cnt, 1);
    __sync_fetch_and_add(&g_stat.pkt_byte, (u64)(ctx->data_end - ctx->data));

    return XDP_PASS;
}

示例 Go 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type statItem struct {
    PktCnt uint64
    PktByte uint64
}

func getStat(m *ebpf.Map) (statItem, error) {
    val := statItem{}
    err := m.Lookup(uint32(0), &val)
    return val, err
}

加锁

参考 PERCPU bpf map 方式,也是使用 bpf map,不过在进行统计时加锁保护统计的处理。

当然,不推荐使用该方式进行统计。

示例 eBPF 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct stat_item {
    u64 pkt_cnt;
    u64 pkt_byte;
    struct bpf_spin_lock lock;
};

struct {
    __int(type, BPF_MAP_TYPE_ARRAY);
    __type(key, u32);
    __type(value, struct stat_item);
    __int(max_entries, 1);
} stats SEC(".maps");

SEC("xdp")
int xdp_fn(struct xdp_md *ctx)
{
    struct stat_item *stat;
    stat = (typeof(stat))bpf_map_lookup_elem(&stats, 0);
    if (stat) {
        bpf_spin_lock(&stat->lock);
        stat->pkt_cnt++;
        stat->pkt_byte += (u64)(ctx->data_end - ctx->data);
        bpf_spin_unlock(&stat->lock);
    }

    return XDP_PASS;
}

示例 Go 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type statItem struct {
    PktCnt uint64
    PktByte uint64
    Lock uint32
}

func getStat(m *ebpf.Map) (statItem, error) {
    val := statItem{}
    err := m.LookupWithFlags(uint32(0), &val, ebpf.LookupLock)
    return val, err
}

小结

以上介绍了 3 种在 eBPF 中进行统计的方式,推荐使用 PERCPU bpf map 方式进行统计,以空间换时间,更何况并没有消耗多少空间呢。