Merge branch 'collectd-5.7' into collectd-5.8
[collectd.git] / src / utils_config_cores.c
1 /**
2  * collectd - src/utils_config_cores.c
3  *
4  * Copyright(c) 2018 Intel Corporation. All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  *
24  * Authors:
25  *   Kamil Wiatrowski <kamilx.wiatrowski@intel.com>
26  **/
27
28 #include "collectd.h"
29
30 #include "common.h"
31
32 #include "utils_config_cores.h"
33
34 #define UTIL_NAME "utils_config_cores"
35
36 #define MAX_SOCKETS 8
37 #define MAX_SOCKET_CORES 64
38 #define MAX_CORES (MAX_SOCKET_CORES * MAX_SOCKETS)
39
40 static inline _Bool is_in_list(unsigned val, const unsigned *list, size_t len) {
41   for (size_t i = 0; i < len; i++)
42     if (list[i] == val)
43       return 1;
44   return 0;
45 }
46
47 static int str_to_uint(const char *s, unsigned *n) {
48   if (s == NULL || n == NULL)
49     return -EINVAL;
50   char *endptr = NULL;
51
52   *n = (unsigned)strtoul(s, &endptr, 0);
53   if (*s == '\0' || *endptr != '\0') {
54     ERROR(UTIL_NAME ": Failed to parse '%s' into unsigned number", s);
55     return -EINVAL;
56   }
57
58   return 0;
59 }
60
61 /*
62  * NAME
63  *   str_list_to_nums
64  *
65  * DESCRIPTION
66  *   Converts string of characters representing list of numbers into array of
67  *   numbers. Allowed formats are:
68  *     0,1,2,3
69  *     0-10,20-18
70  *     1,3,5-8,10,0x10-12
71  *
72  *   Numbers can be in decimal or hexadecimal format.
73  *
74  * PARAMETERS
75  *   `s'         String representing list of unsigned numbers.
76  *   `nums'      Array to put converted numeric values into.
77  *   `nums_len'  Maximum number of elements that nums can accommodate.
78  *
79  * RETURN VALUE
80  *    Number of elements placed into nums.
81  */
82 static size_t str_list_to_nums(char *s, unsigned *nums, size_t nums_len) {
83   char *saveptr = NULL;
84   char *token;
85   size_t idx = 0;
86
87   while ((token = strtok_r(s, ",", &saveptr))) {
88     char *pos;
89     unsigned start, end = 0;
90     s = NULL;
91
92     while (isspace(*token))
93       token++;
94     if (*token == '\0')
95       continue;
96
97     pos = strchr(token, '-');
98     if (pos) {
99       *pos = '\0';
100     }
101
102     if (str_to_uint(token, &start))
103       return 0;
104
105     if (pos) {
106       if (str_to_uint(pos + 1, &end))
107         return 0;
108     } else {
109       end = start;
110     }
111
112     if (start > end) {
113       unsigned swap = start;
114       start = end;
115       end = swap;
116     }
117
118     for (unsigned i = start; i <= end; i++) {
119       if (is_in_list(i, nums, idx))
120         continue;
121       if (idx >= nums_len) {
122         WARNING(UTIL_NAME ": exceeded the cores number limit: %" PRIsz,
123                 nums_len);
124         return idx;
125       }
126       nums[idx] = i;
127       idx++;
128     }
129   }
130   return idx;
131 }
132
133 /*
134  * NAME
135  *   check_core_grouping
136  *
137  * DESCRIPTION
138  *   Look for [...] brackets in *in string and if found copy the
139  *   part between brackets into *out string and set grouped to 0.
140  *   Otherwise grouped is set to 1 and input is copied without leading
141  *   whitespaces.
142  *
143  * PARAMETERS
144  *   `out'       Output string to store result.
145  *   `in'        Input string to be parsed and copied.
146  *   `out_size'  Maximum number of elements that out can accommodate.
147  *   `grouped'   Set by function depending if cores should be grouped or not.
148  *
149  * RETURN VALUE
150  *    Zero upon success or non-zero if an error occurred.
151  */
152 static int check_core_grouping(char *out, const char *in, size_t out_size,
153                                _Bool *grouped) {
154   const char *start = in;
155   char *end;
156   while (isspace(*start))
157     ++start;
158   if (start[0] == '[') {
159     *grouped = 0;
160     ++start;
161     end = strchr(start, ']');
162     if (end == NULL) {
163       ERROR(UTIL_NAME ": Missing closing bracket ] in option %s.", in);
164       return -EINVAL;
165     }
166     if ((end - start) >= out_size) {
167       ERROR(UTIL_NAME ": Out buffer is too small.");
168       return -EINVAL;
169     }
170     sstrncpy(out, start, end - start + 1);
171     DEBUG(UTIL_NAME ": Mask for individual (not aggregated) cores: %s", out);
172   } else {
173     *grouped = 1;
174     sstrncpy(out, start, out_size);
175   }
176   return 0;
177 }
178
179 int config_cores_parse(const oconfig_item_t *ci, core_groups_list_t *cgl) {
180   if (ci == NULL || cgl == NULL)
181     return -EINVAL;
182   if (ci->values_num == 0 || ci->values_num > MAX_CORES)
183     return -EINVAL;
184   core_group_t cgroups[MAX_CORES] = {{0}};
185   size_t cg_idx = 0; /* index for cgroups array */
186   int ret = 0;
187
188   for (int i = 0; i < ci->values_num; i++) {
189     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
190       WARNING(UTIL_NAME ": The %s option requires string arguments.", ci->key);
191       return -EINVAL;
192     }
193   }
194
195   if (ci->values_num == 1 && ci->values[0].value.string &&
196       strlen(ci->values[0].value.string) == 0)
197     return 0;
198
199   for (int i = 0; i < ci->values_num; i++) {
200     size_t n;
201     _Bool grouped = 1;
202     char str[DATA_MAX_NAME_LEN];
203     unsigned cores[MAX_CORES] = {0};
204
205     if (cg_idx >= STATIC_ARRAY_SIZE(cgroups)) {
206       ERROR(UTIL_NAME
207             ": Configuration exceeds maximum number of cores: %" PRIsz,
208             STATIC_ARRAY_SIZE(cgroups));
209       ret = -EINVAL;
210       goto parse_error;
211     }
212     if ((ci->values[i].value.string == NULL) ||
213         (strlen(ci->values[i].value.string) == 0)) {
214       ERROR(UTIL_NAME ": Failed to parse parameters for %s option.", ci->key);
215       ret = -EINVAL;
216       goto parse_error;
217     }
218
219     ret = check_core_grouping(str, ci->values[i].value.string, sizeof(str),
220                               &grouped);
221     if (ret != 0) {
222       ERROR(UTIL_NAME ": Failed to parse config option [%d] %s.", i,
223             ci->values[i].value.string);
224       goto parse_error;
225     }
226     n = str_list_to_nums(str, cores, STATIC_ARRAY_SIZE(cores));
227     if (n == 0) {
228       ERROR(UTIL_NAME ": Failed to parse config option [%d] %s.", i,
229             ci->values[i].value.string);
230       ret = -EINVAL;
231       goto parse_error;
232     }
233
234     if (grouped) {
235       cgroups[cg_idx].desc = strdup(ci->values[i].value.string);
236       if (cgroups[cg_idx].desc == NULL) {
237         ERROR(UTIL_NAME ": Failed to allocate description.");
238         ret = -ENOMEM;
239         goto parse_error;
240       }
241
242       cgroups[cg_idx].cores = calloc(n, sizeof(*cgroups[cg_idx].cores));
243       if (cgroups[cg_idx].cores == NULL) {
244         ERROR(UTIL_NAME ": Failed to allocate cores for cgroup.");
245         ret = -ENOMEM;
246         goto parse_error;
247       }
248
249       for (size_t j = 0; j < n; j++)
250         cgroups[cg_idx].cores[j] = cores[j];
251
252       cgroups[cg_idx].num_cores = n;
253       cg_idx++;
254     } else {
255       for (size_t j = 0; j < n && cg_idx < STATIC_ARRAY_SIZE(cgroups); j++) {
256         char desc[DATA_MAX_NAME_LEN];
257         snprintf(desc, sizeof(desc), "%u", cores[j]);
258
259         cgroups[cg_idx].desc = strdup(desc);
260         if (cgroups[cg_idx].desc == NULL) {
261           ERROR(UTIL_NAME ": Failed to allocate desc for core %u.", cores[j]);
262           ret = -ENOMEM;
263           goto parse_error;
264         }
265
266         cgroups[cg_idx].cores = calloc(1, sizeof(*(cgroups[cg_idx].cores)));
267         if (cgroups[cg_idx].cores == NULL) {
268           ERROR(UTIL_NAME ": Failed to allocate cgroup for core %u.", cores[j]);
269           ret = -ENOMEM;
270           goto parse_error;
271         }
272         cgroups[cg_idx].num_cores = 1;
273         cgroups[cg_idx].cores[0] = cores[j];
274         cg_idx++;
275       }
276     }
277   }
278
279   cgl->cgroups = calloc(cg_idx, sizeof(*cgl->cgroups));
280   if (cgl->cgroups == NULL) {
281     ERROR(UTIL_NAME ": Failed to allocate core groups.");
282     ret = -ENOMEM;
283     goto parse_error;
284   }
285
286   cgl->num_cgroups = cg_idx;
287   for (size_t i = 0; i < cg_idx; i++)
288     cgl->cgroups[i] = cgroups[i];
289
290   return 0;
291
292 parse_error:
293
294   cg_idx = 0;
295   while (cg_idx < STATIC_ARRAY_SIZE(cgroups) && cgroups[cg_idx].desc != NULL) {
296     sfree(cgroups[cg_idx].desc);
297     sfree(cgroups[cg_idx].cores);
298     cg_idx++;
299   }
300   return ret;
301 }
302
303 int config_cores_default(int num_cores, core_groups_list_t *cgl) {
304   if (cgl == NULL || num_cores < 0 || num_cores > MAX_CORES)
305     return -EINVAL;
306
307   cgl->cgroups = calloc(num_cores, sizeof(*(cgl->cgroups)));
308   if (cgl->cgroups == NULL) {
309     ERROR(UTIL_NAME ": Failed to allocate memory for core groups.");
310     return -ENOMEM;
311   }
312   cgl->num_cgroups = num_cores;
313
314   for (int i = 0; i < num_cores; i++) {
315     char desc[DATA_MAX_NAME_LEN];
316     snprintf(desc, sizeof(desc), "%d", i);
317
318     cgl->cgroups[i].cores = calloc(1, sizeof(*(cgl->cgroups[i].cores)));
319     if (cgl->cgroups[i].cores == NULL) {
320       ERROR(UTIL_NAME ": Failed to allocate default cores for cgroup %d.", i);
321       config_cores_cleanup(cgl);
322       return -ENOMEM;
323     }
324     cgl->cgroups[i].num_cores = 1;
325     cgl->cgroups[i].cores[0] = i;
326
327     cgl->cgroups[i].desc = strdup(desc);
328     if (cgl->cgroups[i].desc == NULL) {
329       ERROR(UTIL_NAME ": Failed to allocate description for cgroup %d.", i);
330       config_cores_cleanup(cgl);
331       return -ENOMEM;
332     }
333   }
334   return 0;
335 }
336
337 void config_cores_cleanup(core_groups_list_t *cgl) {
338   if (cgl == NULL)
339     return;
340   for (size_t i = 0; i < cgl->num_cgroups; i++) {
341     sfree(cgl->cgroups[i].desc);
342     sfree(cgl->cgroups[i].cores);
343   }
344   sfree(cgl->cgroups);
345   cgl->num_cgroups = 0;
346 }
347
348 int config_cores_cmp_cgroups(const core_group_t *cg_a,
349                              const core_group_t *cg_b) {
350   size_t found = 0;
351
352   assert(cg_a != NULL);
353   assert(cg_b != NULL);
354
355   const size_t sz_a = cg_a->num_cores;
356   const size_t sz_b = cg_b->num_cores;
357   const unsigned *tab_a = cg_a->cores;
358   const unsigned *tab_b = cg_b->cores;
359
360   for (size_t i = 0; i < sz_a; i++)
361     if (is_in_list(tab_a[i], tab_b, sz_b))
362       found++;
363
364   /* if no cores are the same */
365   if (!found)
366     return 0;
367   /* if group contains same cores */
368   if (sz_a == sz_b && sz_b == found)
369     return 1;
370   /* if not all cores are the same */
371   return -1;
372 }