Skip to content

Commit 1ef6204

Browse files
authored
Merge pull request #527 from dqminh/regexp-cache
add a negative cache to regexp decoder
2 parents dc09d39 + f711f4d commit 1ef6204

File tree

7 files changed

+153
-14
lines changed

7 files changed

+153
-14
lines changed

cmd/ebpf_exporter/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func main() {
4141
metricsPath := kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String()
4242
capabilities := kingpin.Flag("capabilities.keep", "Comma separated list of capabilities to keep (cap_syslog, cap_bpf, etc.), 'all' or 'none'").Default("all").String()
4343
btfPath := kingpin.Flag("btf.path", "Optional BTF file path.").Default("").String()
44+
skipCacheSize := kingpin.Flag("config.skip-cache-size", "Size of the LRU skip cache").Int()
4445
kingpin.Version(version.Print("ebpf_exporter"))
4546
kingpin.HelpFlag.Short('h')
4647
kingpin.Parse()
@@ -92,7 +93,7 @@ func main() {
9293

9394
notify("creating exporter...")
9495

95-
e, err := exporter.New(configs, tracing.NewProvider(processor), *btfPath)
96+
e, err := exporter.New(configs, *skipCacheSize, tracing.NewProvider(processor), *btfPath)
9697
if err != nil {
9798
log.Fatalf("Error creating exporter: %s", err)
9899
}

decoder/decoder.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/cloudflare/ebpf_exporter/v2/config"
99
"github.com/cloudflare/ebpf_exporter/v2/kallsyms"
10+
lru "github.com/hashicorp/golang-lru/v2"
1011
)
1112

1213
// ErrSkipLabelSet instructs exporter to skip label set
@@ -21,13 +22,14 @@ type Decoder interface {
2122

2223
// Set is a set of Decoders that may be applied to produce a label
2324
type Set struct {
24-
mu sync.Mutex
25-
decoders map[string]Decoder
26-
cache map[string]map[string][]string
25+
mu sync.Mutex
26+
decoders map[string]Decoder
27+
cache map[string]map[string][]string
28+
skipCache *lru.Cache[string, struct{}]
2729
}
2830

2931
// NewSet creates a Set with all known decoders
30-
func NewSet() (*Set, error) {
32+
func NewSet(skipCacheSize int) (*Set, error) {
3133
cgroup, err := NewCgroupDecoder()
3234
if err != nil {
3335
return nil, fmt.Errorf("error creating cgroup decoder: %w", err)
@@ -38,7 +40,7 @@ func NewSet() (*Set, error) {
3840
return nil, fmt.Errorf("error creating ksym decoder: %w", err)
3941
}
4042

41-
return &Set{
43+
s := &Set{
4244
decoders: map[string]Decoder{
4345
"cgroup": cgroup,
4446
"dname": &Dname{},
@@ -60,7 +62,16 @@ func NewSet() (*Set, error) {
6062
"uint": &UInt{},
6163
},
6264
cache: map[string]map[string][]string{},
63-
}, nil
65+
}
66+
67+
if skipCacheSize > 0 {
68+
skipCache, err := lru.New[string, struct{}](skipCacheSize)
69+
if err != nil {
70+
return nil, err
71+
}
72+
s.skipCache = skipCache
73+
}
74+
return s, nil
6475
}
6576

6677
// decode transforms input byte field into a string according to configuration
@@ -75,6 +86,9 @@ func (s *Set) decode(in []byte, label config.Label) ([]byte, error) {
7586
decoded, err := s.decoders[decoder.Name].Decode(result, decoder)
7687
if err != nil {
7788
if errors.Is(err, ErrSkipLabelSet) {
89+
if s.skipCache != nil {
90+
s.skipCache.Add(string(in), struct{}{})
91+
}
7892
return decoded, err
7993
}
8094

@@ -106,6 +120,14 @@ func (s *Set) DecodeLabelsForMetrics(in []byte, name string, labels []config.Lab
106120
return cached, nil
107121
}
108122

123+
// Also check the skip cache if the input would have return ErrSkipLabelSet
124+
// and return the error early.
125+
if s.skipCache != nil {
126+
if _, ok := s.skipCache.Get(string(in)); ok {
127+
return nil, ErrSkipLabelSet
128+
}
129+
}
130+
109131
values, err := s.decodeLabels(in, labels)
110132
if err != nil {
111133
return nil, err

decoder/decoder_test.go

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package decoder
22

33
import (
4+
"errors"
45
"fmt"
56
"sync"
67
"testing"
@@ -148,7 +149,7 @@ func TestDecodeLabels(t *testing.T) {
148149
}
149150

150151
for i, c := range cases {
151-
s, err := NewSet()
152+
s, err := NewSet(0)
152153
if err != nil {
153154
t.Fatal(err)
154155
}
@@ -178,6 +179,118 @@ func TestDecodeLabels(t *testing.T) {
178179
}
179180
}
180181

182+
func TestDecodeSkipLabels(t *testing.T) {
183+
cases := []struct {
184+
in []byte
185+
skipCacheIn string
186+
labels []config.Label
187+
out []string
188+
err bool
189+
}{
190+
{
191+
in: append([]byte{0x8, 0x0, 0x0, 0x0}, zeroPaddedString("bananas", 32)...),
192+
skipCacheIn: "",
193+
labels: []config.Label{
194+
{
195+
Name: "number",
196+
Size: 4,
197+
Decoders: []config.Decoder{
198+
{
199+
Name: "uint",
200+
},
201+
},
202+
},
203+
{
204+
Name: "fruit",
205+
Size: 32,
206+
Decoders: []config.Decoder{
207+
{
208+
Name: "string",
209+
},
210+
{
211+
Name: "regexp",
212+
Regexps: []string{
213+
"^bananas$",
214+
"$is-banana-even-fruit$",
215+
},
216+
},
217+
},
218+
},
219+
},
220+
out: []string{"8", "bananas"},
221+
err: false,
222+
},
223+
{
224+
in: append([]byte{0x8, 0x0, 0x0, 0x0}, zeroPaddedString("bananas", 32)...),
225+
skipCacheIn: string(zeroPaddedString("bananas", 32)),
226+
labels: []config.Label{
227+
{
228+
Name: "number",
229+
Size: 4,
230+
Decoders: []config.Decoder{
231+
{
232+
Name: "uint",
233+
},
234+
},
235+
},
236+
{
237+
Name: "fruit",
238+
Size: 32,
239+
Decoders: []config.Decoder{
240+
{
241+
Name: "string",
242+
},
243+
{
244+
Name: "regexp",
245+
Regexps: []string{
246+
"^tomato$",
247+
},
248+
},
249+
},
250+
},
251+
},
252+
out: []string{"8", "bananas"},
253+
err: true, // this label should be skipped, only tomatoes allowed
254+
},
255+
}
256+
257+
for i, c := range cases {
258+
s, err := NewSet(100)
259+
if err != nil {
260+
t.Fatal(err)
261+
}
262+
263+
out, err := s.DecodeLabelsForMetrics(c.in, fmt.Sprintf("test:%d", i), c.labels)
264+
if c.err {
265+
if err == nil {
266+
t.Errorf("Expected error for input %#v and labels %#v, but did not receive it", c.in, c.labels)
267+
}
268+
269+
if errors.Is(err, ErrSkipLabelSet) {
270+
if !s.skipCache.Contains(c.skipCacheIn) {
271+
t.Errorf("Expected skipCache to have input %#v", c.skipCacheIn)
272+
}
273+
}
274+
275+
continue
276+
}
277+
278+
if err != nil {
279+
t.Errorf("Error decoding %#v with labels set to %#v: %s", c.in, c.labels, err)
280+
}
281+
282+
if len(c.out) != len(out) {
283+
t.Errorf("Expected %d outputs (%v), received %d (%v)", len(c.out), c.out, len(out), out)
284+
}
285+
286+
for i := 0; i < len(c.out) && i < len(out); i++ {
287+
if c.out[i] != out[i] {
288+
t.Errorf("Output label %d for input %#v is wrong: expected %s, but received %s", i, c.in, c.out[i], out[i])
289+
}
290+
}
291+
}
292+
}
293+
181294
func TestDecoderSetConcurrency(t *testing.T) {
182295
in := append([]byte{0x8, 0x0, 0x0, 0x0}, zeroPaddedString("bananas", 32)...)
183296

@@ -209,7 +322,7 @@ func TestDecoderSetConcurrency(t *testing.T) {
209322
},
210323
}
211324

212-
s, err := NewSet()
325+
s, err := NewSet(0)
213326
if err != nil {
214327
t.Fatal(err)
215328
}
@@ -274,7 +387,7 @@ func TestDecoderSetCache(t *testing.T) {
274387
},
275388
}
276389

277-
s, err := NewSet()
390+
s, err := NewSet(0)
278391
if err != nil {
279392
t.Fatal(err)
280393
}
@@ -345,7 +458,7 @@ func BenchmarkCache(b *testing.B) {
345458
},
346459
}
347460

348-
s, err := NewSet()
461+
s, err := NewSet(0)
349462
if err != nil {
350463
b.Fatal(err)
351464
}

exporter/exporter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ type Exporter struct {
5656
}
5757

5858
// New creates a new exporter with the provided config
59-
func New(configs []config.Config, tracingProvider tracing.Provider, btfPath string) (*Exporter, error) {
59+
func New(configs []config.Config, skipCacheSize int, tracingProvider tracing.Provider, btfPath string) (*Exporter, error) {
6060
enabledConfigsDesc := prometheus.NewDesc(
6161
prometheus.BuildFQName(prometheusNamespace, "", "enabled_configs"),
6262
"The set of enabled configs",
@@ -101,7 +101,7 @@ func New(configs []config.Config, tracingProvider tracing.Provider, btfPath stri
101101
decoderErrorCount.WithLabelValues(config.Name).Add(0.0)
102102
}
103103

104-
decoders, err := decoder.NewSet()
104+
decoders, err := decoder.NewSet(skipCacheSize)
105105
if err != nil {
106106
return nil, fmt.Errorf("error creating decoder set: %w", err)
107107
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/aquasecurity/libbpfgo v0.9.1-libbpf-1.5.1
88
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
99
github.com/elastic/go-perf v0.0.0-20191212140718-9c656876f595
10+
github.com/hashicorp/golang-lru/v2 v2.0.7
1011
github.com/iovisor/gobpf v0.2.0
1112
github.com/jaypipes/pcidb v1.0.1
1213
github.com/mdlayher/sdnotify v1.0.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrR
3232
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
3333
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
3434
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
35+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
36+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
3537
github.com/iovisor/gobpf v0.2.0 h1:34xkQxft+35GagXBk3n23eqhm0v7q0ejeVirb8sqEOQ=
3638
github.com/iovisor/gobpf v0.2.0/go.mod h1:WSY9Jj5RhdgC3ci1QaacvbFdQ8cbrEjrpiZbLHLt2s4=
3739
github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=

tracing/extract_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func TestExtractFilled(t *testing.T) {
151151
},
152152
}
153153

154-
decoders, err := decoder.NewSet()
154+
decoders, err := decoder.NewSet(0)
155155
if err != nil {
156156
t.Fatalf("Error creating decoders set: %v", err)
157157
}

0 commit comments

Comments
 (0)