Merge branch 'collectd-5.8'
authorPavel Rochnyack <pavel2000@ngs.ru>
Sun, 20 May 2018 15:51:49 +0000 (22:51 +0700)
committerPavel Rochnyack <pavel2000@ngs.ru>
Sun, 20 May 2018 15:51:49 +0000 (22:51 +0700)
Makefile.am
src/collectd.conf.in
src/collectd.conf.pod
src/intel_pmu.c
src/intel_rdt.c
src/utils_config_cores.c [new file with mode: 0644]
src/utils_config_cores.h [new file with mode: 0644]
src/utils_config_cores_test.c [new file with mode: 0644]

index d547a4f..98b71ab 100644 (file)
@@ -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)
index c1681f1..96c2630 100644 (file)
 #    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]"
 #</Plugin>
 
 #<Plugin "intel_rdt">
index bbeddf2..17bc680 100644 (file)
@@ -3182,6 +3182,7 @@ B<Synopsis:>
     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]"
   </Plugin>
 
 B<Options:>
@@ -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<EventList> option to be configured.
 
+=item B<Cores> I<cores groups>
+
+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<intel_rdt>
index 2353684..c9bbb50 100644 (file)
@@ -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
  *
  * Authors:
  *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
+ *   Kamil Wiatrowski <kamilx.wiatrowski@intel.com>
  **/
 
 #include "collectd.h"
 #include "common.h"
 
+#include "utils_config_cores.h"
+
 #include <jevents.h>
 #include <jsession.h>
 
@@ -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;
 }
 
index 3d4c988..684e2b6 100644 (file)
@@ -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 <serhiyx.pshyk@intel.com>
  **/
 
-#include "collectd.h"
 #include "common.h"
+#include "utils_config_cores.h"
+#include "collectd.h"
 
 #include <pqos.h>
 
@@ -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 (file)
index 0000000..085e8ab
--- /dev/null
@@ -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 <kamilx.wiatrowski@intel.com>
+ **/
+
+#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 (file)
index 0000000..e22cbcf
--- /dev/null
@@ -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 <kamilx.wiatrowski@intel.com>
+ **/
+
+#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 (file)
index 0000000..4809bd3
--- /dev/null
@@ -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 <kamilx.wiatrowski@intel.com>
+ **/
+
+#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;
+}