diff --git a/gotests/main.go b/gotests/main.go index 3bd56737..00a1f7d2 100644 --- a/gotests/main.go +++ b/gotests/main.go @@ -436,6 +436,25 @@ func parseOSRelease(content string) int { return -1 } +func IsDebianFlavor() bool { + data, err := os.ReadFile("/etc/os-release") + if err != nil { + return false + } + + for _, line := range strings.Split(string(data), "\n") { + if !strings.HasPrefix(line, "ID=") { + continue + } + + value := strings.TrimSpace(strings.TrimPrefix(line, "ID=")) + value = strings.Trim(value, "\"'") + return value == "debian" + } + + return false +} + func parseRedHatRelease(release string) int { major := 0 minor := -1 @@ -724,6 +743,30 @@ func modeSuffix(bufferMode bool, arenaMode bool) string { return "" } +func effectiveModeFlags(moduleName string, kernelVersion int, isDebian bool, bufferMode bool, arenaMode bool) (bool, bool) { + if bufferMode || arenaMode { + return bufferMode, arenaMode + } + + if moduleName == "cachestat" { + if kernelVersion >= netdataEBPFKernel510 { + return true, false + } + + return false, false + } + + if moduleHasArena(moduleName) && kernelVersion >= netdataEBPFKernel612 && !isDebian { + return false, true + } + + if moduleHasBuffer(moduleName) && kernelVersion >= netdataEBPFKernel510 { + return true, false + } + + return false, false +} + func candidateMatches(filename string, moduleName string, isReturn bool, version string, rhfVersion int, bufferMode bool, arenaMode bool) bool { prefix := fmt.Sprintf("%cnetdata_ebpf_%s%s.", map[bool]rune{true: 'r', false: 'p'}[isReturn], moduleName, modeSuffix(bufferMode, arenaMode)) if !strings.HasPrefix(filename, prefix) || !strings.HasSuffix(filename, ".o") { @@ -1050,29 +1093,32 @@ func moduleHasArena(name string) bool { func runNetdataTests(w io.Writer, rhfVersion int, kernelVersion int, isReturn bool, opts options, nprocesses int) { supportedMapTypes := detectSupportedMapTypes(rhfVersion, kernelVersion) + isDebian := IsDebianFlavor() for _, mod := range ebpfModules { + bufferMode, arenaMode := effectiveModeFlags(mod.name, kernelVersion, isDebian, opts.bufferMode, opts.arenaMode) + if opts.flags&mod.flags == 0 { continue } - if opts.arenaMode && !moduleHasArena(mod.name) { + if arenaMode && !moduleHasArena(mod.name) { continue } - if opts.bufferMode && !moduleHasBuffer(mod.name) { + if bufferMode && !moduleHasBuffer(mod.name) { continue } kernels := mod.kernels - if opts.arenaMode && mod.arenaKernels != 0 { + if arenaMode && mod.arenaKernels != 0 { kernels = mod.arenaKernels - } else if opts.bufferMode && mod.bufferKernels != 0 { + } else if bufferMode && mod.bufferKernels != 0 { kernels = mod.bufferKernels } maxIndex := selectMaxIndex(rhfVersion, kernelVersion) idx := selectIndex(kernels, rhfVersion, kernelVersion) - candidates := discoverCandidates(mod.name, isReturn, rhfVersion, kernels, maxIndex, opts.netdataPath, opts.bufferMode, opts.arenaMode) + candidates := discoverCandidates(mod.name, isReturn, rhfVersion, kernels, maxIndex, opts.netdataPath, bufferMode, arenaMode) compatible, incompatible, unsupportedType := filterCompatibleCandidates(candidates, supportedMapTypes) if len(compatible) == 0 { @@ -1083,7 +1129,7 @@ func runNetdataTests(w io.Writer, rhfVersion int, kernelVersion int, isReturn bo continue } - compatible = []string{mountName(idx, mod.name, isReturn, rhfVersion, opts.netdataPath, opts.bufferMode, opts.arenaMode)} + compatible = []string{mountName(idx, mod.name, isReturn, rhfVersion, opts.netdataPath, bufferMode, arenaMode)} } for _, filename := range compatible { diff --git a/gotests/main_test.go b/gotests/main_test.go index 9256f1d0..62105b92 100644 --- a/gotests/main_test.go +++ b/gotests/main_test.go @@ -135,7 +135,7 @@ VERSION_ID="12" want: -1, }, { - name: "empty content", + name: "empty content", content: ``, want: -1, }, @@ -763,6 +763,74 @@ func TestModeSuffix(t *testing.T) { } } +func TestEffectiveModeFlags(t *testing.T) { + tests := map[string]struct { + module string + kernelVersion int + isDebian bool + bufferMode bool + arenaMode bool + wantBuffer bool + wantArena bool + }{ + "cachestat-defaults-to-buffer-on-supported-kernel": { + module: "cachestat", + kernelVersion: netdataEBPFKernel510, + wantBuffer: true, + wantArena: false, + }, + "process-defaults-to-arena-on-6-12-nondebian": { + module: "process", + kernelVersion: netdataEBPFKernel612, + wantBuffer: false, + wantArena: true, + }, + "process-stays-buffer-on-debian": { + module: "process", + kernelVersion: netdataEBPFKernel612, + isDebian: true, + wantBuffer: true, + wantArena: false, + }, + "cachestat-keeps-tracing-before-buffer-support": { + module: "cachestat", + kernelVersion: netdataEBPFKernel414, + wantBuffer: false, + wantArena: false, + }, + "explicit-buffer-stays-buffer": { + module: "cachestat", + kernelVersion: netdataEBPFKernel612, + bufferMode: true, + wantBuffer: true, + wantArena: false, + }, + "explicit-arena-wins": { + module: "cachestat", + kernelVersion: netdataEBPFKernel612, + bufferMode: true, + arenaMode: true, + wantBuffer: true, + wantArena: true, + }, + "other-modules-unaffected": { + module: "swap", + kernelVersion: netdataEBPFKernel612, + wantBuffer: false, + wantArena: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + gotBuffer, gotArena := effectiveModeFlags(tc.module, tc.kernelVersion, tc.isDebian, tc.bufferMode, tc.arenaMode) + if gotBuffer != tc.wantBuffer || gotArena != tc.wantArena { + t.Fatalf("effectiveModeFlags() = (%v, %v), want (%v, %v)", gotBuffer, gotArena, tc.wantBuffer, tc.wantArena) + } + }) + } +} + func TestModuleModeLookup(t *testing.T) { bufferArenaModules := []string{"cachestat", "dc", "fd", "oomkill", "process", "shm", "swap", "vfs", "dns", "socket"} for _, name := range bufferArenaModules { @@ -896,45 +964,45 @@ func TestCandidateVersionIndex(t *testing.T) { wantIndex int }{ { - name: "rhf 5.14 matches at index 7", + name: "rhf 5.14 matches at index 7", filename: "pnetdata_ebpf_swap.5.14.rhf.o", - module: "swap", rhf: 1, kernels: netdataV514, maxIndex: 7, + module: "swap", rhf: 1, kernels: netdataV514, maxIndex: 7, wantIndex: 7, }, { - name: "non-rhf masks out V514", + name: "non-rhf masks out V514", filename: "pnetdata_ebpf_swap.5.14.rhf.o", - module: "swap", rhf: -1, kernels: netdataV514, maxIndex: 10, + module: "swap", rhf: -1, kernels: netdataV514, maxIndex: 10, wantIndex: -1, }, { - name: "non-rhf 6.8 matches at index 10", + name: "non-rhf 6.8 matches at index 10", filename: "pnetdata_ebpf_swap.6.8.o", - module: "swap", rhf: -1, kernels: netdataV68, maxIndex: 10, + module: "swap", rhf: -1, kernels: netdataV68, maxIndex: 10, wantIndex: 10, }, { - name: "picks file version from multi-version kernel set", + name: "picks file version from multi-version kernel set", filename: "pnetdata_ebpf_swap.5.4.o", - module: "swap", rhf: -1, kernels: netdataV54 | netdataV68, maxIndex: 10, + module: "swap", rhf: -1, kernels: netdataV54 | netdataV68, maxIndex: 10, wantIndex: 4, }, { - name: "wrong module name returns -1", + name: "wrong module name returns -1", filename: "pnetdata_ebpf_process.6.8.o", - module: "swap", rhf: -1, kernels: netdataV68, maxIndex: 10, + module: "swap", rhf: -1, kernels: netdataV68, maxIndex: 10, wantIndex: -1, }, { - name: "arena file matches with arenaMode enabled", + name: "arena file matches with arenaMode enabled", filename: "pnetdata_ebpf_swap_arena.6.12.o", - module: "swap", rhf: -1, kernels: netdataV612, maxIndex: 11, arenaMode: true, + module: "swap", rhf: -1, kernels: netdataV612, maxIndex: 11, arenaMode: true, wantIndex: 11, }, { - name: "arena file rejected without arenaMode", + name: "arena file rejected without arenaMode", filename: "pnetdata_ebpf_swap_arena.6.12.o", - module: "swap", rhf: -1, kernels: netdataV612, maxIndex: 11, arenaMode: false, + module: "swap", rhf: -1, kernels: netdataV612, maxIndex: 11, arenaMode: false, wantIndex: -1, }, } diff --git a/tests/tester_user.c b/tests/tester_user.c index 277bfe71..e4341165 100644 --- a/tests/tester_user.c +++ b/tests/tester_user.c @@ -490,7 +490,7 @@ static void ebpf_free_candidate_list(ebpf_candidate_list_t *list) memset(list, 0, sizeof(*list)); } -static const char *ebpf_mode_suffix(void) +static const char *ebpf_mode_suffix(int buffer_mode, int arena_mode) { if (arena_mode) return "_arena"; @@ -500,7 +500,7 @@ static const char *ebpf_mode_suffix(void) } static int ebpf_candidate_matches(const char *filename, const char *name, int is_return, - const char *version, int rhf_version) + const char *version, int rhf_version, int buffer_mode, int arena_mode) { char prefix[128]; size_t prefix_len; @@ -509,7 +509,8 @@ static int ebpf_candidate_matches(const char *filename, const char *name, int is const char *rest; int has_rhf; - snprintf(prefix, sizeof(prefix), "%cnetdata_ebpf_%s%s.", (is_return) ? 'r' : 'p', name, ebpf_mode_suffix()); + snprintf(prefix, sizeof(prefix), "%cnetdata_ebpf_%s%s.", (is_return) ? 'r' : 'p', name, + ebpf_mode_suffix(buffer_mode, arena_mode)); prefix_len = strlen(prefix); if (filename_len <= prefix_len + 2) return 0; @@ -532,7 +533,8 @@ static int ebpf_candidate_matches(const char *filename, const char *name, int is } static int ebpf_candidate_version_index(const char *filename, const char *name, int is_return, - int rhf_version, uint32_t kernels, uint32_t max_index) + int rhf_version, uint32_t kernels, uint32_t max_index, + int buffer_mode, int arena_mode) { int idx; @@ -543,7 +545,8 @@ static int ebpf_candidate_version_index(const char *filename, const char *name, if (!(kernels & (1U << idx))) continue; - if (ebpf_candidate_matches(filename, name, is_return, ebpf_kernel_names[idx], rhf_version)) + if (ebpf_candidate_matches(filename, name, is_return, ebpf_kernel_names[idx], rhf_version, + buffer_mode, arena_mode)) return idx; } @@ -551,7 +554,8 @@ static int ebpf_candidate_version_index(const char *filename, const char *name, } static void ebpf_discover_candidates(ebpf_candidate_list_t *list, const char *name, int is_return, - uint32_t kernels, uint32_t max_index, int rhf_version) + uint32_t kernels, uint32_t max_index, int rhf_version, + int buffer_mode, int arena_mode) { char *path = ebpf_resolve_binary_directory(); DIR *dir; @@ -576,7 +580,8 @@ static void ebpf_discover_candidates(ebpf_candidate_list_t *list, const char *na continue; candidate_index = ebpf_candidate_version_index(entry->d_name, name, is_return, - rhf_version, kernels, max_index); + rhf_version, kernels, max_index, + buffer_mode, arena_mode); if (candidate_index < 0) continue; @@ -964,6 +969,37 @@ int ebpf_get_redhat_release() return ebpf_get_rh_from_os_release(); } +static int ebpf_is_debian_flavor(void) +{ + FILE *fp = fopen("/etc/os-release", "r"); + if (!fp) + return 0; + + char line[VERSION_STRING_LEN + 1]; + int is_debian = 0; + + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "ID=", 3) != 0) + continue; + + char *value = line + 3; + if (*value == '"' || *value == '\'') + value++; + + char *end = strpbrk(value, "\"\n"); + if (end) + *end = '\0'; + + if (!strcmp(value, "debian")) { + is_debian = 1; + break; + } + } + + fclose(fp); + return is_debian; +} + /** * Kernel Name * @@ -1109,7 +1145,8 @@ static void ebpf_start_netdata_json(char *filename, int is_return) * @param is_return is return or entry ? * @param rhf_version Red Hat version. */ -static void ebpf_mount_name(char *out, size_t len, uint32_t kver, char *name, int is_return, int rhf_version) +static void ebpf_mount_name(char *out, size_t len, uint32_t kver, char *name, int is_return, int rhf_version, + int buffer_mode, int arena_mode) { char *version = ebpf_select_kernel_name(kver); char *path = ebpf_resolve_binary_directory(); @@ -1120,7 +1157,7 @@ static void ebpf_mount_name(char *out, size_t len, uint32_t kver, char *name, in path, (is_return) ? 'r' : 'p', name, - ebpf_mode_suffix(), + ebpf_mode_suffix(buffer_mode, arena_mode), version, (rhf_version != -1) ? ".rhf" : ""); free(path); @@ -1906,15 +1943,32 @@ static void ebpf_run_netdata_tests(int rhf_version, uint32_t kver, int is_return ebpf_map_support_t map_support; char load[FILENAME_MAX]; int i = 0; + int is_debian = ebpf_is_debian_flavor(); ebpf_detect_map_support(&map_support, rhf_version, kver); while (ebpf_modules[i].name) { - if (arena_mode && !ebpf_module_has_arena(ebpf_modules[i].name)) { + int use_buffer_mode = buffer_mode; + int use_arena_mode = arena_mode; + + if (!use_buffer_mode && !use_arena_mode) { + if (!strcmp(ebpf_modules[i].name, "cachestat")) { + if (kver >= NETDATA_EBPF_KERNEL_5_10) + use_buffer_mode = 1; + } else if (ebpf_module_has_arena(ebpf_modules[i].name) && + kver >= NETDATA_EBPF_KERNEL_6_12 && !is_debian) { + use_arena_mode = 1; + } else if (ebpf_module_has_buffer(ebpf_modules[i].name) && + kver >= NETDATA_EBPF_KERNEL_5_10) { + use_buffer_mode = 1; + } + } + + if (use_arena_mode && !ebpf_module_has_arena(ebpf_modules[i].name)) { i++; continue; } - if (buffer_mode && !ebpf_module_has_buffer(ebpf_modules[i].name)) { + if (use_buffer_mode && !ebpf_module_has_buffer(ebpf_modules[i].name)) { i++; continue; } @@ -1925,15 +1979,15 @@ static void ebpf_run_netdata_tests(int rhf_version, uint32_t kver, int is_return char *first_incompatible = NULL; int unsupported_type = 0; size_t j; - uint32_t kernels_to_use = (arena_mode && ebpf_modules[i].arena_kernels) ? + uint32_t kernels_to_use = (use_arena_mode && ebpf_modules[i].arena_kernels) ? ebpf_modules[i].arena_kernels : - ((buffer_mode && ebpf_modules[i].buffer_kernels) ? + ((use_buffer_mode && ebpf_modules[i].buffer_kernels) ? ebpf_modules[i].buffer_kernels : ebpf_modules[i].kernels); uint32_t max_idx = ebpf_select_max_index(rhf_version, kver); uint32_t idx = ebpf_select_index(kernels_to_use, rhf_version, kver); ebpf_discover_candidates(&candidates, ebpf_modules[i].name, is_return, - kernels_to_use, max_idx, rhf_version); + kernels_to_use, max_idx, rhf_version, use_buffer_mode, use_arena_mode); for (j = 0; j < candidates.size; j++) { struct bpf_object *obj = bpf_object__open_file(candidates.files[j], NULL); if (libbpf_get_error(obj)) { @@ -1971,7 +2025,8 @@ NETDATA_LOG_THREAD_SAFE(" },\n \"Status\" : \"%s\"\n},\n", result); ebpf_write_map_compatibility_debug(unsupported_type, &map_support); NETDATA_LOG_THREAD_SAFE(" },\n \"Status\" : \"%s\"\n},\n", "Fail"); } else { - ebpf_mount_name(load, FILENAME_MAX - 1, idx, ebpf_modules[i].name, is_return, rhf_version); + ebpf_mount_name(load, FILENAME_MAX - 1, idx, ebpf_modules[i].name, is_return, rhf_version, + use_buffer_mode, use_arena_mode); ebpf_start_netdata_json(load, is_return); { char *result = ebpf_tester(load, ebpf_modules[i].update_names, flags & NETDATA_FLAG_CONTENT,