在 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 方式进行统计,以空间换时间,更何况并没有消耗多少空间呢。