ebpf_exporter
153 строки · 3.8 Кб
1package decoder
2
3import (
4"errors"
5"fmt"
6"sync"
7
8"github.com/cloudflare/ebpf_exporter/v2/config"
9"github.com/cloudflare/ebpf_exporter/v2/kallsyms"
10)
11
12// ErrSkipLabelSet instructs exporter to skip label set
13var ErrSkipLabelSet = errors.New("this label set should be skipped")
14
15// Decoder transforms byte field value into a byte value representing string
16// to either use as an input for another Decoder or to use as the final
17// label value for Prometheus metrics
18type Decoder interface {
19Decode([]byte, config.Decoder) ([]byte, error)
20}
21
22// Set is a set of Decoders that may be applied to produce a label
23type Set struct {
24mu sync.Mutex
25decoders map[string]Decoder
26cache map[string]map[string][]string
27}
28
29// NewSet creates a Set with all known decoders
30func NewSet() (*Set, error) {
31cgroup, err := NewCgroupDecoder()
32if err != nil {
33return nil, fmt.Errorf("error creating cgroup decoder: %v", err)
34}
35
36ksym, err := kallsyms.NewDecoder("/proc/kallsyms")
37if err != nil {
38return nil, fmt.Errorf("error creating ksym decoder: %v", err)
39}
40
41return &Set{
42decoders: map[string]Decoder{
43"cgroup": cgroup,
44"dname": &Dname{},
45"ifname": &IfName{},
46"inet_ip": &InetIP{},
47"kstack": &KStack{ksym},
48"ksym": &KSym{ksym},
49"majorminor": &MajorMinor{},
50"pci_class": &PCIClass{},
51"pci_device": &PCIDevice{},
52"pci_subclass": &PCISubClass{},
53"pci_vendor": &PCIVendor{},
54"regexp": &Regexp{},
55"static_map": &StaticMap{},
56"string": &String{},
57"syscall": &Syscall{},
58"uint": &UInt{},
59},
60cache: map[string]map[string][]string{},
61}, nil
62}
63
64// decode transforms input byte field into a string according to configuration
65func (s *Set) decode(in []byte, label config.Label) ([]byte, error) {
66result := in
67
68for _, decoder := range label.Decoders {
69if _, ok := s.decoders[decoder.Name]; !ok {
70return result, fmt.Errorf("unknown decoder %q", decoder.Name)
71}
72
73decoded, err := s.decoders[decoder.Name].Decode(result, decoder)
74if err != nil {
75if err == ErrSkipLabelSet {
76return decoded, err
77}
78return decoded, fmt.Errorf("error decoding with decoder %q: %s", decoder.Name, err)
79}
80
81result = decoded
82}
83
84return result, nil
85}
86
87// DecodeLabels transforms eBPF map key bytes into a list of label values
88// according to configuration (different label sets require different names)
89func (s *Set) DecodeLabels(in []byte, name string, labels []config.Label) ([]string, error) {
90s.mu.Lock()
91defer s.mu.Unlock()
92
93cache, ok := s.cache[name]
94if !ok {
95cache = map[string][]string{}
96s.cache[name] = cache
97}
98
99// string(in) must not be a variable to avoid allocation:
100// * https://github.com/golang/go/commit/f5f5a8b6209f8
101if cached, ok := cache[string(in)]; ok {
102return cached, nil
103}
104
105values, err := s.decodeLabels(in, labels)
106if err != nil {
107return nil, err
108}
109
110cache[string(in)] = values
111
112return values, nil
113}
114
115// decodeLabels is the inner function of DecodeLabels without any caching
116func (s *Set) decodeLabels(in []byte, labels []config.Label) ([]string, error) {
117values := make([]string, len(labels))
118
119off := uint(0)
120
121totalSize := uint(0)
122for _, label := range labels {
123size := label.Size
124if size == 0 {
125return nil, fmt.Errorf("error decoding label %q: size is zero or not set", label.Name)
126}
127
128totalSize += size
129}
130
131if totalSize != uint(len(in)) {
132return nil, fmt.Errorf("error decoding labels: total size of key %#v is %d bytes, but we have labels to decode %d", in, len(in), totalSize)
133}
134
135for i, label := range labels {
136if len(label.Decoders) == 0 {
137return nil, fmt.Errorf("error decoding label %q: no decoders set", label.Name)
138}
139
140size := label.Size
141
142decoded, err := s.decode(in[off:off+size], label)
143if err != nil {
144return nil, err
145}
146
147off += size
148
149values[i] = string(decoded)
150}
151
152return values, nil
153}
154