Skip to content

Commit 6fc58f8

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 091f97d commit 6fc58f8

File tree

5 files changed

+154
-26
lines changed

5 files changed

+154
-26
lines changed

config/config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type Counter struct {
4040
PerfEventArray bool `yaml:"perf_event_array"`
4141
FlushInterval time.Duration `yaml:"flush_interval"`
4242
Labels []Label `yaml:"labels"`
43+
Values []Value `yaml:"values"`
4344
}
4445

4546
// Histogram is a metric defining prometheus histogram
@@ -70,6 +71,12 @@ type Decoder struct {
7071
AllowUnknown bool `yaml:"allow_unknown"`
7172
}
7273

74+
// Value describes a metric in when it's split across multiple u64
75+
type Value struct {
76+
Name string `yaml:"name"`
77+
Help string `yaml:"help"`
78+
}
79+
7380
// HistogramBucketType is an enum to define how to interpret histogram
7481
type HistogramBucketType string
7582

examples/complex-value.bpf.c

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

237-
addDescs(cfg.Name, counter.Name, counter.Help, counter.Labels)
237+
if counter.Values != nil {
238+
for _, value := range counter.Values {
239+
addDescs(cfg.Name, value.Name, value.Help, counter.Labels)
240+
}
241+
} else {
242+
addDescs(cfg.Name, counter.Name, counter.Help, counter.Labels)
243+
}
238244
}
239245

240246
for _, histogram := range cfg.Metrics.Histograms {
@@ -303,10 +309,14 @@ func (e *Exporter) collectCounters(ch chan<- prometheus.Metric) {
303309

304310
aggregatedMapValues := aggregateMapValues(mapValues)
305311

306-
desc := e.descs[cfg.Name][counter.Name]
307-
308312
for _, metricValue := range aggregatedMapValues {
309-
ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, metricValue.value, metricValue.labels...)
313+
if counter.Values != nil {
314+
for i, value := range counter.Values {
315+
ch <- prometheus.MustNewConstMetric(e.descs[cfg.Name][value.Name], prometheus.CounterValue, metricValue.value[i], metricValue.labels...)
316+
}
317+
} else {
318+
ch <- prometheus.MustNewConstMetric(e.descs[cfg.Name][counter.Name], prometheus.CounterValue, metricValue.value[0], metricValue.labels...)
319+
}
310320
}
311321
}
312322
}
@@ -356,7 +366,7 @@ func (e *Exporter) collectHistograms(ch chan<- prometheus.Metric) {
356366
break
357367
}
358368

359-
histograms[key].buckets[float64(leUint)] = uint64(metricValue.value)
369+
histograms[key].buckets[float64(leUint)] = uint64(metricValue.value[0])
360370
}
361371

362372
if skip {
@@ -497,29 +507,33 @@ func (e *Exporter) MapsHandler(w http.ResponseWriter, r *http.Request) {
497507
}
498508

499509
func validateMaps(module *libbpfgo.Module, cfg config.Config) error {
500-
maps := []string{}
510+
sizes := map[string]int{}
501511

502512
for _, counter := range cfg.Metrics.Counters {
503513
if counter.Name != "" {
504-
maps = append(maps, counter.Name)
514+
if counter.Values != nil {
515+
sizes[counter.Name] = len(counter.Values) * 8
516+
} else {
517+
sizes[counter.Name] = 8
518+
}
505519
}
506520
}
507521

508522
for _, histogram := range cfg.Metrics.Histograms {
509523
if histogram.Name != "" {
510-
maps = append(maps, histogram.Name)
524+
sizes[histogram.Name] = 8
511525
}
512526
}
513527

514-
for _, name := range maps {
528+
for name, expected := range sizes {
515529
m, err := module.GetMap(name)
516530
if err != nil {
517531
return fmt.Errorf("failed to get map %q: %v", name, err)
518532
}
519533

520534
valueSize := m.ValueSize()
521-
if valueSize != 8 {
522-
return fmt.Errorf("value size for map %q is not expected 8 bytes (u64), it is %d bytes", name, valueSize)
535+
if valueSize != expected {
536+
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)
523537
}
524538
}
525539

@@ -545,7 +559,9 @@ func aggregateMapValues(values []metricValue) []aggregatedMetricValue {
545559
value: value.value,
546560
}
547561
} else {
548-
existing.value += value.value
562+
for i := range existing.value {
563+
existing.value[i] += value.value[i]
564+
}
549565
}
550566
}
551567

@@ -609,17 +625,17 @@ func readMapValues(m *libbpfgo.BPFMap, labels []config.Label) ([]metricValue, er
609625
return metricValues, nil
610626
}
611627

612-
func mapValue(m *libbpfgo.BPFMap, key []byte) (float64, error) {
628+
func mapValue(m *libbpfgo.BPFMap, key []byte) ([]float64, error) {
613629
v, err := m.GetValue(unsafe.Pointer(&key[0]))
614630
if err != nil {
615-
return 0.0, err
631+
return []float64{0.0}, err
616632
}
617633

618634
return decodeValue(v), nil
619635
}
620636

621-
func mapValuePerCPU(m *libbpfgo.BPFMap, key []byte) ([]float64, error) {
622-
values := []float64{}
637+
func mapValuePerCPU(m *libbpfgo.BPFMap, key []byte) ([][]float64, error) {
638+
values := [][]float64{}
623639

624640
size := m.ValueSize()
625641
value := make([]byte, size*runtime.NumCPU())
@@ -636,8 +652,14 @@ func mapValuePerCPU(m *libbpfgo.BPFMap, key []byte) ([]float64, error) {
636652
}
637653

638654
// Assuming counter's value type is always u64
639-
func decodeValue(value []byte) float64 {
640-
return float64(util.GetHostByteOrder().Uint64(value))
655+
func decodeValue(value []byte) []float64 {
656+
values := make([]float64, len(value)/8)
657+
658+
for i := range values {
659+
values[i] = float64(util.GetHostByteOrder().Uint64(value[i*8:]))
660+
}
661+
662+
return values
641663
}
642664

643665
// metricValue is a row in a kernel map
@@ -647,13 +669,13 @@ type metricValue struct {
647669
// labels are decoded from the raw key
648670
labels []string
649671
// value is the kernel map value
650-
value float64
672+
value []float64
651673
}
652674

653675
// aggregatedMetricValue is a value after aggregation of equal label sets
654676
type aggregatedMetricValue struct {
655677
// labels are decoded from the raw key
656678
labels []string
657679
// value is the kernel map value
658-
value float64
680+
value []float64
659681
}

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)