Skip to content

Commit dc471a1

Browse files
committed
Add support for complex counter values
This allows having a single map value to support multiple counters: ivan@vm:~$ curl -s http://localhost:9435/metrics | fgrep block_rq # HELP ebpf_exporter_block_rq_completed_bytes_total Total number of bytes served by block requests completions # TYPE ebpf_exporter_block_rq_completed_bytes_total counter ebpf_exporter_block_rq_completed_bytes_total{device="nvme0n1"} 258048 ebpf_exporter_block_rq_completed_bytes_total{device="nvme1n1"} 966656 # HELP ebpf_exporter_block_rq_completions_total Total number of block request completions # TYPE ebpf_exporter_block_rq_completions_total counter ebpf_exporter_block_rq_completions_total{device="nvme0n1"} 35 ebpf_exporter_block_rq_completions_total{device="nvme1n1"} 103 ebpf_exporter_ebpf_program_info{config="complex-value",id="72",program="block_rq_complete",tag="34d2100b313409cd"} 1
1 parent 99585f4 commit dc471a1

File tree

7 files changed

+169
-26
lines changed

7 files changed

+169
-26
lines changed

.vscode/config-schema.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ properties:
3636
type: string
3737
labels:
3838
$ref: "#/definitions/labels"
39+
values:
40+
type: array
41+
items:
42+
type: object
43+
additionalProperties: false
44+
required:
45+
- name
46+
- help
47+
properties:
48+
name:
49+
type: string
50+
help:
51+
type: string
3952
histograms:
4053
type: array
4154
items:

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,10 +759,13 @@ perf_event_array: <whether map is a BPF_MAP_TYPE_PERF_EVENT_ARRAY map: bool>
759759
flush_interval: <how often should we flush metrics from the perf_event_array: time.Duration>
760760
labels:
761761
[ - label ]
762+
[ values: { name: "...", help: "..."}]
762763
```
763764
764765
An example of `perf_map` can be found [here](examples/oomkill.yaml).
765766
767+
An example of `values` can be found [here](examples/complex-value.yaml).
768+
766769
#### `histogram`
767770
768771
See [Histograms](#histograms) section for more details.

config/config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type Counter struct {
3131
PerfEventArray bool `yaml:"perf_event_array"`
3232
FlushInterval time.Duration `yaml:"flush_interval"`
3333
Labels []Label `yaml:"labels"`
34+
Values []Value `yaml:"values"`
3435
}
3536

3637
// Histogram is a metric defining prometheus histogram
@@ -75,6 +76,12 @@ type Decoder struct {
7576
AllowUnknown bool `yaml:"allow_unknown"`
7677
}
7778

79+
// Value describes a metric in when it's split across multiple u64
80+
type Value struct {
81+
Name string `yaml:"name"`
82+
Help string `yaml:"help"`
83+
}
84+
7885
// HistogramBucketType is an enum to define how to interpret histogram
7986
type HistogramBucketType string
8087

examples/complex-value.bpf.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include <vmlinux.h>
2+
#include <bpf/bpf_core_read.h>
3+
#include <bpf/bpf_tracing.h>
4+
5+
#define MKDEV(ma, mi) ((mi & 0xff) | (ma << 8) | ((mi & ~0xff) << 12))
6+
7+
/**
8+
* commit d152c682f03c ("block: add an explicit ->disk backpointer to the
9+
* request_queue") and commit f3fa33acca9f ("block: remove the ->rq_disk
10+
* field in struct request") make some changes to `struct request` and
11+
* `struct request_queue`. Now, to get the `struct gendisk *` field in a CO-RE
12+
* way, we need both `struct request` and `struct request_queue`.
13+
* see:
14+
* https://github.com/torvalds/linux/commit/d152c682f03c
15+
* https://github.com/torvalds/linux/commit/f3fa33acca9f
16+
*/
17+
struct request_queue___x {
18+
struct gendisk *disk;
19+
} __attribute__((preserve_access_index));
20+
21+
struct request___x {
22+
struct request_queue___x *q;
23+
struct gendisk *rq_disk;
24+
} __attribute__((preserve_access_index));
25+
26+
struct key_t {
27+
u32 dev;
28+
};
29+
30+
struct value_t {
31+
u64 count;
32+
u64 bytes;
33+
};
34+
35+
struct {
36+
__uint(type, BPF_MAP_TYPE_HASH);
37+
__uint(max_entries, 1024);
38+
__type(key, struct key_t);
39+
__type(value, struct value_t);
40+
} block_rq_completions SEC(".maps");
41+
42+
static __always_inline struct gendisk *get_disk(void *request)
43+
{
44+
struct request___x *r = request;
45+
46+
if (bpf_core_field_exists(r->rq_disk))
47+
return r->rq_disk;
48+
return r->q->disk;
49+
}
50+
51+
static struct value_t *get_value(void *map, struct key_t *key)
52+
{
53+
struct value_t *value = bpf_map_lookup_elem(map, key);
54+
if (!value) {
55+
struct value_t zero = { .count = 0, .bytes = 0 };
56+
bpf_map_update_elem(map, key, &zero, BPF_NOEXIST);
57+
value = bpf_map_lookup_elem(map, key);
58+
if (!value) {
59+
return NULL;
60+
}
61+
}
62+
63+
return value;
64+
}
65+
66+
SEC("tp_btf/block_rq_complete")
67+
int BPF_PROG(block_rq_complete, struct request *rq, blk_status_t error, unsigned int nr_bytes)
68+
{
69+
struct gendisk *disk = get_disk(rq);
70+
struct key_t key = { .dev = disk ? MKDEV(disk->major, disk->first_minor) : 0 };
71+
struct value_t *value = get_value(&block_rq_completions, &key);
72+
73+
if (!value) {
74+
return 0;
75+
}
76+
77+
__sync_fetch_and_add(&value->count, 1);
78+
__sync_fetch_and_add(&value->bytes, nr_bytes);
79+
80+
return 0;
81+
}
82+
83+
char LICENSE[] SEC("license") = "GPL";

examples/complex-value.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
metrics:
2+
counters:
3+
- name: block_rq_completions
4+
help: Block request completions split into count and bytes
5+
labels:
6+
- name: device
7+
size: 4
8+
decoders:
9+
- name: majorminor
10+
values:
11+
- name: block_rq_completions_total
12+
help: Total number of block request completions
13+
- name: block_rq_completed_bytes_total
14+
help: Total number of bytes served by block requests completions

exporter/exporter.go

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,13 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
392392
e.perfEventArrayCollectors = append(e.perfEventArrayCollectors, perfSink)
393393
}
394394

395-
addDescs(cfg.Name, counter.Name, counter.Help, counter.Labels)
395+
if counter.Values != nil {
396+
for _, value := range counter.Values {
397+
addDescs(cfg.Name, value.Name, value.Help, counter.Labels)
398+
}
399+
} else {
400+
addDescs(cfg.Name, counter.Name, counter.Help, counter.Labels)
401+
}
396402
}
397403

398404
for _, histogram := range cfg.Metrics.Histograms {
@@ -477,10 +483,14 @@ func (e *Exporter) collectCounters(ch chan<- prometheus.Metric) {
477483

478484
aggregatedMapValues := aggregateMapValues(mapValues)
479485

480-
desc := e.descs[cfg.Name][counter.Name]
481-
482486
for _, metricValue := range aggregatedMapValues {
483-
ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, metricValue.value, metricValue.labels...)
487+
if counter.Values != nil {
488+
for i, value := range counter.Values {
489+
ch <- prometheus.MustNewConstMetric(e.descs[cfg.Name][value.Name], prometheus.CounterValue, metricValue.value[i], metricValue.labels...)
490+
}
491+
} else {
492+
ch <- prometheus.MustNewConstMetric(e.descs[cfg.Name][counter.Name], prometheus.CounterValue, metricValue.value[0], metricValue.labels...)
493+
}
484494
}
485495
}
486496
}
@@ -531,7 +541,7 @@ func (e *Exporter) collectHistograms(ch chan<- prometheus.Metric) {
531541
break
532542
}
533543

534-
histograms[key].buckets[float64(leUint)] = uint64(metricValue.value)
544+
histograms[key].buckets[float64(leUint)] = uint64(metricValue.value[0])
535545
}
536546

537547
if skip {
@@ -673,29 +683,33 @@ func (e *Exporter) MapsHandler(w http.ResponseWriter, r *http.Request) {
673683
}
674684

675685
func validateMaps(module *libbpfgo.Module, cfg config.Config) error {
676-
maps := []string{}
686+
sizes := map[string]int{}
677687

678688
for _, counter := range cfg.Metrics.Counters {
679689
if counter.Name != "" && !counter.PerfEventArray {
680-
maps = append(maps, counter.Name)
690+
if counter.Values != nil {
691+
sizes[counter.Name] = len(counter.Values) * 8
692+
} else {
693+
sizes[counter.Name] = 8
694+
}
681695
}
682696
}
683697

684698
for _, histogram := range cfg.Metrics.Histograms {
685699
if histogram.Name != "" {
686-
maps = append(maps, histogram.Name)
700+
sizes[histogram.Name] = 8
687701
}
688702
}
689703

690-
for _, name := range maps {
704+
for name, expected := range sizes {
691705
m, err := module.GetMap(name)
692706
if err != nil {
693707
return fmt.Errorf("failed to get map %q: %w", name, err)
694708
}
695709

696710
valueSize := m.ValueSize()
697-
if valueSize != 8 {
698-
return fmt.Errorf("value size for map %q is not expected 8 bytes (u64), it is %d bytes", name, valueSize)
711+
if valueSize != expected {
712+
return fmt.Errorf("value size for map %q is not expected %d bytes (8 bytes per u64 value), it is %d bytes", name, expected, valueSize)
699713
}
700714
}
701715

@@ -721,7 +735,9 @@ func aggregateMapValues(values []metricValue) []aggregatedMetricValue {
721735
value: value.value,
722736
}
723737
} else {
724-
existing.value += value.value
738+
for i := range existing.value {
739+
existing.value[i] += value.value[i]
740+
}
725741
}
726742
}
727743

@@ -785,17 +801,18 @@ func readMapValues(m *libbpfgo.BPFMap, labels []config.Label) ([]metricValue, er
785801
return metricValues, nil
786802
}
787803

788-
func mapValue(m *libbpfgo.BPFMap, key []byte) (float64, error) {
804+
func mapValue(m *libbpfgo.BPFMap, key []byte) ([]float64, error) {
789805
v, err := m.GetValue(unsafe.Pointer(&key[0]))
790806
if err != nil {
791-
return 0.0, err
807+
return []float64{0.0}, err
792808
}
793809

794810
return decodeValue(v), nil
795811
}
796812

797-
func mapValuePerCPU(m *libbpfgo.BPFMap, key []byte) ([]float64, error) {
798-
values := []float64{}
813+
func mapValuePerCPU(m *libbpfgo.BPFMap, key []byte) ([][]float64, error) {
814+
values := [][]float64{}
815+
799816
size := m.ValueSize()
800817

801818
value, err := m.GetValue(unsafe.Pointer(&key[0]))
@@ -811,8 +828,14 @@ func mapValuePerCPU(m *libbpfgo.BPFMap, key []byte) ([]float64, error) {
811828
}
812829

813830
// Assuming counter's value type is always u64
814-
func decodeValue(value []byte) float64 {
815-
return float64(util.GetHostByteOrder().Uint64(value))
831+
func decodeValue(value []byte) []float64 {
832+
values := make([]float64, len(value)/8)
833+
834+
for i := range values {
835+
values[i] = float64(util.GetHostByteOrder().Uint64(value[i*8:]))
836+
}
837+
838+
return values
816839
}
817840

818841
// metricValue is a row in a kernel map
@@ -822,13 +845,13 @@ type metricValue struct {
822845
// labels are decoded from the raw key
823846
labels []string
824847
// value is the kernel map value
825-
value float64
848+
value []float64
826849
}
827850

828851
// aggregatedMetricValue is a value after aggregation of equal label sets
829852
type aggregatedMetricValue struct {
830853
// labels are decoded from the raw key
831854
labels []string
832855
// value is the kernel map value
833-
value float64
856+
value []float64
834857
}

exporter/exporter_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,32 @@ func TestAggregatedMetricValues(t *testing.T) {
1010
values := []metricValue{
1111
{
1212
labels: []string{"foo"},
13-
value: 8,
13+
value: []float64{8},
1414
},
1515
{
1616
labels: []string{"bar"},
17-
value: 1,
17+
value: []float64{1},
1818
},
1919
{
2020
labels: []string{"foo"},
21-
value: 3,
21+
value: []float64{3},
2222
},
2323
}
2424

2525
aggregated := aggregateMapValues(values)
2626

2727
sort.Slice(aggregated, func(i, j int) bool {
28-
return aggregated[i].value > aggregated[j].value
28+
return aggregated[i].value[0] > aggregated[j].value[0]
2929
})
3030

3131
expected := []aggregatedMetricValue{
3232
{
3333
labels: []string{"foo"},
34-
value: 11,
34+
value: []float64{11},
3535
},
3636
{
3737
labels: []string{"bar"},
38-
value: 1,
38+
value: []float64{1},
3939
},
4040
}
4141

0 commit comments

Comments
 (0)