From: Pavel Rochnyack Date: Sun, 20 May 2018 15:51:49 +0000 (+0700) Subject: Merge branch 'collectd-5.8' X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=07ba05937aeaedd683656c3912040950dbf4a152;hp=b9b5e5d573d0011c4f3276e9b84b73ba4dd2e870;p=collectd.git Merge branch 'collectd-5.8' --- diff --git a/Makefile.am b/Makefile.am index d547a4f1..98b71ab4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -142,7 +142,8 @@ check_PROGRAMS = \ test_utils_subst \ test_utils_time \ test_utils_vl_lookup \ - test_libcollectd_network_parse + test_libcollectd_network_parse \ + test_utils_config_cores TESTS = $(check_PROGRAMS) @@ -327,6 +328,11 @@ test_utils_subst_SOURCES = \ src/daemon/utils_subst.h test_utils_subst_LDADD = libplugin_mock.la +test_utils_config_cores_SOURCES = \ + src/utils_config_cores_test.c \ + src/testing.h +test_utils_config_cores_LDADD = libplugin_mock.la + libavltree_la_SOURCES = \ src/daemon/utils_avltree.c \ src/daemon/utils_avltree.h @@ -915,7 +921,10 @@ endif if BUILD_PLUGIN_INTEL_PMU pkglib_LTLIBRARIES += intel_pmu.la -intel_pmu_la_SOURCES = src/intel_pmu.c +intel_pmu_la_SOURCES = \ + src/intel_pmu.c \ + src/utils_config_cores.h \ + src/utils_config_cores.c intel_pmu_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBJEVENTS_CPPFLAGS) intel_pmu_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBJEVENTS_LDFLAGS) intel_pmu_la_LIBADD = $(BUILD_WITH_LIBJEVENTS_LIBS) @@ -923,7 +932,10 @@ endif if BUILD_PLUGIN_INTEL_RDT pkglib_LTLIBRARIES += intel_rdt.la -intel_rdt_la_SOURCES = src/intel_rdt.c +intel_rdt_la_SOURCES = \ + src/intel_rdt.c \ + src/utils_config_cores.h \ + src/utils_config_cores.c intel_rdt_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBPQOS_CPPFLAGS) intel_rdt_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBPQOS_LDFLAGS) intel_rdt_la_LIBADD = $(BUILD_WITH_LIBPQOS_LIBS) diff --git a/src/collectd.conf.in b/src/collectd.conf.in index c1681f1e..96c26303 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -666,6 +666,7 @@ # ReportSoftwareEvents true # EventList "/var/cache/pmu/GenuineIntel-6-2D-core.json" # HardwareEvents "L2_RQSTS.CODE_RD_HIT,L2_RQSTS.CODE_RD_MISS" "L2_RQSTS.ALL_CODE_RD" +# Cores "[0-3]" # # diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index bbeddf21..17bc680f 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -3182,6 +3182,7 @@ B ReportSoftwareEvents true EventList "/var/cache/pmu/GenuineIntel-6-2D-core.json" HardwareEvents "L2_RQSTS.CODE_RD_HIT,L2_RQSTS.CODE_RD_MISS" "L2_RQSTS.ALL_CODE_RD" + Cores "0-3" "4,6" "[12-15]" B @@ -3253,6 +3254,23 @@ event_download.py script to download event list for current CPU. This field is a list of event names or groups of comma separated event names. This option requires B option to be configured. +=item B I + +All events are reported on a per core basis. Monitoring of the events can be +configured for a group of cores (aggregated statistics). This field defines +groups of cores on which to monitor supported events. The field is represented +as list of strings with core group values. Each string represents a list of +cores in a group. If a group is enclosed in square brackets each core is added +individually to a separate group (that is statistics are not aggregated). +Allowed formats are: + 0,1,2,3 + 0-10,20-18 + 1,3,5-8,10,0x10-12 + [4-15,32-63] + +If an empty string is provided as value for this field default cores +configuration is applied - that is separate group is created for each core. + =back =head2 Plugin C diff --git a/src/intel_pmu.c b/src/intel_pmu.c index 23536841..c9bbb50c 100644 --- a/src/intel_pmu.c +++ b/src/intel_pmu.c @@ -1,7 +1,7 @@ /** * collectd - src/intel_pmu.c * - * Copyright(c) 2017 Intel Corporation. All rights reserved. + * Copyright(c) 2017-2018 Intel Corporation. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,11 +23,14 @@ * * Authors: * Serhiy Pshyk + * Kamil Wiatrowski **/ #include "collectd.h" #include "common.h" +#include "utils_config_cores.h" + #include #include @@ -70,6 +73,7 @@ struct intel_pmu_ctx_s { char event_list_fn[PATH_MAX]; char **hw_events; size_t hw_events_count; + core_groups_list_t cores; struct eventlist *event_list; }; typedef struct intel_pmu_ctx_s intel_pmu_ctx_t; @@ -197,8 +201,61 @@ static void pmu_dump_config(void) { } } +static void pmu_dump_cgroups(void) { + + DEBUG(PMU_PLUGIN ": Core groups:"); + + for (size_t i = 0; i < g_ctx.cores.num_cgroups; i++) { + core_group_t *cgroup = g_ctx.cores.cgroups + i; + const size_t cores_size = cgroup->num_cores * 4 + 1; + char *cores = calloc(cores_size, sizeof(*cores)); + if (cores == NULL) { + DEBUG(PMU_PLUGIN ": Failed to allocate string to list cores."); + return; + } + for (size_t j = 0; j < cgroup->num_cores; j++) + if (snprintf(cores + strlen(cores), cores_size - strlen(cores), " %d", + cgroup->cores[j]) < 0) { + DEBUG(PMU_PLUGIN ": Failed to write list of cores to string."); + sfree(cores); + return; + } + + DEBUG(PMU_PLUGIN ": group[%" PRIsz "]", i); + DEBUG(PMU_PLUGIN ": description: %s", cgroup->desc); + DEBUG(PMU_PLUGIN ": cores count: %" PRIsz, cgroup->num_cores); + DEBUG(PMU_PLUGIN ": cores :%s", cores); + sfree(cores); + } +} + #endif /* COLLECT_DEBUG */ +static int pmu_validate_cgroups(core_group_t *cgroups, size_t len, + int max_cores) { + /* i - group index, j - core index */ + for (size_t i = 0; i < len; i++) { + for (size_t j = 0; j < cgroups[i].num_cores; j++) { + int core = (int)cgroups[i].cores[j]; + + /* Core index cannot exceed number of cores in system, + note that max_cores include both online and offline CPUs. */ + if (core >= max_cores) { + ERROR(PMU_PLUGIN ": Core %d is not valid, max core index: %d.", core, + max_cores - 1); + return -1; + } + } + /* Check if cores are set in remaining groups */ + for (size_t k = i + 1; k < len; k++) + if (config_cores_cmp_cgroups(&cgroups[i], &cgroups[k]) != 0) { + ERROR(PMU_PLUGIN ": Same cores cannot be set in different groups."); + return -1; + } + } + return 0; +} + static int pmu_config_hw_events(oconfig_item_t *ci) { if (strcasecmp("HardwareEvents", ci->key) != 0) { @@ -253,6 +310,8 @@ static int pmu_config(oconfig_item_t *ci) { ret = pmu_config_hw_events(child); } else if (strcasecmp("ReportSoftwareEvents", child->key) == 0) { ret = cf_util_get_boolean(child, &g_ctx.sw_events); + } else if (strcasecmp("Cores", child->key) == 0) { + ret = config_cores_parse(child, &g_ctx.cores); } else { ERROR(PMU_PLUGIN ": Unknown configuration parameter \"%s\".", child->key); ret = -1; @@ -271,20 +330,17 @@ static int pmu_config(oconfig_item_t *ci) { return 0; } -static void pmu_submit_counter(int cpu, char *event, counter_t value, - meta_data_t *meta) { +static void pmu_submit_counter(const char *cgroup, const char *event, + counter_t value, meta_data_t *meta) { value_list_t vl = VALUE_LIST_INIT; vl.values = &(value_t){.counter = value}; vl.values_len = 1; sstrncpy(vl.plugin, PMU_PLUGIN, sizeof(vl.plugin)); - if (cpu == -1) { - snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "all"); - } else { + sstrncpy(vl.plugin_instance, cgroup, sizeof(vl.plugin_instance)); + if (meta) vl.meta = meta; - snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%d", cpu); - } sstrncpy(vl.type, "counter", sizeof(vl.type)); sstrncpy(vl.type_instance, event, sizeof(vl.type_instance)); @@ -317,49 +373,65 @@ static void pmu_dispatch_data(void) { struct event *e; for (e = g_ctx.event_list->eventlist; e; e = e->next) { - uint64_t all_value = 0; - int event_enabled = 0; - for (int i = 0; i < g_ctx.event_list->num_cpus; i++) { - - if (e->efd[i].fd < 0) - continue; - - event_enabled++; - - /* If there are more events than counters, the kernel uses time - * multiplexing. With multiplexing, at the end of the run, - * the counter is scaled basing on total time enabled vs time running. - * final_count = raw_count * time_enabled/time_running - */ - uint64_t value = event_scaled_value(e, i); - all_value += value; - - /* get meta data with information about scaling */ - meta_data_t *meta = pmu_meta_data_create(&e->efd[i]); - - /* dispatch per CPU value */ - pmu_submit_counter(i, e->event, value, meta); - - meta_data_destroy(meta); - } + for (size_t i = 0; i < g_ctx.cores.num_cgroups; i++) { + core_group_t *cgroup = g_ctx.cores.cgroups + i; + uint64_t cgroup_value = 0; + int event_enabled_cgroup = 0; + meta_data_t *meta = NULL; + + for (size_t j = 0; j < cgroup->num_cores; j++) { + int core = (int)cgroup->cores[j]; + if (e->efd[core].fd < 0) + continue; + + event_enabled_cgroup++; + + /* If there are more events than counters, the kernel uses time + * multiplexing. With multiplexing, at the end of the run, + * the counter is scaled basing on total time enabled vs time running. + * final_count = raw_count * time_enabled/time_running + */ + uint64_t value = event_scaled_value(e, core); + cgroup_value += value; + + /* get meta data with information about scaling */ + if (cgroup->num_cores == 1) + meta = pmu_meta_data_create(&e->efd[core]); + } - if (event_enabled > 0) { - DEBUG(PMU_PLUGIN ": %-20s %'10lu", e->event, all_value); - /* dispatch all CPU value */ - pmu_submit_counter(-1, e->event, all_value, NULL); + if (event_enabled_cgroup > 0) { + DEBUG(PMU_PLUGIN ": %s/%s = %lu", e->event, cgroup->desc, cgroup_value); + /* dispatch per core group value */ + pmu_submit_counter(cgroup->desc, e->event, cgroup_value, meta); + meta_data_destroy(meta); + } } } } static int pmu_read(__attribute__((unused)) user_data_t *ud) { int ret; + struct event *e; DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__); - ret = read_all_events(g_ctx.event_list); - if (ret != 0) { - ERROR(PMU_PLUGIN ": Failed to read values of all events."); - return ret; + /* read all events only for configured cores */ + for (e = g_ctx.event_list->eventlist; e; e = e->next) { + for (size_t i = 0; i < g_ctx.cores.num_cgroups; i++) { + core_group_t *cgroup = g_ctx.cores.cgroups + i; + for (size_t j = 0; j < cgroup->num_cores; j++) { + int core = (int)cgroup->cores[j]; + if (e->efd[core].fd < 0) + continue; + + ret = read_event(e, core); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to read value of %s/%d event.", e->event, + core); + return ret; + } + } + } } pmu_dispatch_data(); @@ -404,7 +476,7 @@ static int pmu_add_hw_events(struct eventlist *el, char **e, size_t count) { if (!events) return -1; - char *s, *tmp; + char *s, *tmp = NULL; for (s = strtok_r(events, ",", &tmp); s; s = strtok_r(NULL, ",", &tmp)) { /* Allocate memory for event struct that contains array of efd structs @@ -460,6 +532,7 @@ static void pmu_free_events(struct eventlist *el) { while (e) { struct event *next = e->next; + sfree(e->event); sfree(e); e = next; } @@ -474,13 +547,18 @@ static int pmu_setup_events(struct eventlist *el, bool measure_all, for (e = el->eventlist; e; e = e->next) { - for (int i = 0; i < el->num_cpus; i++) { - if (setup_event(e, i, leader, measure_all, measure_pid) < 0) { - WARNING(PMU_PLUGIN ": perf event '%s' is not available (cpu=%d).", - e->event, i); - } else { - /* success if at least one event was set */ - ret = 0; + for (size_t i = 0; i < g_ctx.cores.num_cgroups; i++) { + core_group_t *cgroup = g_ctx.cores.cgroups + i; + for (size_t j = 0; j < cgroup->num_cores; j++) { + int core = (int)cgroup->cores[j]; + + if (setup_event(e, core, leader, measure_all, measure_pid) < 0) { + WARNING(PMU_PLUGIN ": perf event '%s' is not available (cpu=%d).", + e->event, core); + } else { + /* success if at least one event was set */ + ret = 0; + } } } @@ -504,6 +582,24 @@ static int pmu_init(void) { return -ENOMEM; } + if (g_ctx.cores.num_cgroups == 0) { + ret = config_cores_default(g_ctx.event_list->num_cpus, &g_ctx.cores); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Failed to set default core groups."); + goto init_error; + } + } else { + ret = pmu_validate_cgroups(g_ctx.cores.cgroups, g_ctx.cores.num_cgroups, + g_ctx.event_list->num_cpus); + if (ret != 0) { + ERROR(PMU_PLUGIN ": Invalid core groups configuration."); + goto init_error; + } + } +#if COLLECT_DEBUG + pmu_dump_cgroups(); +#endif + if (g_ctx.hw_cache_events) { ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_HW_CACHE, g_hw_cache_events, @@ -579,6 +675,8 @@ init_error: sfree(g_ctx.hw_events); g_ctx.hw_events_count = 0; + config_cores_cleanup(&g_ctx.cores); + return ret; } @@ -594,6 +692,8 @@ static int pmu_shutdown(void) { sfree(g_ctx.hw_events); g_ctx.hw_events_count = 0; + config_cores_cleanup(&g_ctx.cores); + return 0; } diff --git a/src/intel_rdt.c b/src/intel_rdt.c index 3d4c9889..684e2b6d 100644 --- a/src/intel_rdt.c +++ b/src/intel_rdt.c @@ -1,7 +1,7 @@ /** * collectd - src/intel_rdt.c * - * Copyright(c) 2016 Intel Corporation. All rights reserved. + * Copyright(c) 2016-2018 Intel Corporation. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,8 +25,9 @@ * Serhiy Pshyk **/ -#include "collectd.h" #include "common.h" +#include "utils_config_cores.h" +#include "collectd.h" #include @@ -41,16 +42,9 @@ typedef enum { CONFIGURATION_ERROR, } rdt_config_status; -struct rdt_core_group_s { - char *desc; - size_t num_cores; - unsigned *cores; - enum pqos_mon_event events; -}; -typedef struct rdt_core_group_s rdt_core_group_t; - struct rdt_ctx_s { - rdt_core_group_t cgroups[RDT_MAX_CORES]; + core_groups_list_t cores; + enum pqos_mon_event events[RDT_MAX_CORES]; struct pqos_mon_data *pgroups[RDT_MAX_CORES]; size_t num_groups; const struct pqos_cpuinfo *pqos_cpu; @@ -63,243 +57,6 @@ static rdt_ctx_t *g_rdt; static rdt_config_status g_state = UNKNOWN; -static int isdup(const uint64_t *nums, size_t size, uint64_t val) { - for (size_t i = 0; i < size; i++) - if (nums[i] == val) - return 1; - return 0; -} - -static int strtouint64(const char *s, uint64_t *n) { - char *endptr = NULL; - - assert(s != NULL); - assert(n != NULL); - - *n = strtoull(s, &endptr, 0); - - if (!(*s != '\0' && *endptr == '\0')) { - DEBUG(RDT_PLUGIN ": Error converting '%s' to unsigned number.", s); - return -EINVAL; - } - - return 0; -} - -/* - * NAME - * strlisttonums - * - * DESCRIPTION - * Converts string of characters representing list of numbers into array of - * numbers. Allowed formats are: - * 0,1,2,3 - * 0-10,20-18 - * 1,3,5-8,10,0x10-12 - * - * Numbers can be in decimal or hexadecimal format. - * - * PARAMETERS - * `s' String representing list of unsigned numbers. - * `nums' Array to put converted numeric values into. - * `max' Maximum number of elements that nums can accommodate. - * - * RETURN VALUE - * Number of elements placed into nums. - */ -static size_t strlisttonums(char *s, uint64_t *nums, size_t max) { - int ret; - size_t index = 0; - char *saveptr = NULL; - - if (s == NULL || nums == NULL || max == 0) - return index; - - for (;;) { - char *p = NULL; - char *token = NULL; - - token = strtok_r(s, ",", &saveptr); - if (token == NULL) - break; - - s = NULL; - - while (isspace(*token)) - token++; - if (*token == '\0') - continue; - - p = strchr(token, '-'); - if (p != NULL) { - uint64_t n, start, end; - *p = '\0'; - ret = strtouint64(token, &start); - if (ret < 0) - return 0; - ret = strtouint64(p + 1, &end); - if (ret < 0) - return 0; - if (start > end) { - return 0; - } - for (n = start; n <= end; n++) { - if (!(isdup(nums, index, n))) { - nums[index] = n; - index++; - } - if (index >= max) - return index; - } - } else { - uint64_t val; - - ret = strtouint64(token, &val); - if (ret < 0) - return 0; - - if (!(isdup(nums, index, val))) { - nums[index] = val; - index++; - } - if (index >= max) - return index; - } - } - - return index; -} - -/* - * NAME - * cgroup_cmp - * - * DESCRIPTION - * Function to compare cores in 2 core groups. - * - * PARAMETERS - * `cg_a' Pointer to core group a. - * `cg_b' Pointer to core group b. - * - * RETURN VALUE - * 1 if both groups contain the same cores - * 0 if none of their cores match - * -1 if some but not all cores match - */ -static int cgroup_cmp(const rdt_core_group_t *cg_a, - const rdt_core_group_t *cg_b) { - int found = 0; - - assert(cg_a != NULL); - assert(cg_b != NULL); - - const int sz_a = cg_a->num_cores; - const int sz_b = cg_b->num_cores; - const unsigned *tab_a = cg_a->cores; - const unsigned *tab_b = cg_b->cores; - - for (int i = 0; i < sz_a; i++) { - for (int j = 0; j < sz_b; j++) - if (tab_a[i] == tab_b[j]) - found++; - } - /* if no cores are the same */ - if (!found) - return 0; - /* if group contains same cores */ - if (sz_a == sz_b && sz_b == found) - return 1; - /* if not all cores are the same */ - return -1; -} - -static int cgroup_set(rdt_core_group_t *cg, char *desc, uint64_t *cores, - size_t num_cores) { - assert(cg != NULL); - assert(desc != NULL); - assert(cores != NULL); - assert(num_cores > 0); - - cg->cores = calloc(num_cores, sizeof(unsigned)); - if (cg->cores == NULL) { - ERROR(RDT_PLUGIN ": Error allocating core group table"); - return -ENOMEM; - } - cg->num_cores = num_cores; - cg->desc = strdup(desc); - if (cg->desc == NULL) { - ERROR(RDT_PLUGIN ": Error allocating core group description"); - sfree(cg->cores); - return -ENOMEM; - } - - for (size_t i = 0; i < num_cores; i++) - cg->cores[i] = (unsigned)cores[i]; - - return 0; -} - -/* - * NAME - * oconfig_to_cgroups - * - * DESCRIPTION - * Function to set the descriptions and cores for each core group. - * Takes a config option containing list of strings that are used to set - * core group values. - * - * PARAMETERS - * `item' Config option containing core groups. - * `groups' Table of core groups to set values in. - * `max_groups' Maximum number of core groups allowed. - * - * RETURN VALUE - * On success, the number of core groups set up. On error, appropriate - * negative error value. - */ -static int oconfig_to_cgroups(oconfig_item_t *item, rdt_core_group_t *groups, - size_t max_groups) { - int index = 0; - - assert(groups != NULL); - assert(max_groups > 0); - assert(item != NULL); - - for (int j = 0; j < item->values_num; j++) { - int ret; - size_t n; - uint64_t cores[RDT_MAX_CORES] = {0}; - char value[DATA_MAX_NAME_LEN]; - - if ((item->values[j].value.string == NULL) || - (strlen(item->values[j].value.string) == 0)) - continue; - - sstrncpy(value, item->values[j].value.string, sizeof(value)); - - n = strlisttonums(value, cores, STATIC_ARRAY_SIZE(cores)); - if (n == 0) { - ERROR(RDT_PLUGIN ": Error parsing core group (%s)", - item->values[j].value.string); - return -EINVAL; - } - - /* set core group info */ - ret = cgroup_set(&groups[index], item->values[j].value.string, cores, n); - if (ret < 0) - return ret; - - index++; - - if (index >= max_groups) { - WARNING(RDT_PLUGIN ": Too many core groups configured"); - return index; - } - } - - return index; -} - #if COLLECT_DEBUG static void rdt_dump_cgroups(void) { char cores[RDT_MAX_CORES * 4]; @@ -311,17 +68,18 @@ static void rdt_dump_cgroups(void) { DEBUG(RDT_PLUGIN ": groups count: %" PRIsz, g_rdt->num_groups); for (int i = 0; i < g_rdt->num_groups; i++) { + core_group_t *cgroup = g_rdt->cores.cgroups + i; memset(cores, 0, sizeof(cores)); - for (int j = 0; j < g_rdt->cgroups[i].num_cores; j++) { + for (int j = 0; j < cgroup->num_cores; j++) { snprintf(cores + strlen(cores), sizeof(cores) - strlen(cores) - 1, " %d", - g_rdt->cgroups[i].cores[j]); + cgroup->cores[j]); } DEBUG(RDT_PLUGIN ": group[%d]:", i); - DEBUG(RDT_PLUGIN ": description: %s", g_rdt->cgroups[i].desc); + DEBUG(RDT_PLUGIN ": description: %s", cgroup->desc); DEBUG(RDT_PLUGIN ": cores: %s", cores); - DEBUG(RDT_PLUGIN ": events: 0x%X", g_rdt->cgroups[i].events); + DEBUG(RDT_PLUGIN ": events: 0x%X", g_rdt->events[i]); } return; @@ -350,40 +108,54 @@ static void rdt_dump_data(void) { double mbr = bytes_to_mb(pv->mbm_remote_delta); double mbl = bytes_to_mb(pv->mbm_local_delta); - DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cgroups[i].desc, + DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cores.cgroups[i].desc, g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr); } } #endif /* COLLECT_DEBUG */ static void rdt_free_cgroups(void) { + config_cores_cleanup(&g_rdt->cores); for (int i = 0; i < RDT_MAX_CORES; i++) { - sfree(g_rdt->cgroups[i].desc); - - sfree(g_rdt->cgroups[i].cores); - g_rdt->cgroups[i].num_cores = 0; - sfree(g_rdt->pgroups[i]); } } static int rdt_default_cgroups(void) { - int ret; + unsigned num_cores = g_rdt->pqos_cpu->num_cores; + + g_rdt->cores.cgroups = calloc(num_cores, sizeof(*(g_rdt->cores.cgroups))); + if (g_rdt->cores.cgroups == NULL) { + ERROR(RDT_PLUGIN ": Error allocating core groups array"); + return -ENOMEM; + } + g_rdt->cores.num_cgroups = num_cores; /* configure each core in separate group */ - for (unsigned i = 0; i < g_rdt->pqos_cpu->num_cores; i++) { + for (unsigned i = 0; i < num_cores; i++) { + core_group_t *cgroup = g_rdt->cores.cgroups + i; char desc[DATA_MAX_NAME_LEN]; - uint64_t core = i; - - snprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore); /* set core group info */ - ret = cgroup_set(&g_rdt->cgroups[i], desc, &core, 1); - if (ret < 0) - return ret; + cgroup->cores = calloc(1, sizeof(*(cgroup->cores))); + if (cgroup->cores == NULL) { + ERROR(RDT_PLUGIN ": Error allocating cores array"); + rdt_free_cgroups(); + return -ENOMEM; + } + cgroup->num_cores = 1; + cgroup->cores[0] = i; + + snprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore); + cgroup->desc = strdup(desc); + if (cgroup->desc == NULL) { + ERROR(RDT_PLUGIN ": Error allocating core group description"); + rdt_free_cgroups(); + return -ENOMEM; + } } - return g_rdt->pqos_cpu->num_cores; + return num_cores; } static int rdt_is_core_id_valid(int core_id) { @@ -396,38 +168,23 @@ static int rdt_is_core_id_valid(int core_id) { } static int rdt_config_cgroups(oconfig_item_t *item) { - int n = 0; + size_t n = 0; enum pqos_mon_event events = 0; - if (item == NULL) { - DEBUG(RDT_PLUGIN ": cgroups_config: Invalid argument."); - return -EINVAL; - } - - DEBUG(RDT_PLUGIN ": Core groups [%d]:", item->values_num); - for (int j = 0; j < item->values_num; j++) { - if (item->values[j].type != OCONFIG_TYPE_STRING) { - ERROR(RDT_PLUGIN ": given core group value is not a string [idx=%d]", j); - return -EINVAL; - } - DEBUG(RDT_PLUGIN ": [%d]: %s", j, item->values[j].value.string); - } - - n = oconfig_to_cgroups(item, g_rdt->cgroups, g_rdt->pqos_cpu->num_cores); - if (n < 0) { + if (config_cores_parse(item, &g_rdt->cores) < 0) { rdt_free_cgroups(); ERROR(RDT_PLUGIN ": Error parsing core groups configuration."); return -EINVAL; } + n = g_rdt->cores.num_cgroups; /* validate configured core id values */ - for (int group_idx = 0; group_idx < n; group_idx++) { - for (int core_idx = 0; core_idx < g_rdt->cgroups[group_idx].num_cores; - core_idx++) { - if (!rdt_is_core_id_valid(g_rdt->cgroups[group_idx].cores[core_idx])) { + for (size_t group_idx = 0; group_idx < n; group_idx++) { + core_group_t *cgroup = g_rdt->cores.cgroups + group_idx; + for (size_t core_idx = 0; core_idx < cgroup->num_cores; core_idx++) { + if (!rdt_is_core_id_valid((int)cgroup->cores[core_idx])) { ERROR(RDT_PLUGIN ": Core group '%s' contains invalid core id '%d'", - g_rdt->cgroups[group_idx].desc, - (int)g_rdt->cgroups[group_idx].cores[core_idx]); + cgroup->desc, (int)cgroup->cores[core_idx]); rdt_free_cgroups(); return -EINVAL; } @@ -436,12 +193,13 @@ static int rdt_config_cgroups(oconfig_item_t *item) { if (n == 0) { /* create default core groups if "Cores" config option is empty */ - n = rdt_default_cgroups(); - if (n < 0) { + int ret = rdt_default_cgroups(); + if (ret < 0) { rdt_free_cgroups(); ERROR(RDT_PLUGIN ": Error creating default core groups configuration."); - return n; + return ret; } + n = (size_t)ret; INFO(RDT_PLUGIN ": No core groups configured. Default core groups created."); } @@ -457,10 +215,11 @@ static int rdt_config_cgroups(oconfig_item_t *item) { DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events); g_rdt->num_groups = n; - for (int i = 0; i < n; i++) { - for (int j = 0; j < i; j++) { + for (size_t i = 0; i < n; i++) { + for (size_t j = 0; j < i; j++) { int found = 0; - found = cgroup_cmp(&g_rdt->cgroups[j], &g_rdt->cgroups[i]); + found = config_cores_cmp_cgroups(&g_rdt->cores.cgroups[j], + &g_rdt->cores.cgroups[i]); if (found != 0) { rdt_free_cgroups(); ERROR(RDT_PLUGIN ": Cannot monitor same cores in different groups."); @@ -468,7 +227,7 @@ static int rdt_config_cgroups(oconfig_item_t *item) { } } - g_rdt->cgroups[i].events = events; + g_rdt->events[i] = events; g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i])); if (g_rdt->pgroups[i] == NULL) { rdt_free_cgroups(); @@ -628,6 +387,8 @@ static int rdt_read(__attribute__((unused)) user_data_t *ud) { #endif /* COLLECT_DEBUG */ for (int i = 0; i < g_rdt->num_groups; i++) { + core_group_t *cgroup = g_rdt->cores.cgroups + i; + enum pqos_mon_event mbm_events = (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW | PQOS_MON_EVENT_RMEM_BW); @@ -636,16 +397,16 @@ static int rdt_read(__attribute__((unused)) user_data_t *ud) { /* Submit only monitored events data */ - if (g_rdt->cgroups[i].events & PQOS_MON_EVENT_L3_OCCUP) - rdt_submit_gauge(g_rdt->cgroups[i].desc, "bytes", "llc", pv->llc); + if (g_rdt->events[i] & PQOS_MON_EVENT_L3_OCCUP) + rdt_submit_gauge(cgroup->desc, "bytes", "llc", pv->llc); - if (g_rdt->cgroups[i].events & PQOS_PERF_EVENT_IPC) - rdt_submit_gauge(g_rdt->cgroups[i].desc, "ipc", NULL, pv->ipc); + if (g_rdt->events[i] & PQOS_PERF_EVENT_IPC) + rdt_submit_gauge(cgroup->desc, "ipc", NULL, pv->ipc); - if (g_rdt->cgroups[i].events & mbm_events) { - rdt_submit_derive(g_rdt->cgroups[i].desc, "memory_bandwidth", "local", + if (g_rdt->events[i] & mbm_events) { + rdt_submit_derive(cgroup->desc, "memory_bandwidth", "local", pv->mbm_local_delta); - rdt_submit_derive(g_rdt->cgroups[i].desc, "memory_bandwidth", "remote", + rdt_submit_derive(cgroup->desc, "memory_bandwidth", "remote", pv->mbm_remote_delta); } } @@ -665,10 +426,10 @@ static int rdt_init(void) { /* Start monitoring */ for (int i = 0; i < g_rdt->num_groups; i++) { - rdt_core_group_t *cg = &g_rdt->cgroups[i]; + core_group_t *cg = g_rdt->cores.cgroups + i; - ret = pqos_mon_start(cg->num_cores, cg->cores, cg->events, (void *)cg->desc, - g_rdt->pgroups[i]); + ret = pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i], + (void *)cg->desc, g_rdt->pgroups[i]); if (ret != PQOS_RETVAL_OK) ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)", diff --git a/src/utils_config_cores.c b/src/utils_config_cores.c new file mode 100644 index 00000000..085e8abe --- /dev/null +++ b/src/utils_config_cores.c @@ -0,0 +1,372 @@ +/** + * collectd - src/utils_config_cores.c + * + * Copyright(c) 2018 Intel Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Kamil Wiatrowski + **/ + +#include "collectd.h" + +#include "common.h" + +#include "utils_config_cores.h" + +#define UTIL_NAME "utils_config_cores" + +#define MAX_SOCKETS 8 +#define MAX_SOCKET_CORES 64 +#define MAX_CORES (MAX_SOCKET_CORES * MAX_SOCKETS) + +static inline _Bool is_in_list(unsigned val, const unsigned *list, size_t len) { + for (size_t i = 0; i < len; i++) + if (list[i] == val) + return 1; + return 0; +} + +static int str_to_uint(const char *s, unsigned *n) { + if (s == NULL || n == NULL) + return -EINVAL; + char *endptr = NULL; + + *n = (unsigned)strtoul(s, &endptr, 0); + if (*s == '\0' || *endptr != '\0') { + ERROR(UTIL_NAME ": Failed to parse '%s' into unsigned number", s); + return -EINVAL; + } + + return 0; +} + +/* + * NAME + * str_list_to_nums + * + * DESCRIPTION + * Converts string of characters representing list of numbers into array of + * numbers. Allowed formats are: + * 0,1,2,3 + * 0-10,20-18 + * 1,3,5-8,10,0x10-12 + * + * Numbers can be in decimal or hexadecimal format. + * + * PARAMETERS + * `s' String representing list of unsigned numbers. + * `nums' Array to put converted numeric values into. + * `nums_len' Maximum number of elements that nums can accommodate. + * + * RETURN VALUE + * Number of elements placed into nums. + */ +static size_t str_list_to_nums(char *s, unsigned *nums, size_t nums_len) { + char *saveptr = NULL; + char *token; + size_t idx = 0; + + while ((token = strtok_r(s, ",", &saveptr))) { + char *pos; + unsigned start, end = 0; + s = NULL; + + while (isspace(*token)) + token++; + if (*token == '\0') + continue; + + pos = strchr(token, '-'); + if (pos) { + *pos = '\0'; + } + + if (str_to_uint(token, &start)) + return 0; + + if (pos) { + if (str_to_uint(pos + 1, &end)) + return 0; + } else { + end = start; + } + + if (start > end) { + unsigned swap = start; + start = end; + end = swap; + } + + for (unsigned i = start; i <= end; i++) { + if (is_in_list(i, nums, idx)) + continue; + if (idx >= nums_len) { + WARNING(UTIL_NAME ": exceeded the cores number limit: %" PRIsz, + nums_len); + return idx; + } + nums[idx] = i; + idx++; + } + } + return idx; +} + +/* + * NAME + * check_core_grouping + * + * DESCRIPTION + * Look for [...] brackets in *in string and if found copy the + * part between brackets into *out string and set grouped to 0. + * Otherwise grouped is set to 1 and input is copied without leading + * whitespaces. + * + * PARAMETERS + * `out' Output string to store result. + * `in' Input string to be parsed and copied. + * `out_size' Maximum number of elements that out can accommodate. + * `grouped' Set by function depending if cores should be grouped or not. + * + * RETURN VALUE + * Zero upon success or non-zero if an error occurred. + */ +static int check_core_grouping(char *out, const char *in, size_t out_size, + _Bool *grouped) { + const char *start = in; + char *end; + while (isspace(*start)) + ++start; + if (start[0] == '[') { + *grouped = 0; + ++start; + end = strchr(start, ']'); + if (end == NULL) { + ERROR(UTIL_NAME ": Missing closing bracket ] in option %s.", in); + return -EINVAL; + } + if ((end - start) >= out_size) { + ERROR(UTIL_NAME ": Out buffer is too small."); + return -EINVAL; + } + sstrncpy(out, start, end - start + 1); + DEBUG(UTIL_NAME ": Mask for individual (not aggregated) cores: %s", out); + } else { + *grouped = 1; + sstrncpy(out, start, out_size); + } + return 0; +} + +int config_cores_parse(const oconfig_item_t *ci, core_groups_list_t *cgl) { + if (ci == NULL || cgl == NULL) + return -EINVAL; + if (ci->values_num == 0 || ci->values_num > MAX_CORES) + return -EINVAL; + core_group_t cgroups[MAX_CORES] = {{0}}; + size_t cg_idx = 0; /* index for cgroups array */ + int ret = 0; + + for (int i = 0; i < ci->values_num; i++) { + if (ci->values[i].type != OCONFIG_TYPE_STRING) { + WARNING(UTIL_NAME ": The %s option requires string arguments.", ci->key); + return -EINVAL; + } + } + + if (ci->values_num == 1 && ci->values[0].value.string && + strlen(ci->values[0].value.string) == 0) + return 0; + + for (int i = 0; i < ci->values_num; i++) { + size_t n; + _Bool grouped = 1; + char str[DATA_MAX_NAME_LEN]; + unsigned cores[MAX_CORES] = {0}; + + if (cg_idx >= STATIC_ARRAY_SIZE(cgroups)) { + ERROR(UTIL_NAME + ": Configuration exceeds maximum number of cores: %" PRIsz, + STATIC_ARRAY_SIZE(cgroups)); + ret = -EINVAL; + goto parse_error; + } + if ((ci->values[i].value.string == NULL) || + (strlen(ci->values[i].value.string) == 0)) { + ERROR(UTIL_NAME ": Failed to parse parameters for %s option.", ci->key); + ret = -EINVAL; + goto parse_error; + } + + ret = check_core_grouping(str, ci->values[i].value.string, sizeof(str), + &grouped); + if (ret != 0) { + ERROR(UTIL_NAME ": Failed to parse config option [%d] %s.", i, + ci->values[i].value.string); + goto parse_error; + } + n = str_list_to_nums(str, cores, STATIC_ARRAY_SIZE(cores)); + if (n == 0) { + ERROR(UTIL_NAME ": Failed to parse config option [%d] %s.", i, + ci->values[i].value.string); + ret = -EINVAL; + goto parse_error; + } + + if (grouped) { + cgroups[cg_idx].desc = strdup(ci->values[i].value.string); + if (cgroups[cg_idx].desc == NULL) { + ERROR(UTIL_NAME ": Failed to allocate description."); + ret = -ENOMEM; + goto parse_error; + } + + cgroups[cg_idx].cores = calloc(n, sizeof(*cgroups[cg_idx].cores)); + if (cgroups[cg_idx].cores == NULL) { + ERROR(UTIL_NAME ": Failed to allocate cores for cgroup."); + ret = -ENOMEM; + goto parse_error; + } + + for (size_t j = 0; j < n; j++) + cgroups[cg_idx].cores[j] = cores[j]; + + cgroups[cg_idx].num_cores = n; + cg_idx++; + } else { + for (size_t j = 0; j < n && cg_idx < STATIC_ARRAY_SIZE(cgroups); j++) { + char desc[DATA_MAX_NAME_LEN]; + snprintf(desc, sizeof(desc), "%u", cores[j]); + + cgroups[cg_idx].desc = strdup(desc); + if (cgroups[cg_idx].desc == NULL) { + ERROR(UTIL_NAME ": Failed to allocate desc for core %u.", cores[j]); + ret = -ENOMEM; + goto parse_error; + } + + cgroups[cg_idx].cores = calloc(1, sizeof(*(cgroups[cg_idx].cores))); + if (cgroups[cg_idx].cores == NULL) { + ERROR(UTIL_NAME ": Failed to allocate cgroup for core %u.", cores[j]); + ret = -ENOMEM; + goto parse_error; + } + cgroups[cg_idx].num_cores = 1; + cgroups[cg_idx].cores[0] = cores[j]; + cg_idx++; + } + } + } + + cgl->cgroups = calloc(cg_idx, sizeof(*cgl->cgroups)); + if (cgl->cgroups == NULL) { + ERROR(UTIL_NAME ": Failed to allocate core groups."); + ret = -ENOMEM; + goto parse_error; + } + + cgl->num_cgroups = cg_idx; + for (size_t i = 0; i < cg_idx; i++) + cgl->cgroups[i] = cgroups[i]; + + return 0; + +parse_error: + + cg_idx = 0; + while (cg_idx < STATIC_ARRAY_SIZE(cgroups) && cgroups[cg_idx].desc != NULL) { + sfree(cgroups[cg_idx].desc); + sfree(cgroups[cg_idx].cores); + cg_idx++; + } + return ret; +} + +int config_cores_default(int num_cores, core_groups_list_t *cgl) { + if (cgl == NULL || num_cores < 0 || num_cores > MAX_CORES) + return -EINVAL; + + cgl->cgroups = calloc(num_cores, sizeof(*(cgl->cgroups))); + if (cgl->cgroups == NULL) { + ERROR(UTIL_NAME ": Failed to allocate memory for core groups."); + return -ENOMEM; + } + cgl->num_cgroups = num_cores; + + for (int i = 0; i < num_cores; i++) { + char desc[DATA_MAX_NAME_LEN]; + snprintf(desc, sizeof(desc), "%d", i); + + cgl->cgroups[i].cores = calloc(1, sizeof(*(cgl->cgroups[i].cores))); + if (cgl->cgroups[i].cores == NULL) { + ERROR(UTIL_NAME ": Failed to allocate default cores for cgroup %d.", i); + config_cores_cleanup(cgl); + return -ENOMEM; + } + cgl->cgroups[i].num_cores = 1; + cgl->cgroups[i].cores[0] = i; + + cgl->cgroups[i].desc = strdup(desc); + if (cgl->cgroups[i].desc == NULL) { + ERROR(UTIL_NAME ": Failed to allocate description for cgroup %d.", i); + config_cores_cleanup(cgl); + return -ENOMEM; + } + } + return 0; +} + +void config_cores_cleanup(core_groups_list_t *cgl) { + if (cgl == NULL) + return; + for (size_t i = 0; i < cgl->num_cgroups; i++) { + sfree(cgl->cgroups[i].desc); + sfree(cgl->cgroups[i].cores); + } + sfree(cgl->cgroups); + cgl->num_cgroups = 0; +} + +int config_cores_cmp_cgroups(const core_group_t *cg_a, + const core_group_t *cg_b) { + size_t found = 0; + + assert(cg_a != NULL); + assert(cg_b != NULL); + + const size_t sz_a = cg_a->num_cores; + const size_t sz_b = cg_b->num_cores; + const unsigned *tab_a = cg_a->cores; + const unsigned *tab_b = cg_b->cores; + + for (size_t i = 0; i < sz_a; i++) + if (is_in_list(tab_a[i], tab_b, sz_b)) + found++; + + /* if no cores are the same */ + if (!found) + return 0; + /* if group contains same cores */ + if (sz_a == sz_b && sz_b == found) + return 1; + /* if not all cores are the same */ + return -1; +} diff --git a/src/utils_config_cores.h b/src/utils_config_cores.h new file mode 100644 index 00000000..e22cbcfe --- /dev/null +++ b/src/utils_config_cores.h @@ -0,0 +1,136 @@ +/** + * collectd - src/utils_config_cores.h + * + * Copyright(c) 2018 Intel Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Kamil Wiatrowski + **/ + +#ifndef UTILS_CONFIG_CORES_H +#define UTILS_CONFIG_CORES_H 1 + +#include "configfile.h" + +#ifndef PRIsz +#define PRIsz "zu" +#endif /* PRIsz */ + +struct core_group_s { + char *desc; + unsigned *cores; + size_t num_cores; +}; +typedef struct core_group_s core_group_t; + +struct core_groups_list_s { + core_group_t *cgroups; + size_t num_cgroups; +}; +typedef struct core_groups_list_s core_groups_list_t; + +/* + * NAME + * config_cores_parse + * + * DESCRIPTION + * Convert strings from config item into list of core groups. + * + * PARAMETERS + * `ci' Pointer to config item. + * `cgl' Pointer to core groups list to be filled. + * + * RETURN VALUE + * Zero upon success or non-zero if an error occurred. + * + * NOTES + * In case of an error, *cgl is not modified. + * Numbers can be in decimal or hexadecimal format. + * The memory allocated for *cgroups in list needs to be freed + * with config_cores_cleanup. + * + * EXAMPLES + * If config is "0-3" "[4-15]" it means that cores 0-3 are aggregated + * into one group and cores 4 to 15 are stored individualily in + * separate groups. Examples of allowed formats: + * "0,3,4" "10-15" - cores collected into two groups + * "0" "0x3" "7" - 3 cores, each in individual group + * "[32-63]" - 32 cores, each in individual group + * + * For empty string "" *cgl is not modified and zero is returned. + */ +int config_cores_parse(const oconfig_item_t *ci, core_groups_list_t *cgl); + +/* + * NAME + * config_cores_default + * + * DESCRIPTION + * Set number of cores starting from zero into individual + * core groups in *cgl list. + * + * PARAMETERS + * `num_cores' Number of cores to be configured. + * `cgl' Pointer to core groups list. + * + * RETURN VALUE + * Zero upon success or non-zero if an error occurred. + * + * NOTES + * The memory allocated for *cgroups in list needs to be freed + * with config_cores_cleanup. In case of error the memory is + * freed by the function itself. + */ +int config_cores_default(int num_cores, core_groups_list_t *cgl); + +/* + * NAME + * config_cores_cleanup + * + * DESCRIPTION + * Free the memory allocated for cgroups and set + * num_cgroups to zero. + * + * PARAMETERS + * `cgl' Pointer to core groups list. + */ +void config_cores_cleanup(core_groups_list_t *cgl); + +/* + * NAME + * config_cores_cmp_cgroups + * + * DESCRIPTION + * Function to compare cores in 2 core groups. + * + * PARAMETERS + * `cg_a' Pointer to core group a. + * `cg_b' Pointer to core group b. + * + * RETURN VALUE + * 1 if both groups contain the same cores + * 0 if none of their cores match + * -1 if some but not all cores match + */ +int config_cores_cmp_cgroups(const core_group_t *cg_a, + const core_group_t *cg_b); + +#endif /* UTILS_CONFIG_CORES_H */ diff --git a/src/utils_config_cores_test.c b/src/utils_config_cores_test.c new file mode 100644 index 00000000..4809bd30 --- /dev/null +++ b/src/utils_config_cores_test.c @@ -0,0 +1,247 @@ +/** + * collectd - src/utils_config_cores_test.c + * + * Copyright(c) 2018 Intel Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Kamil Wiatrowski + **/ + +#include "testing.h" +#include "utils_config_cores.c" /* sic */ + +oconfig_value_t test_cfg_values[] = {{{"0"}, OCONFIG_TYPE_STRING}, + {{"1-2"}, OCONFIG_TYPE_STRING}, + {{"[3-4]"}, OCONFIG_TYPE_STRING}}; + +oconfig_item_t test_cfg = { + "Cores", test_cfg_values, STATIC_ARRAY_SIZE(test_cfg_values), NULL, NULL, + 0}; + +static int compare_with_test_config(core_groups_list_t *cgl) { + if (cgl->num_cgroups == 4 && cgl->cgroups[0].num_cores == 1 && + strcmp("0", cgl->cgroups[0].desc) == 0 && cgl->cgroups[0].cores[0] == 0 && + cgl->cgroups[1].num_cores == 2 && + strcmp("1-2", cgl->cgroups[1].desc) == 0 && + cgl->cgroups[1].cores[0] == 1 && cgl->cgroups[1].cores[1] == 2 && + cgl->cgroups[2].num_cores == 1 && + strcmp("3", cgl->cgroups[2].desc) == 0 && cgl->cgroups[2].cores[0] == 3 && + cgl->cgroups[3].num_cores == 1 && + strcmp("4", cgl->cgroups[3].desc) == 0 && cgl->cgroups[3].cores[0] == 4) + return 0; + + return -1; +} + +DEF_TEST(string_to_uint) { + int ret = 0; + char *s = "13", *s1 = "0xd", *s2 = "g"; + unsigned n = 0; + + ret = str_to_uint(s, &n); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(13, n); + + ret = str_to_uint(s1, &n); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(13, n); + + ret = str_to_uint(s2, &n); + OK(ret < 0); + + ret = str_to_uint(NULL, &n); + OK(ret < 0); + return 0; +} + +DEF_TEST(cores_list_to_numbers) { + size_t n = 0; + unsigned nums[MAX_CORES]; + char str[64] = ""; + + n = str_list_to_nums(str, nums, STATIC_ARRAY_SIZE(nums)); + EXPECT_EQ_INT(0, n); + + strncpy(str, "1", STATIC_ARRAY_SIZE(str)); + n = str_list_to_nums(str, nums, STATIC_ARRAY_SIZE(nums)); + EXPECT_EQ_INT(1, n); + EXPECT_EQ_INT(1, nums[0]); + + strncpy(str, "0,2-3", STATIC_ARRAY_SIZE(str)); + n = str_list_to_nums(str, nums, STATIC_ARRAY_SIZE(nums)); + EXPECT_EQ_INT(3, n); + EXPECT_EQ_INT(0, nums[0]); + EXPECT_EQ_INT(2, nums[1]); + EXPECT_EQ_INT(3, nums[2]); + + strncpy(str, "11-0xa", STATIC_ARRAY_SIZE(str)); + n = str_list_to_nums(str, nums, STATIC_ARRAY_SIZE(nums)); + EXPECT_EQ_INT(2, n); + EXPECT_EQ_INT(10, nums[0]); + EXPECT_EQ_INT(11, nums[1]); + + snprintf(str, sizeof(str), "0-%d", (MAX_CORES - 1)); + n = str_list_to_nums(str, nums, STATIC_ARRAY_SIZE(nums)); + EXPECT_EQ_INT(MAX_CORES, n); + EXPECT_EQ_INT(0, nums[0]); + EXPECT_EQ_INT(MAX_CORES - 1, nums[MAX_CORES - 1]); + + /* Should return 0 for incorrect syntax. */ + strncpy(str, "5g", STATIC_ARRAY_SIZE(str)); + n = str_list_to_nums(str, nums, STATIC_ARRAY_SIZE(nums)); + EXPECT_EQ_INT(0, n); + return 0; +} + +DEF_TEST(check_grouped_cores) { + int ret = 0; + _Bool grouped; + char src[64] = "[5-15]"; + char dest[64]; + + ret = check_core_grouping(dest, src, sizeof(dest), &grouped); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(0, grouped); + EXPECT_EQ_STR("5-15", dest); + + strncpy(src, " 5-15", STATIC_ARRAY_SIZE(src)); + ret = check_core_grouping(dest, src, sizeof(dest), &grouped); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(1, grouped); + EXPECT_EQ_STR("5-15", dest); + return 0; +} + +DEF_TEST(cores_option_parse) { + int ret = 0; + core_groups_list_t cgl = {0}; + + ret = config_cores_parse(&test_cfg, &cgl); + EXPECT_EQ_INT(0, ret); + CHECK_NOT_NULL(cgl.cgroups); + EXPECT_EQ_INT(0, compare_with_test_config(&cgl)); + + config_cores_cleanup(&cgl); + return 0; +} + +DEF_TEST(cores_option_parse_fail) { + int ret = 0; + core_groups_list_t cgl = {0}; + /* Wrong value, missing closing bracket ] */ + oconfig_value_t values = {{"[0-15"}, OCONFIG_TYPE_STRING}; + oconfig_item_t cfg = {"Cores", &values, 1, NULL, NULL, 0}; + + ret = config_cores_parse(&cfg, &cgl); + EXPECT_EQ_INT(-EINVAL, ret); + EXPECT_EQ_INT(0, cgl.num_cgroups); + OK(NULL == cgl.cgroups); + return 0; +} + +DEF_TEST(cores_default_list) { + int ret = 0; + core_groups_list_t cgl = {0}; + + ret = config_cores_default(2, &cgl); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(2, cgl.num_cgroups); + CHECK_NOT_NULL(cgl.cgroups); + + CHECK_NOT_NULL(cgl.cgroups[0].cores); + CHECK_NOT_NULL(cgl.cgroups[0].desc); + EXPECT_EQ_STR("0", cgl.cgroups[0].desc); + EXPECT_EQ_INT(1, cgl.cgroups[0].num_cores); + EXPECT_EQ_INT(0, cgl.cgroups[0].cores[0]); + + CHECK_NOT_NULL(cgl.cgroups[1].cores); + CHECK_NOT_NULL(cgl.cgroups[1].desc); + EXPECT_EQ_STR("1", cgl.cgroups[1].desc); + EXPECT_EQ_INT(1, cgl.cgroups[1].num_cores); + EXPECT_EQ_INT(1, cgl.cgroups[1].cores[0]); + + config_cores_cleanup(&cgl); + return 0; +} + +DEF_TEST(cores_default_list_fail) { + int ret = 0; + core_groups_list_t cgl = {0}; + + ret = config_cores_default(-1, &cgl); + OK(ret < 0); + ret = config_cores_default(MAX_CORES + 1, &cgl); + OK(ret < 0); + ret = config_cores_default(1, NULL); + OK(ret < 0); + return 0; +} + +DEF_TEST(cores_group_cleanup) { + core_groups_list_t cgl; + cgl.cgroups = calloc(1, sizeof(*cgl.cgroups)); + CHECK_NOT_NULL(cgl.cgroups); + cgl.num_cgroups = 1; + cgl.cgroups[0].desc = strdup("1"); + cgl.cgroups[0].cores = calloc(1, sizeof(*cgl.cgroups[0].cores)); + CHECK_NOT_NULL(cgl.cgroups[0].cores); + cgl.cgroups[0].cores[0] = 1; + cgl.cgroups[0].num_cores = 1; + + config_cores_cleanup(&cgl); + OK(NULL == cgl.cgroups); + EXPECT_EQ_INT(0, cgl.num_cgroups); + return 0; +} + +DEF_TEST(cores_group_cmp) { + unsigned cores_mock[] = {0, 1, 2}; + core_group_t group_mock = {"0,1,2", cores_mock, 3}; + unsigned cores_mock_2[] = {2, 3}; + core_group_t group_mock_2 = {"2,3", cores_mock_2, 2}; + + int ret = config_cores_cmp_cgroups(&group_mock, &group_mock); + EXPECT_EQ_INT(1, ret); + + ret = config_cores_cmp_cgroups(&group_mock, &group_mock_2); + EXPECT_EQ_INT(-1, ret); + + cores_mock_2[0] = 4; + ret = config_cores_cmp_cgroups(&group_mock, &group_mock_2); + EXPECT_EQ_INT(0, ret); + return 0; +} + +int main(void) { + RUN_TEST(string_to_uint); + RUN_TEST(cores_list_to_numbers); + RUN_TEST(check_grouped_cores); + + RUN_TEST(cores_group_cleanup); + RUN_TEST(cores_option_parse); + RUN_TEST(cores_option_parse_fail); + RUN_TEST(cores_default_list); + RUN_TEST(cores_default_list_fail); + + RUN_TEST(cores_group_cmp); + + END_TEST; +}