diff --git a/gotests/cgo_helpers.go b/gotests/cgo_helpers.go index 38a27550..172a8d5a 100644 --- a/gotests/cgo_helpers.go +++ b/gotests/cgo_helpers.go @@ -20,6 +20,11 @@ package main #include #include +struct netdata_ringbuf_stats { + uint64_t samples; + uint64_t bytes; +}; + #ifdef LIBBPF_MAJOR_VERSION static int netdata_libbpf_probe_bpf_map_type(unsigned int map_type) { @@ -113,6 +118,99 @@ static int netdata_close_fd(int fd) { return close(fd); } + +static int netdata_ring_buffer_sample_cb(void *ctx, void *data, size_t size) +{ + struct netdata_ringbuf_stats *stats = ctx; + + (void)data; + if (stats) { + stats->samples++; + stats->bytes += size; + } + + return 0; +} + +static struct netdata_ringbuf_stats *netdata_ringbuf_stats_new(void) +{ + return calloc(1, sizeof(struct netdata_ringbuf_stats)); +} + +static void netdata_ringbuf_stats_free(struct netdata_ringbuf_stats *stats) +{ + free(stats); +} + +static uint64_t netdata_ringbuf_stats_samples(const struct netdata_ringbuf_stats *stats) +{ + return stats ? stats->samples : 0; +} + +static uint64_t netdata_ringbuf_stats_bytes(const struct netdata_ringbuf_stats *stats) +{ + return stats ? stats->bytes : 0; +} + +static struct ring_buffer *netdata_ring_buffer_new(int map_fd, struct netdata_ringbuf_stats *stats) +{ + return ring_buffer__new(map_fd, netdata_ring_buffer_sample_cb, stats, NULL); +} + +static int netdata_ring_buffer_poll(struct ring_buffer *rb, int timeout_ms) +{ + return ring_buffer__poll(rb, timeout_ms); +} + +static uint64_t netdata_ring_buffer_avail_data(const struct ring_buffer *rb) +{ + struct ring *ring = ring_buffer__ring((struct ring_buffer *)rb, 0); + + if (!ring) + return 0; + + return (uint64_t)ring__avail_data_size(ring); +} + +static uint64_t netdata_ring_buffer_size(const struct ring_buffer *rb) +{ + struct ring *ring = ring_buffer__ring((struct ring_buffer *)rb, 0); + + if (!ring) + return 0; + + return (uint64_t)ring__size(ring); +} + +static void netdata_ring_buffer_free(struct ring_buffer *rb) +{ + ring_buffer__free(rb); +} + +static struct user_ring_buffer *netdata_user_ring_buffer_new(int map_fd) +{ + return user_ring_buffer__new(map_fd, NULL); +} + +static int netdata_user_ring_buffer_submit_u64(struct user_ring_buffer *rb, uint64_t value) +{ + void *sample; + + errno = 0; + sample = user_ring_buffer__reserve(rb, sizeof(value)); + if (!sample) + return errno ? -errno : -1; + + memcpy(sample, &value, sizeof(value)); + user_ring_buffer__submit(rb, sample); + + return 0; +} + +static void netdata_user_ring_buffer_free(struct user_ring_buffer *rb) +{ + user_ring_buffer__free(rb); +} */ import "C" @@ -129,6 +227,8 @@ const ( bpfMapTypeArray = uint32(C.BPF_MAP_TYPE_ARRAY) bpfMapTypePerCPUHash = uint32(C.BPF_MAP_TYPE_PERCPU_HASH) bpfMapTypePerCPUArray = uint32(C.BPF_MAP_TYPE_PERCPU_ARRAY) + bpfMapTypeRingBuf = uint32(C.BPF_MAP_TYPE_RINGBUF) + bpfMapTypeUserRingBuf = uint32(C.BPF_MAP_TYPE_USER_RINGBUF) ) type bpfObject struct { @@ -147,6 +247,15 @@ type bpfLink struct { ptr *C.struct_bpf_link } +type ringBuffer struct { + ptr *C.struct_ring_buffer + stats *C.struct_netdata_ringbuf_stats +} + +type userRingBuffer struct { + ptr *C.struct_user_ring_buffer +} + type mapMeta struct { Name string FD int @@ -365,3 +474,70 @@ func closeFD(fd int) error { return nil } + +func newRingBuffer(mapFD int) (*ringBuffer, int) { + stats := C.netdata_ringbuf_stats_new() + if stats == nil { + return nil, -int(C.ENOMEM) + } + + rb := C.netdata_ring_buffer_new(C.int(mapFD), stats) + if err := int(C.netdata_libbpf_get_error(unsafe.Pointer(rb))); err != 0 { + C.netdata_ringbuf_stats_free(stats) + return nil, err + } + + return &ringBuffer{ptr: rb, stats: stats}, 0 +} + +func (rb *ringBuffer) free() { + if rb == nil { + return + } + + if rb.ptr != nil { + C.netdata_ring_buffer_free(rb.ptr) + } + if rb.stats != nil { + C.netdata_ringbuf_stats_free(rb.stats) + } +} + +func (rb *ringBuffer) poll(timeoutMS int) int { + return int(C.netdata_ring_buffer_poll(rb.ptr, C.int(timeoutMS))) +} + +func (rb *ringBuffer) samples() uint64 { + return uint64(C.netdata_ringbuf_stats_samples(rb.stats)) +} + +func (rb *ringBuffer) bytes() uint64 { + return uint64(C.netdata_ringbuf_stats_bytes(rb.stats)) +} + +func (rb *ringBuffer) availData() uint64 { + return uint64(C.netdata_ring_buffer_avail_data(rb.ptr)) +} + +func (rb *ringBuffer) size() uint64 { + return uint64(C.netdata_ring_buffer_size(rb.ptr)) +} + +func newUserRingBuffer(mapFD int) (*userRingBuffer, int) { + rb := C.netdata_user_ring_buffer_new(C.int(mapFD)) + if err := int(C.netdata_libbpf_get_error(unsafe.Pointer(rb))); err != 0 { + return nil, err + } + + return &userRingBuffer{ptr: rb}, 0 +} + +func (rb *userRingBuffer) free() { + if rb != nil && rb.ptr != nil { + C.netdata_user_ring_buffer_free(rb.ptr) + } +} + +func (rb *userRingBuffer) submitUint64(value uint64) int { + return int(C.netdata_user_ring_buffer_submit_u64(rb.ptr, C.uint64_t(value))) +} diff --git a/gotests/main.go b/gotests/main.go index 99351d4e..82bba5f0 100644 --- a/gotests/main.go +++ b/gotests/main.go @@ -663,9 +663,18 @@ func detectSupportedMapTypes(rhfVersion int, kernelVersion int) map[uint32]bool bpfMapTypeArray: kernelVersion >= netdataMinimumEBPFKernel || rhfVersion > 0, bpfMapTypePerCPUHash: fallbackPerCPUMapSupport(rhfVersion, kernelVersion), bpfMapTypePerCPUArray: fallbackPerCPUMapSupport(rhfVersion, kernelVersion), - } - - for _, mapType := range []uint32{bpfMapTypeHash, bpfMapTypeArray, bpfMapTypePerCPUHash, bpfMapTypePerCPUArray} { + bpfMapTypeRingBuf: false, + bpfMapTypeUserRingBuf: false, + } + + for _, mapType := range []uint32{ + bpfMapTypeHash, + bpfMapTypeArray, + bpfMapTypePerCPUHash, + bpfMapTypePerCPUArray, + bpfMapTypeRingBuf, + bpfMapTypeUserRingBuf, + } { if probe := probeMapTypeSupport(mapType); probe >= 0 { supported[mapType] = probe > 0 } @@ -684,14 +693,25 @@ func mapTypeName(mapType uint32) string { return "percpu_hash" case bpfMapTypePerCPUArray: return "percpu_array" + case bpfMapTypeRingBuf: + return "ringbuf" + case bpfMapTypeUserRingBuf: + return "user_ringbuf" default: return fmt.Sprintf("type_%d", mapType) } } func writeSupportedMapTypes(w io.Writer, supported map[uint32]bool) { - names := make([]string, 0, 4) - for _, mapType := range []uint32{bpfMapTypeHash, bpfMapTypeArray, bpfMapTypePerCPUHash, bpfMapTypePerCPUArray} { + names := make([]string, 0, 6) + for _, mapType := range []uint32{ + bpfMapTypeHash, + bpfMapTypeArray, + bpfMapTypePerCPUHash, + bpfMapTypePerCPUArray, + bpfMapTypeRingBuf, + bpfMapTypeUserRingBuf, + } { if supported[mapType] { names = append(names, fmt.Sprintf("\"%s\"", mapTypeName(mapType))) } @@ -1121,6 +1141,18 @@ func isPerCPUMapType(mapType uint32) bool { return mapType == bpfMapTypePerCPUArray || mapType == bpfMapTypePerCPUHash } +func isRingBufferMapType(mapType uint32) bool { + return mapType == bpfMapTypeRingBuf || mapType == bpfMapTypeUserRingBuf +} + +func isUserRingBufferMapType(mapType uint32) bool { + return mapType == bpfMapTypeUserRingBuf +} + +func supportsMapKeyValueIO(mapType uint32) bool { + return !isRingBufferMapType(mapType) +} + func roundUpSize(value int, align int) int { return ((value + align - 1) / align) * align } @@ -1232,10 +1264,93 @@ func controllerJSON(w io.Writer, fd int, meta mapMeta, nprocesses int) { filled+zero, filled, zero) } +func testRingBufferMap(w io.Writer, meta mapMeta, iterations int) { + mode := "ringbuf_consumer" + var ( + rb *ringBuffer + urb *userRingBuffer + setupErr int + ) + + if isUserRingBufferMapType(meta.Type) { + mode = "user_ringbuf_producer" + urb, setupErr = newUserRingBuffer(meta.FD) + } else { + rb, setupErr = newRingBuffer(meta.FD) + } + + fmt.Fprintf(w, + " \"%s\" : {\n \"Info\" : { \"Length\" : { \"Key\" : %d, \"Value\" : %d},\n"+ + " \"Type\" : %d,\n"+ + " \"FD\" : %d,\n"+ + " \"Data\" : [\n", + meta.Name, meta.KeySize, meta.ValueSize, meta.Type, meta.FD) + + var prevSamples uint64 + var prevBytes uint64 + for i := 0; i < iterations; i++ { + time.Sleep(5 * time.Second) + + opErr := setupErr + opResult := 0 + var iterSamples uint64 + var iterBytes uint64 + var availData uint64 + var ringSize uint64 + + if rb != nil { + opResult = rb.poll(0) + if opResult < 0 { + opErr = opResult + } + + samples := rb.samples() + bytes := rb.bytes() + iterSamples = samples - prevSamples + iterBytes = bytes - prevBytes + prevSamples = samples + prevBytes = bytes + availData = rb.availData() + ringSize = rb.size() + } else if urb != nil { + opResult = urb.submitUint64(uint64(i + 1)) + if opResult < 0 { + opErr = opResult + } + } + + if i > 0 { + fmt.Fprint(w, ",\n") + } + fmt.Fprintf(w, + " { \"Iteration\" : %d, \"Mode\" : \"%s\", \"Setup\" : %d, "+ + "\"Operation Result\" : %d, \"Samples\" : %d, \"Bytes\" : %d, "+ + "\"Available\" : %d, \"Ring Size\" : %d, \"Error Code\" : %d, "+ + "\"Error Message\" : \"%s\" }", + i, mode, boolToInt(setupErr == 0), opResult, iterSamples, iterBytes, + availData, ringSize, opErr, describeError(opErr)) + } + fmt.Fprint(w, "\n") + + if rb != nil { + rb.free() + } + if urb != nil { + urb.free() + } +} + func testMaps(w io.Writer, obj *bpfObject, ctrl string, iterations int, nprocesses int) { tables := 0 for m := obj.firstMap(); m != nil; m = obj.nextMap(m) { meta := m.meta() + if !supportsMapKeyValueIO(meta.Type) { + testRingBufferMap(w, meta, iterations) + fmt.Fprint(w, " ]\n }\n },\n") + tables++ + continue + } + values := allocateTableData(meta, nprocesses) fmt.Fprintf(w, " \"%s\" : {\n \"Info\" : { \"Length\" : { \"Key\" : %d, \"Value\" : %d},\n"+ @@ -1266,6 +1381,9 @@ func fillCtrl(obj *bpfObject, ctrl string, mapLevel int, nprocesses int) { } meta := m.meta() + if !supportsMapKeyValueIO(meta.Type) { + return + } values := []uint64{1, uint64(mapLevel), 0, 0, 0, 0} key := make([]byte, meta.KeySize) value := make([]byte, mapValueLength(meta, nprocesses)) diff --git a/gotests/main_test.go b/gotests/main_test.go index b87455fd..00d4ef93 100644 --- a/gotests/main_test.go +++ b/gotests/main_test.go @@ -210,14 +210,37 @@ func TestCandidateSelectionHelpers(t *testing.T) { bpfMapTypeArray: true, bpfMapTypePerCPUHash: true, bpfMapTypePerCPUArray: false, + bpfMapTypeRingBuf: false, + bpfMapTypeUserRingBuf: true, } - mapType, ok := firstUnsupportedMapType([]uint32{bpfMapTypeHash, bpfMapTypePerCPUArray}, supported) + mapType, ok := firstUnsupportedMapType([]uint32{bpfMapTypeHash, bpfMapTypeRingBuf, bpfMapTypePerCPUArray}, supported) if !ok { t.Fatal("expected unsupported map type") } - if mapType != bpfMapTypePerCPUArray { - t.Fatalf("unexpected unsupported map type: got %d want %d", mapType, bpfMapTypePerCPUArray) + if mapType != bpfMapTypeRingBuf { + t.Fatalf("unexpected unsupported map type: got %d want %d", mapType, bpfMapTypeRingBuf) + } + }) + + t.Run("names ring buffer map types", func(t *testing.T) { + if got := mapTypeName(bpfMapTypeRingBuf); got != "ringbuf" { + t.Fatalf("unexpected ringbuf name: %q", got) + } + if got := mapTypeName(bpfMapTypeUserRingBuf); got != "user_ringbuf" { + t.Fatalf("unexpected user ringbuf name: %q", got) + } + }) + + t.Run("disables key value io for ring buffers", func(t *testing.T) { + if supportsMapKeyValueIO(bpfMapTypeRingBuf) { + t.Fatal("ringbuf should not use generic key/value io") + } + if supportsMapKeyValueIO(bpfMapTypeUserRingBuf) { + t.Fatal("user ringbuf should not use generic key/value io") + } + if !supportsMapKeyValueIO(bpfMapTypeHash) { + t.Fatal("hash maps should keep generic key/value io") } }) diff --git a/includes/netdata_common.h b/includes/netdata_common.h index 717b6894..e6df9e0e 100644 --- a/includes/netdata_common.h +++ b/includes/netdata_common.h @@ -233,6 +233,12 @@ static __always_inline __u32 monitor_apps(void *ctrl_tbl) __uint(max_entries, MAX_ENTRIES); \ } NAME SEC(".maps") +#define NETDATA_BPF_RINGBUF_MAP_DEF(NAME, TYPE, MAX_ENTRIES) \ + struct { \ + __uint(type, TYPE); \ + __uint(max_entries, MAX_ENTRIES); \ + } NAME SEC(".maps") + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0)) #define NETDATA_BPF_HASH_DEF(NAME, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) \ NETDATA_BPF_MAP_DEF(NAME, BPF_MAP_TYPE_HASH, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) @@ -245,6 +251,12 @@ static __always_inline __u32 monitor_apps(void *ctrl_tbl) #define NETDATA_BPF_PERCPU_ARRAY_DEF(NAME, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) \ NETDATA_BPF_MAP_DEF(NAME, BPF_MAP_TYPE_PERCPU_ARRAY, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) + +#define NETDATA_BPF_RINGBUF_DEF(NAME, MAX_ENTRIES) \ + NETDATA_BPF_RINGBUF_MAP_DEF(NAME, BPF_MAP_TYPE_RINGBUF, MAX_ENTRIES) + +#define NETDATA_BPF_USER_RINGBUF_DEF(NAME, MAX_ENTRIES) \ + NETDATA_BPF_RINGBUF_MAP_DEF(NAME, BPF_MAP_TYPE_USER_RINGBUF, MAX_ENTRIES) #else #define NETDATA_BPF_HASH_DEF(NAME, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) \ NETDATA_BPF_MAP_DEF(NAME, BPF_MAP_TYPE_HASH, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) @@ -257,6 +269,12 @@ static __always_inline __u32 monitor_apps(void *ctrl_tbl) #define NETDATA_BPF_PERCPU_ARRAY_DEF(NAME, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) \ NETDATA_BPF_MAP_DEF(NAME, BPF_MAP_TYPE_ARRAY, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) + +#define NETDATA_BPF_RINGBUF_DEF(NAME, MAX_ENTRIES) \ + NETDATA_BPF_RINGBUF_MAP_DEF(NAME, BPF_MAP_TYPE_RINGBUF, MAX_ENTRIES) + +#define NETDATA_BPF_USER_RINGBUF_DEF(NAME, MAX_ENTRIES) \ + NETDATA_BPF_RINGBUF_MAP_DEF(NAME, BPF_MAP_TYPE_USER_RINGBUF, MAX_ENTRIES) #endif #else @@ -268,6 +286,14 @@ static __always_inline __u32 monitor_apps(void *ctrl_tbl) .max_entries = MAX_ENTRIES \ } +#define NETDATA_BPF_RINGBUF_MAP_DEF(NAME, TYPE, MAX_ENTRIES) \ + struct bpf_map_def SEC("maps") NAME = { \ + .type = TYPE, \ + .key_size = 0, \ + .value_size = 0, \ + .max_entries = MAX_ENTRIES \ + } + #define NETDATA_BPF_HASH_DEF(NAME, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) \ NETDATA_BPF_MAP_DEF(NAME, BPF_MAP_TYPE_HASH, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) @@ -280,7 +306,12 @@ static __always_inline __u32 monitor_apps(void *ctrl_tbl) #define NETDATA_BPF_PERCPU_ARRAY_DEF(NAME, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) \ NETDATA_BPF_MAP_DEF(NAME, BPF_MAP_TYPE_ARRAY, KEY_TYPE, VALUE_TYPE, MAX_ENTRIES) +#define NETDATA_BPF_RINGBUF_DEF(NAME, MAX_ENTRIES) \ + NETDATA_BPF_RINGBUF_MAP_DEF(NAME, BPF_MAP_TYPE_RINGBUF, MAX_ENTRIES) + +#define NETDATA_BPF_USER_RINGBUF_DEF(NAME, MAX_ENTRIES) \ + NETDATA_BPF_RINGBUF_MAP_DEF(NAME, BPF_MAP_TYPE_USER_RINGBUF, MAX_ENTRIES) + #endif #endif /* _NETDATA_COMMON_ */ - diff --git a/kernel/DEVELOPER.md b/kernel/DEVELOPER.md index 2943a080..21510391 100644 --- a/kernel/DEVELOPER.md +++ b/kernel/DEVELOPER.md @@ -62,13 +62,21 @@ unnecessary headers inside eBPF codes. Thanks this every time you see the prepro means that we are using codes that will be compiled with latest `libbpf` version. -For all hash tables we are defining: +For key/value tables we are defining: - Type: We are working with [HASH](https://docs.kernel.org/bpf/map_hash.html) and [ARRAY](https://docs.kernel.org/bpf/map_array.html) - Key size: the size in bytes of the key. - Value size: the size in bytes of the value. - Max entries: Number of entries expected in the table. +For ring-buffer maps we use dedicated helpers in [`includes/netdata_common.h`](../includes/netdata_common.h): + +- `NETDATA_BPF_RINGBUF_DEF` +- `NETDATA_BPF_USER_RINGBUF_DEF` + +These maps are not generic key/value maps. Their key and value sizes are zero, and user-space tests must use +ring-buffer-specific logic instead of `lookup`, `get_next_key`, or generic `update` paths. + ### Tracers In this repo we use the following tracers: @@ -105,6 +113,9 @@ for j in `seq 0 2`; do for i in `ls *.o`; do ./gotests/go_tester --content --pid The `legacy_test` and `go_tester` binaries are compiled with debug symbols to make local debugging easier. +When a tested object contains `BPF_MAP_TYPE_RINGBUF` or `BPF_MAP_TYPE_USER_RINGBUF`, both testers switch to +ring-buffer-specific handling. They no longer try to iterate those maps as generic key/value tables. + You can take a look in all options available for tests running: ```sh diff --git a/tests/tester_user.c b/tests/tester_user.c index df9fe44a..ba041caa 100644 --- a/tests/tester_user.c +++ b/tests/tester_user.c @@ -128,8 +128,15 @@ typedef struct ebpf_map_support { int array; int percpu_array; int percpu_hash; + int ringbuf; + int user_ringbuf; } ebpf_map_support_t; +typedef struct ebpf_ringbuf_stats { + size_t samples; + size_t bytes; +} ebpf_ringbuf_stats_t; + typedef struct ebpf_candidate_list { char **files; size_t size; @@ -159,6 +166,21 @@ static int ebpf_map_is_percpu(uint32_t type) return type == BPF_MAP_TYPE_PERCPU_ARRAY || type == BPF_MAP_TYPE_PERCPU_HASH; } +static int ebpf_map_is_ringbuf(uint32_t type) +{ + return type == BPF_MAP_TYPE_RINGBUF || type == BPF_MAP_TYPE_USER_RINGBUF; +} + +static int ebpf_map_is_user_ringbuf(uint32_t type) +{ + return type == BPF_MAP_TYPE_USER_RINGBUF; +} + +static int ebpf_map_supports_key_value_io(uint32_t type) +{ + return !ebpf_map_is_ringbuf(type); +} + static size_t ebpf_map_value_stride(uint32_t type, size_t value_size) { if (!ebpf_map_is_percpu(type)) @@ -265,6 +287,34 @@ static const char *ebpf_describe_error(int err, char *buffer, size_t length) return buffer; } +static int ebpf_ringbuf_sample_cb(void *ctx, void *data, size_t size) +{ + ebpf_ringbuf_stats_t *stats = ctx; + + (void)data; + if (stats) { + stats->samples++; + stats->bytes += size; + } + + return 0; +} + +static int ebpf_user_ringbuf_submit_sample(struct user_ring_buffer *rb, uint64_t value) +{ + void *sample; + + errno = 0; + sample = user_ring_buffer__reserve(rb, sizeof(value)); + if (!sample) + return -(errno ? errno : EIO); + + memcpy(sample, &value, sizeof(value)); + user_ring_buffer__submit(rb, sample); + + return 0; +} + static void ebpf_write_program_inventory(struct bpf_object *obj) { struct bpf_program *prog; @@ -482,6 +532,8 @@ static void ebpf_detect_map_support(ebpf_map_support_t *support, int rhf_version support->array = support->hash; support->percpu_array = ebpf_should_fallback_percpu_support(rhf_version, kver); support->percpu_hash = support->percpu_array; + support->ringbuf = 0; + support->user_ringbuf = 0; probe = ebpf_probe_map_type_support(BPF_MAP_TYPE_HASH); if (probe >= 0) @@ -498,6 +550,14 @@ static void ebpf_detect_map_support(ebpf_map_support_t *support, int rhf_version probe = ebpf_probe_map_type_support(BPF_MAP_TYPE_PERCPU_HASH); if (probe >= 0) support->percpu_hash = probe > 0; + + probe = ebpf_probe_map_type_support(BPF_MAP_TYPE_RINGBUF); + if (probe >= 0) + support->ringbuf = probe > 0; + + probe = ebpf_probe_map_type_support(BPF_MAP_TYPE_USER_RINGBUF); + if (probe >= 0) + support->user_ringbuf = probe > 0; } static const char *ebpf_map_type_name(int map_type) @@ -511,6 +571,10 @@ static const char *ebpf_map_type_name(int map_type) return "percpu_hash"; case BPF_MAP_TYPE_PERCPU_ARRAY: return "percpu_array"; + case BPF_MAP_TYPE_RINGBUF: + return "ringbuf"; + case BPF_MAP_TYPE_USER_RINGBUF: + return "user_ringbuf"; default: return "unknown"; } @@ -527,6 +591,10 @@ static int ebpf_is_supported_map_type(const ebpf_map_support_t *support, int map return support->percpu_hash; case BPF_MAP_TYPE_PERCPU_ARRAY: return support->percpu_array; + case BPF_MAP_TYPE_RINGBUF: + return support->ringbuf; + case BPF_MAP_TYPE_USER_RINGBUF: + return support->user_ringbuf; default: return 1; } @@ -573,8 +641,16 @@ static void ebpf_write_supported_map_types_json(const ebpf_map_support_t *suppor fprintf(stdlog, "%s\"percpu_hash\"", first ? "" : ", "); first = 0; } - if (support->percpu_array) + if (support->percpu_array) { fprintf(stdlog, "%s\"percpu_array\"", first ? "" : ", "); + first = 0; + } + if (support->ringbuf) { + fprintf(stdlog, "%s\"ringbuf\"", first ? "" : ", "); + first = 0; + } + if (support->user_ringbuf) + fprintf(stdlog, "%s\"user_ringbuf\"", first ? "" : ", "); fprintf(stdlog, "]"); } @@ -582,7 +658,7 @@ static void ebpf_write_supported_map_types_json(const ebpf_map_support_t *suppor static void ebpf_write_object_map_types(struct bpf_object *obj) { struct bpf_map *map; - int seen_hash = 0, seen_array = 0, seen_percpu_hash = 0, seen_percpu_array = 0; + int seen[__MAX_BPF_MAP_TYPE] = { 0 }; int first = 1; fprintf(stdlog, " \"Map Types Used\" : ["); @@ -598,10 +674,7 @@ static void ebpf_write_object_map_types(struct bpf_object *obj) } #endif - if ((type == BPF_MAP_TYPE_HASH && seen_hash) || - (type == BPF_MAP_TYPE_ARRAY && seen_array) || - (type == BPF_MAP_TYPE_PERCPU_HASH && seen_percpu_hash) || - (type == BPF_MAP_TYPE_PERCPU_ARRAY && seen_percpu_array)) + if (type >= 0 && type < __MAX_BPF_MAP_TYPE && seen[type]) continue; if (!first) @@ -610,14 +683,8 @@ static void ebpf_write_object_map_types(struct bpf_object *obj) fprintf(stdlog, "\"%s\"", ebpf_map_type_name(type)); first = 0; - if (type == BPF_MAP_TYPE_HASH) - seen_hash = 1; - else if (type == BPF_MAP_TYPE_ARRAY) - seen_array = 1; - else if (type == BPF_MAP_TYPE_PERCPU_HASH) - seen_percpu_hash = 1; - else if (type == BPF_MAP_TYPE_PERCPU_ARRAY) - seen_percpu_array = 1; + if (type >= 0 && type < __MAX_BPF_MAP_TYPE) + seen[type] = 1; } } @@ -1321,6 +1388,92 @@ static void ebpf_controller_json(ebpf_table_data_t *values, int fd) value + zero, value, zero); } +static void ebpf_test_ringbuf_map(const char *name, int fd, uint32_t type, uint32_t key_size, uint32_t value_size) +{ + char error_buffer[128]; + const char *mode = ebpf_map_is_user_ringbuf(type) ? "user_ringbuf_producer" : "ringbuf_consumer"; + struct ring_buffer *rb = NULL; + struct user_ring_buffer *urb = NULL; + ebpf_ringbuf_stats_t stats = { 0 }; + ebpf_ringbuf_stats_t previous = { 0 }; + int setup_error = 0; + int i; + + fprintf(stdlog, + " \"%s\" : {\n" + " \"Info\" : { \"Length\" : { \"Key\" : %u, \"Value\" : %u},\n" + " \"Type\" : %u,\n" + " \"FD\" : %d,\n" + " \"Data\" : [\n", + name, key_size, value_size, type, fd); + + if (type == BPF_MAP_TYPE_RINGBUF) { + rb = ring_buffer__new(fd, ebpf_ringbuf_sample_cb, &stats, NULL); + setup_error = (int)libbpf_get_error(rb); + if (setup_error) + rb = NULL; + } else { + urb = user_ring_buffer__new(fd, NULL); + setup_error = (int)libbpf_get_error(urb); + if (setup_error) + urb = NULL; + } + + for (i = 0; i < end_iteration; i++) { + int op_error = setup_error; + int op_result = 0; + size_t iter_samples = 0; + size_t iter_bytes = 0; + size_t ring_size = 0; + size_t avail_data = 0; + const char *error_message; + + sleep(5); + + if (type == BPF_MAP_TYPE_RINGBUF && rb) { + struct ring *ring = ring_buffer__ring(rb, 0); + + op_result = ring_buffer__poll(rb, 0); + if (op_result < 0) + op_error = op_result; + + iter_samples = stats.samples - previous.samples; + iter_bytes = stats.bytes - previous.bytes; + previous = stats; + + if (ring) { + ring_size = ring__size(ring); + avail_data = ring__avail_data_size(ring); + } + } else if (type == BPF_MAP_TYPE_USER_RINGBUF && urb) { + op_result = ebpf_user_ringbuf_submit_sample(urb, (uint64_t)(i + 1)); + if (op_result < 0) + op_error = op_result; + } + + error_message = ebpf_describe_error(op_error, error_buffer, sizeof(error_buffer)); + + if (i) + fprintf(stdlog, ",\n"); + + fprintf(stdlog, + " " + "{ \"Iteration\" : %d, \"Mode\" : \"%s\", \"Setup\" : %d, " + "\"Operation Result\" : %d, \"Samples\" : %zu, \"Bytes\" : %zu, " + "\"Available\" : %zu, \"Ring Size\" : %zu, \"Error Code\" : %d, " + "\"Error Message\" : \"%s\" }", + i, mode, setup_error == 0, op_result, iter_samples, iter_bytes, + avail_data, ring_size, op_error, error_message); + } + + fprintf(stdlog, "\n"); + + if (rb) + ring_buffer__free(rb); + if (urb) + user_ring_buffer__free(urb); +} + /** * Test Maps * @@ -1352,6 +1505,15 @@ static void ebpf_test_maps(struct bpf_object *obj, char *ctrl) key_size = def->key_size; value_size = def->value_size; #endif + if (!ebpf_map_supports_key_value_io(type)) { + ebpf_test_ringbuf_map(name, fd, type, key_size, value_size); + fprintf(stdlog, " ]\n" + " }\n" + " },\n"); + tables++; + continue; + } + values = ebpf_allocate_tables(name, type, key_size, value_size); if (values) { // Write header @@ -1418,6 +1580,12 @@ static void ebpf_fill_ctrl(struct bpf_object *obj, char *ctrl) value_size = def->value_size; end = def->max_entries; #endif + if (!ebpf_map_supports_key_value_io(type)) { + fprintf(stdlog, "\"error\" : \"Control table %s uses unsupported map type %s.\",", + name, ebpf_map_type_name(type)); + continue; + } + uint64_t values[NETDATA_CONTROLLER_END] = { 1, (uint64_t)map_level, 0, 0, 0, 0 }; size_t value_length = ebpf_map_value_buffer_length(type, value_size); size_t value_stride = ebpf_map_value_stride(type, value_size);