Skip to content

Commit 2df5c79

Browse files
committed
add cgroup_id_map config to sync cgroup id to ebpf programs
For some usecases, it's useful for ebpf programs to filter their metrics based on cgroups. It's generally hard to ebpf program to be able to filter cgroup at runtime. This make it easier by allowing ebpf exporter to update known interesting cgroup id at runtime via a shared BPF map.
1 parent acede2e commit 2df5c79

File tree

5 files changed

+177
-7
lines changed

5 files changed

+177
-7
lines changed

config/config.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import (
1111

1212
// Config describes how to configure and extract metrics
1313
type Config struct {
14-
Name string `yaml:"name"`
15-
Metrics Metrics `yaml:"metrics"`
16-
Tracing Tracing `yaml:"tracing"`
17-
Kaddrs []string `yaml:"kaddrs"`
18-
BPFPath string
14+
Name string `yaml:"name"`
15+
Metrics Metrics `yaml:"metrics"`
16+
Tracing Tracing `yaml:"tracing"`
17+
Kaddrs []string `yaml:"kaddrs"`
18+
CgroupIdMap CgroupIdMap `yaml:"cgroup_id_map"`
19+
BPFPath string
1920
}
2021

2122
// Metrics is a collection of metrics attached to a program
@@ -45,6 +46,11 @@ type Histogram struct {
4546
Labels []Label `yaml:"labels"`
4647
}
4748

49+
type CgroupIdMap struct {
50+
Name string `yaml:"name"`
51+
Regexps []string `yaml:"regexps"`
52+
}
53+
4854
// Tracing is a collection of spans attached to a program
4955
type Tracing struct {
5056
Spans []Span `yaml:"spans"`

examples/cgroup_id_map.bpf.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include <vmlinux.h>
2+
#include <bpf/bpf_tracing.h>
3+
#include "maps.bpf.h"
4+
5+
struct {
6+
__uint(type, BPF_MAP_TYPE_LRU_HASH);
7+
__uint(max_entries, 1024);
8+
__type(key, u64);
9+
__type(value, u64);
10+
} cgroup_sched_migrations_total SEC(".maps");
11+
12+
struct {
13+
__uint(type, BPF_MAP_TYPE_LRU_HASH);
14+
__uint(max_entries, 1024);
15+
__type(key, u64);
16+
__type(value, u64);
17+
} cgroup_sched_migrations_not_match_total SEC(".maps");
18+
19+
struct {
20+
__uint(type, BPF_MAP_TYPE_LRU_HASH);
21+
__uint(max_entries, 1024);
22+
__type(key, u64);
23+
__type(value, u64);
24+
} cgroup_id_map SEC(".maps");
25+
26+
SEC("tp_btf/sched_migrate_task")
27+
int BPF_PROG(sched_migrate_task)
28+
{
29+
u64 *ok;
30+
u64 cgroup_id = bpf_get_current_cgroup_id();
31+
ok = bpf_map_lookup_elem(&cgroup_id_map, &cgroup_id);
32+
if (ok) {
33+
increment_map(&cgroup_sched_migrations_total, &cgroup_id, 1);
34+
} else {
35+
increment_map(&cgroup_sched_migrations_not_match_total, &cgroup_id, 1);
36+
}
37+
return 0;
38+
}
39+
40+
char LICENSE[] SEC("license") = "GPL";

examples/cgroup_id_map.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
metrics:
2+
counters:
3+
- name: cgroup_sched_migrations_total
4+
help: Number of sched:sched_migrate_task events per cgroup
5+
labels:
6+
- name: cgroup
7+
size: 8
8+
decoders:
9+
- name: uint
10+
- name: cgroup
11+
12+
- name: cgroup_sched_migrations_not_match_total
13+
help: Number of sched:sched_migrate_task events per cgroup not match cgroup id map
14+
labels:
15+
- name: cgroup
16+
size: 8
17+
decoders:
18+
- name: uint
19+
- name: cgroup
20+
21+
cgroup_id_map:
22+
name: cgroup_id_map
23+
regexps:
24+
- ^.*(system.slice/.*)$

exporter/cgroup_id_map.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package exporter
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"unsafe"
7+
8+
"github.com/aquasecurity/libbpfgo"
9+
"github.com/cloudflare/ebpf_exporter/v2/cgroup"
10+
"github.com/cloudflare/ebpf_exporter/v2/config"
11+
)
12+
13+
type CgroupIdMap struct {
14+
bpfMap *libbpfgo.BPFMap
15+
ch chan cgroup.CgroupChange
16+
cache map[string]*regexp.Regexp
17+
}
18+
19+
func newCgroupIdMap(module *libbpfgo.Module, cfg config.Config) (*CgroupIdMap, error) {
20+
m, err := module.GetMap(cfg.CgroupIdMap.Name)
21+
if err != nil {
22+
return nil, fmt.Errorf("failed to get map %q: %w", cfg.CgroupIdMap.Name, err)
23+
}
24+
25+
keySize := m.KeySize()
26+
if keySize != 8 {
27+
return nil, fmt.Errorf("key size for map %q is not expected 8 bytes (u64), it is %d bytes", cfg.CgroupIdMap.Name, keySize)
28+
}
29+
valueSize := m.ValueSize()
30+
if valueSize != 8 {
31+
return nil, fmt.Errorf("value size for map %q is not expected 8 bytes (u64), it is %d bytes", cfg.CgroupIdMap.Name, valueSize)
32+
}
33+
34+
c := &CgroupIdMap{
35+
bpfMap: m,
36+
ch: make(chan cgroup.CgroupChange, 10),
37+
cache: map[string]*regexp.Regexp{},
38+
}
39+
40+
for _, expr := range cfg.CgroupIdMap.Regexps {
41+
if _, ok := c.cache[expr]; !ok {
42+
compiled, err := regexp.Compile(expr)
43+
if err != nil {
44+
return nil, fmt.Errorf("error compiling regexp %q: %w", expr, err)
45+
}
46+
c.cache[expr] = compiled
47+
}
48+
}
49+
50+
return c, nil
51+
}
52+
53+
func (c *CgroupIdMap) subscribe(m *cgroup.Monitor) error {
54+
return m.SubscribeCgroupChange(c.ch)
55+
}
56+
57+
func (c *CgroupIdMap) runLoop() {
58+
for update := range c.ch {
59+
if update.Remove {
60+
key := uint64(update.Id)
61+
c.bpfMap.DeleteKey(unsafe.Pointer(&key))
62+
} else {
63+
key := uint64(update.Id)
64+
value := uint64(1)
65+
if c.checkMatch(update.Path) {
66+
c.bpfMap.Update(unsafe.Pointer(&key), unsafe.Pointer(&value))
67+
}
68+
}
69+
}
70+
}
71+
72+
func (c *CgroupIdMap) checkMatch(path string) bool {
73+
for _, compiled := range c.cache {
74+
if compiled.MatchString(path) {
75+
return true
76+
}
77+
}
78+
return false
79+
}

exporter/exporter.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"unsafe"
1616

1717
"github.com/aquasecurity/libbpfgo"
18+
"github.com/cloudflare/ebpf_exporter/v2/cgroup"
1819
"github.com/cloudflare/ebpf_exporter/v2/config"
1920
"github.com/cloudflare/ebpf_exporter/v2/decoder"
2021
"github.com/cloudflare/ebpf_exporter/v2/tracing"
@@ -36,8 +37,9 @@ var percpuMapTypes = map[libbpfgo.MapType]struct{}{
3637

3738
// Exporter is a ebpf_exporter instance implementing prometheus.Collector
3839
type Exporter struct {
39-
configs []config.Config
40-
modules map[string]*libbpfgo.Module
40+
configs []config.Config
41+
modules map[string]*libbpfgo.Module
42+
4143
perfEventArrayCollectors []*perfEventArraySink
4244
kaddrs map[string]uint64
4345
enabledConfigsDesc *prometheus.Desc
@@ -238,12 +240,31 @@ func (e *Exporter) attachConfig(ctx context.Context, cfg config.Config) error {
238240
return fmt.Errorf("error validating maps for config %q: %w", cfg.Name, err)
239241
}
240242

243+
// attach cgroup id map if exists
244+
if len(cfg.CgroupIdMap.Name) > 0 {
245+
if err := e.attachCgroupIdMap(module, cfg); err != nil {
246+
return err
247+
}
248+
}
249+
241250
e.attachedProgs[cfg.Name] = attachments
242251
e.modules[cfg.Name] = module
243252

244253
return nil
245254
}
246255

256+
func (e *Exporter) attachCgroupIdMap(module *libbpfgo.Module, cfg config.Config) error {
257+
cgMap, err := newCgroupIdMap(module, cfg)
258+
if err != nil {
259+
return err
260+
}
261+
if err := cgMap.subscribe(e.cgroupMonitor); err != nil {
262+
return err
263+
}
264+
go cgMap.runLoop()
265+
return nil
266+
}
267+
247268
// Detach detaches bpf programs and maps for exiting
248269
func (e *Exporter) Detach() {
249270
e.activeMutex.Lock()

0 commit comments

Comments
 (0)