Skip to content

Commit 85f740f

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 2b35ff4 commit 85f740f

File tree

6 files changed

+184
-9
lines changed

6 files changed

+184
-9
lines changed

cgroup/monitor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
"log"
66
)
77

8-
var ErrCgroupIdMapUnsupported = errors.New("cgroup change subscription failed (fanotify not available)")
8+
var ErrCgroupIDMapUnsupported = errors.New("cgroup change subscription failed (fanotify not available)")
99

1010
type CgroupChange struct {
11-
Id int
11+
ID int
1212
Path string
1313
Remove bool
1414
}
@@ -45,6 +45,8 @@ func (m *Monitor) Resolve(id int) string {
4545
return m.inner.Resolve(id)
4646
}
4747

48+
// SubscribeCgroupChange receives cgroup change notifications. This requires
49+
// kernel with fanotify support for cgroup
4850
func (m *Monitor) SubscribeCgroupChange(ch chan<- CgroupChange) error {
4951
return m.inner.SubscribeCgroupChange(ch)
5052
}

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

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)