intel_pmu plugin: implement core groups option
[collectd.git] / src / utils_config_cores.c
diff --git a/src/utils_config_cores.c b/src/utils_config_cores.c
new file mode 100644 (file)
index 0000000..1daa76d
--- /dev/null
@@ -0,0 +1,371 @@
+/**
+ * collectd - src/utils_config_cores.c
+ *
+ * Copyright(c) 2017 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;
+  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: %zu", 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: %zu",
+            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;
+}
+