intel_rdt: Backwards compatibility, libpqos v1
[collectd.git] / src / intel_rdt.c
1 /**
2  * collectd - src/intel_rdt.c
3  *
4  * Copyright(c) 2016-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  *   Serhiy Pshyk <serhiyx.pshyk@intel.com>
26  *   Starzyk, Mateusz <mateuszx.starzyk@intel.com>
27  *   Wojciech Andralojc <wojciechx.andralojc@intel.com>
28  **/
29
30 #include "collectd.h"
31 #include "utils/common/common.h"
32 #include "utils/config_cores/config_cores.h"
33 #include <pqos.h>
34
35 #define RDT_PLUGIN "intel_rdt"
36
37 /* libpqos v2.0 or newer is required for process monitoring*/
38 #undef LIBPQOS2
39 #if defined(PQOS_VERSION) && PQOS_VERSION >= 20000
40 #define LIBPQOS2
41 #endif
42
43 #define RDT_PLUGIN "intel_rdt"
44
45 #define RDT_MAX_SOCKETS 8
46 #define RDT_MAX_SOCKET_CORES 64
47 #define RDT_MAX_CORES (RDT_MAX_SOCKET_CORES * RDT_MAX_SOCKETS)
48
49 #ifdef LIBPQOS2
50 /*
51  * Process name inside comm file is limited to 16 chars.
52  * More info here: http://man7.org/linux/man-pages/man5/proc.5.html
53  */
54 #define RDT_MAX_NAME_LEN 16
55 #define RDT_MAX_NAMES_GROUPS 64
56
57 #define RDT_PROC_PATH "/proc"
58 #endif /* LIBPQOS2 */
59
60 typedef enum {
61   UNKNOWN = 0,
62   CONFIGURATION_ERROR,
63 } rdt_config_status;
64
65 #ifdef LIBPQOS2
66 /* Helper typedef for process name array
67  * Extra 1 char is added for string null termination.
68  */
69 typedef char proc_comm_t[RDT_MAX_NAME_LEN + 1];
70
71 /* Linked one-way list of pids. */
72 typedef struct pids_list_s {
73   pid_t pid;
74   struct pids_list_s *next;
75 } pids_list_t;
76
77 /* Holds process name and list of pids assigned to that name */
78 typedef struct proc_pids_s {
79   proc_comm_t proccess_name;
80   pids_list_t *pids;
81 } proc_pids_t;
82
83 struct rdt_name_group_s {
84   char *desc;
85   size_t num_names;
86   char **names;
87   proc_pids_t *proc_pids_array;
88   enum pqos_mon_event events;
89 };
90 typedef struct rdt_name_group_s rdt_name_group_t;
91 #endif /* LIBPQOS2 */
92
93 struct rdt_ctx_s {
94   core_groups_list_t cores;
95   enum pqos_mon_event events[RDT_MAX_CORES];
96   struct pqos_mon_data *pcgroups[RDT_MAX_CORES];
97 #ifdef LIBPQOS2
98   rdt_name_group_t ngroups[RDT_MAX_NAMES_GROUPS];
99   struct pqos_mon_data *pngroups[RDT_MAX_NAMES_GROUPS];
100   size_t num_ngroups;
101 #endif /* LIBPQOS2 */
102   const struct pqos_cpuinfo *pqos_cpu;
103   const struct pqos_cap *pqos_cap;
104   const struct pqos_capability *cap_mon;
105 };
106 typedef struct rdt_ctx_s rdt_ctx_t;
107
108 static rdt_ctx_t *g_rdt;
109
110 static rdt_config_status g_state = UNKNOWN;
111
112 #ifdef LIBPQOS2
113 static int isdupstr(const char *names[], const size_t size, const char *name) {
114   for (size_t i = 0; i < size; i++)
115     if (strncmp(names[i], name, (size_t)RDT_MAX_NAME_LEN) == 0)
116       return 1;
117
118   return 0;
119 }
120
121 /*
122  * NAME
123  *   strlisttoarray
124  *
125  * DESCRIPTION
126  *   Converts string representing list of strings into array of strings.
127  *   Allowed format is:
128  *     name,name1,name2,name3
129  *
130  * PARAMETERS
131  *   `str_list'  String representing list of strings.
132  *   `names'     Array to put extracted strings into.
133  *   `names_num' Variable to put number of extracted strings.
134  *
135  * RETURN VALUE
136  *    Number of elements placed into names.
137  */
138 static int strlisttoarray(char *str_list, char ***names, size_t *names_num) {
139   char *saveptr = NULL;
140
141   if (str_list == NULL || names == NULL)
142     return -EINVAL;
143
144   for (;;) {
145     char *token = strtok_r(str_list, ",", &saveptr);
146     if (token == NULL)
147       break;
148
149     str_list = NULL;
150
151     while (isspace(*token))
152       token++;
153
154     if (*token == '\0')
155       continue;
156
157     if (!(isdupstr((const char **)*names, *names_num, token)))
158       if (0 != strarray_add(names, names_num, token)) {
159         ERROR(RDT_PLUGIN ": Error allocating process name string");
160         return -ENOMEM;
161       }
162   }
163
164   return 0;
165 }
166
167 /*
168  * NAME
169  *   ngroup_cmp
170  *
171  * DESCRIPTION
172  *   Function to compare names in two name groups.
173  *
174  * PARAMETERS
175  *   `ng_a'      Pointer to name group a.
176  *   `ng_b'      Pointer to name group b.
177  *
178  * RETURN VALUE
179  *    1 if both groups contain the same names
180  *    0 if none of their names match
181  *    -1 if some but not all names match
182  */
183 static int ngroup_cmp(const rdt_name_group_t *ng_a,
184                       const rdt_name_group_t *ng_b) {
185   unsigned found = 0;
186
187   assert(ng_a != NULL);
188   assert(ng_b != NULL);
189
190   const size_t sz_a = (unsigned)ng_a->num_names;
191   const size_t sz_b = (unsigned)ng_b->num_names;
192   const char **tab_a = (const char **)ng_a->names;
193   const char **tab_b = (const char **)ng_b->names;
194
195   for (size_t i = 0; i < sz_a; i++) {
196     for (size_t j = 0; j < sz_b; j++)
197       if (strncmp(tab_a[i], tab_b[j], (size_t)RDT_MAX_NAME_LEN) == 0)
198         found++;
199   }
200   /* if no names are the same */
201   if (!found)
202     return 0;
203   /* if group contains same names */
204   if (sz_a == sz_b && sz_b == (size_t)found)
205     return 1;
206   /* if not all names are the same */
207   return -1;
208 }
209
210 /*
211  * NAME
212  *   oconfig_to_ngroups
213  *
214  * DESCRIPTION
215  *   Function to set the descriptions and names for each process names group.
216  *   Takes a config option containing list of strings that are used to set
217  *   process group values.
218  *
219  * PARAMETERS
220  *   `item'        Config option containing process names groups.
221  *   `groups'      Table of process name groups to set values in.
222  *   `max_groups'  Maximum number of process name groups allowed.
223  *
224  * RETURN VALUE
225  *   On success, the number of name groups set up. On error, appropriate
226  *   negative error value.
227  */
228 static int oconfig_to_ngroups(const oconfig_item_t *item,
229                               rdt_name_group_t *groups,
230                               const size_t max_groups) {
231   int index = 0;
232
233   assert(groups != NULL);
234   assert(max_groups > 0);
235   assert(item != NULL);
236
237   for (int j = 0; j < item->values_num; j++) {
238     int ret;
239     char value[DATA_MAX_NAME_LEN];
240
241     if ((item->values[j].value.string == NULL) ||
242         (strlen(item->values[j].value.string) == 0))
243       continue;
244
245     sstrncpy(value, item->values[j].value.string, sizeof(value));
246
247     ret = strlisttoarray(value, &groups[index].names, &groups[index].num_names);
248     if (ret != 0 || groups[index].num_names == 0) {
249       ERROR(RDT_PLUGIN ": Error parsing process names group (%s)",
250             item->values[j].value.string);
251       return -EINVAL;
252     }
253
254     /* set group description info */
255     groups[index].desc = sstrdup(item->values[j].value.string);
256     if (groups[index].desc == NULL) {
257       ERROR(RDT_PLUGIN ": Error allocating name group description");
258       return -ENOMEM;
259     }
260
261     groups[index].proc_pids_array = NULL;
262
263     index++;
264
265     if (index >= (const int)max_groups) {
266       WARNING(RDT_PLUGIN ": Too many process names groups configured");
267       return index;
268     }
269   }
270
271   return index;
272 }
273 #endif /* LIBPQOS2 */
274
275 #if COLLECT_DEBUG
276 static void rdt_dump_cgroups(void) {
277   char cores[RDT_MAX_CORES * 4];
278
279   if (g_rdt == NULL)
280     return;
281
282   DEBUG(RDT_PLUGIN ": Core Groups Dump");
283   DEBUG(RDT_PLUGIN ":  groups count: %" PRIsz, g_rdt->cores.num_cgroups);
284
285   for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
286     core_group_t *cgroup = g_rdt->cores.cgroups + i;
287
288     memset(cores, 0, sizeof(cores));
289     for (size_t j = 0; j < cgroup->num_cores; j++) {
290       snprintf(cores + strlen(cores), sizeof(cores) - strlen(cores) - 1, " %d",
291                cgroup->cores[j]);
292     }
293
294     DEBUG(RDT_PLUGIN ":  group[%zu]:", i);
295     DEBUG(RDT_PLUGIN ":    description: %s", cgroup->desc);
296     DEBUG(RDT_PLUGIN ":    cores: %s", cores);
297     DEBUG(RDT_PLUGIN ":    events: 0x%X", g_rdt->events[i]);
298   }
299
300   return;
301 }
302
303 #ifdef LIBPQOS2
304 static void rdt_dump_ngroups(void) {
305
306   char names[DATA_MAX_NAME_LEN];
307
308   if (g_rdt == NULL)
309     return;
310
311   DEBUG(RDT_PLUGIN ": Process Names Groups Dump");
312   DEBUG(RDT_PLUGIN ":  groups count: %" PRIsz, g_rdt->num_ngroups);
313
314   for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
315     memset(names, 0, sizeof(names));
316     for (size_t j = 0; j < g_rdt->ngroups[i].num_names; j++)
317       snprintf(names + strlen(names), sizeof(names) - strlen(names) - 1, " %s",
318                g_rdt->ngroups[i].names[j]);
319
320     DEBUG(RDT_PLUGIN ":  group[%d]:", (int)i);
321     DEBUG(RDT_PLUGIN ":    description: %s", g_rdt->ngroups[i].desc);
322     DEBUG(RDT_PLUGIN ":    process names:%s", names);
323     DEBUG(RDT_PLUGIN ":    events: 0x%X", g_rdt->ngroups[i].events);
324   }
325
326   return;
327 }
328 #endif /* LIBPQOS2 */
329
330 static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
331
332 static inline double bytes_to_mb(const double bytes) {
333   return bytes / (1024.0 * 1024.0);
334 }
335
336 static void rdt_dump_data(void) {
337   /*
338    * CORE - monitored group of cores
339    * NAME - monitored group of processes
340    * RMID - Resource Monitoring ID associated with the monitored group
341    * LLC - last level cache occupancy
342    * MBL - local memory bandwidth
343    * MBR - remote memory bandwidth
344    */
345   DEBUG("  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
346   for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
347
348     const struct pqos_event_values *pv = &g_rdt->pcgroups[i]->values;
349
350     double llc = bytes_to_kb(pv->llc);
351     double mbr = bytes_to_mb(pv->mbm_remote_delta);
352     double mbl = bytes_to_mb(pv->mbm_local_delta);
353
354     DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cores.cgroups[i].desc,
355           g_rdt->pcgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
356   }
357
358 #ifdef LIBPQOS2
359   DEBUG("  NAME     PIDs");
360   char pids[DATA_MAX_NAME_LEN];
361   for (size_t i = 0; i < g_rdt->num_ngroups; ++i) {
362     memset(pids, 0, sizeof(pids));
363     for (size_t j = 0; j < g_rdt->ngroups[i].num_names; ++j) {
364       pids_list_t *list = g_rdt->ngroups[i].proc_pids_array[j].pids;
365       while (list != NULL) {
366         snprintf(pids + strlen(pids), sizeof(pids) - strlen(pids) - 1, " %u",
367                  list->pid);
368         list = list->next;
369       }
370     }
371     DEBUG(" [%s] %s", g_rdt->ngroups[i].desc, pids);
372   }
373
374   DEBUG("  NAME     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
375   for (size_t i = 0; i < g_rdt->num_ngroups; i++) {
376
377     if (g_rdt->pngroups[i]->poll_ctx == NULL)
378       continue;
379
380     const struct pqos_event_values *pv = &g_rdt->pngroups[i]->values;
381
382     double llc = bytes_to_kb(pv->llc);
383     double mbr = bytes_to_mb(pv->mbm_remote_delta);
384     double mbl = bytes_to_mb(pv->mbm_local_delta);
385
386     DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->ngroups[i].desc,
387           g_rdt->pngroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
388   }
389 #endif /* LIBPQOS2 */
390 }
391 #endif /* COLLECT_DEBUG */
392
393 static void rdt_free_cgroups(void) {
394   config_cores_cleanup(&g_rdt->cores);
395   for (int i = 0; i < RDT_MAX_CORES; i++) {
396     sfree(g_rdt->pcgroups[i]);
397   }
398 }
399
400 #ifdef LIBPQOS2
401 static int pids_list_free(pids_list_t *list) {
402   assert(list);
403
404   pids_list_t *current = list;
405   while (current != NULL) {
406     pids_list_t *previous = current;
407     current = current->next;
408     sfree(previous);
409   }
410   return 0;
411 }
412
413 static void rdt_free_ngroups(void) {
414   for (int i = 0; i < RDT_MAX_NAMES_GROUPS; i++) {
415     DEBUG(RDT_PLUGIN ": Freeing \'%s\' group\'s data...",
416           g_rdt->ngroups[i].desc);
417     sfree(g_rdt->ngroups[i].desc);
418     strarray_free(g_rdt->ngroups[i].names, g_rdt->ngroups[i].num_names);
419
420     if (g_rdt->ngroups[i].proc_pids_array) {
421       for (size_t j = 0; j < g_rdt->ngroups[i].num_names; ++j) {
422         if (NULL == g_rdt->ngroups[i].proc_pids_array[j].pids)
423           continue;
424         pids_list_free(g_rdt->ngroups[i].proc_pids_array[j].pids);
425       }
426
427       sfree(g_rdt->ngroups[i].proc_pids_array);
428     }
429
430     g_rdt->ngroups[i].num_names = 0;
431     sfree(g_rdt->pngroups[i]);
432   }
433 }
434 #endif /* LIBPQOS2 */
435
436 static int rdt_default_cgroups(void) {
437   unsigned num_cores = g_rdt->pqos_cpu->num_cores;
438
439   g_rdt->cores.cgroups = calloc(num_cores, sizeof(*g_rdt->cores.cgroups));
440   if (g_rdt->cores.cgroups == NULL) {
441     ERROR(RDT_PLUGIN ": Error allocating core groups array");
442     return -ENOMEM;
443   }
444   g_rdt->cores.num_cgroups = num_cores;
445
446   /* configure each core in separate group */
447   for (unsigned i = 0; i < num_cores; i++) {
448     core_group_t *cgroup = g_rdt->cores.cgroups + i;
449     char desc[DATA_MAX_NAME_LEN];
450
451     /* set core group info */
452     cgroup->cores = calloc(1, sizeof(*cgroup->cores));
453     if (cgroup->cores == NULL) {
454       ERROR(RDT_PLUGIN ": Error allocating cores array");
455       rdt_free_cgroups();
456       return -ENOMEM;
457     }
458     cgroup->num_cores = 1;
459     cgroup->cores[0] = i;
460
461     snprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore);
462     cgroup->desc = strdup(desc);
463     if (cgroup->desc == NULL) {
464       ERROR(RDT_PLUGIN ": Error allocating core group description");
465       rdt_free_cgroups();
466       return -ENOMEM;
467     }
468   }
469
470   return num_cores;
471 }
472
473 static int rdt_is_core_id_valid(unsigned int core_id) {
474
475   for (unsigned int i = 0; i < g_rdt->pqos_cpu->num_cores; i++)
476     if (core_id == g_rdt->pqos_cpu->cores[i].lcore)
477       return 1;
478
479   return 0;
480 }
481
482 #ifdef LIBPQOS2
483 static int rdt_is_proc_name_valid(const char *name) {
484
485   if (name != NULL) {
486     unsigned len = strlen(name);
487     if (len > 0 && len <= RDT_MAX_NAME_LEN)
488       return 1;
489     else {
490       DEBUG(RDT_PLUGIN
491             ": Process name \'%s\' is too long. Max supported len is %d chars.",
492             name, RDT_MAX_NAME_LEN);
493     }
494   }
495
496   return 0;
497 }
498 #endif /* LIBPQOS2 */
499
500 static int rdt_config_cgroups(oconfig_item_t *item) {
501   size_t n = 0;
502   enum pqos_mon_event events = 0;
503
504   if (config_cores_parse(item, &g_rdt->cores) < 0) {
505     rdt_free_cgroups();
506     ERROR(RDT_PLUGIN ": Error parsing core groups configuration.");
507     return -EINVAL;
508   }
509   n = g_rdt->cores.num_cgroups;
510
511   /* validate configured core id values */
512   for (size_t group_idx = 0; group_idx < n; group_idx++) {
513     core_group_t *cgroup = g_rdt->cores.cgroups + group_idx;
514     for (size_t core_idx = 0; core_idx < cgroup->num_cores; core_idx++) {
515       if (!rdt_is_core_id_valid(cgroup->cores[core_idx])) {
516         ERROR(RDT_PLUGIN ": Core group '%s' contains invalid core id '%u'",
517               cgroup->desc, cgroup->cores[core_idx]);
518         rdt_free_cgroups();
519         return -EINVAL;
520       }
521     }
522   }
523
524   if (n == 0) {
525     /* create default core groups if "Cores" config option is empty */
526     int ret = rdt_default_cgroups();
527     if (ret < 0) {
528       rdt_free_cgroups();
529       ERROR(RDT_PLUGIN ": Error creating default core groups configuration.");
530       return ret;
531     }
532     n = (size_t)ret;
533     INFO(RDT_PLUGIN
534          ": No core groups configured. Default core groups created.");
535   }
536
537   /* Get all available events on this platform */
538   for (unsigned int i = 0; i < g_rdt->cap_mon->u.mon->num_events; i++)
539     events |= g_rdt->cap_mon->u.mon->events[i].type;
540
541   events &= ~(PQOS_PERF_EVENT_LLC_MISS);
542
543   DEBUG(RDT_PLUGIN ": Number of cores in the system: %u",
544         g_rdt->pqos_cpu->num_cores);
545   DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
546
547   g_rdt->cores.num_cgroups = n;
548   for (int i = 0; i < n; i++) {
549     for (int j = 0; j < i; j++) {
550       int found = 0;
551       found = config_cores_cmp_cgroups(&g_rdt->cores.cgroups[j],
552                                        &g_rdt->cores.cgroups[i]);
553       if (found != 0) {
554         rdt_free_cgroups();
555         ERROR(RDT_PLUGIN ": Cannot monitor same cores in different groups.");
556         return -EINVAL;
557       }
558     }
559
560     g_rdt->events[i] = events;
561     g_rdt->pcgroups[i] = calloc(1, sizeof(*g_rdt->pcgroups[i]));
562     if (g_rdt->pcgroups[i] == NULL) {
563       rdt_free_cgroups();
564       ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
565       return -ENOMEM;
566     }
567   }
568
569   return 0;
570 }
571
572 #ifdef LIBPQOS2
573 static int rdt_config_ngroups(const oconfig_item_t *item) {
574   int n = 0;
575   enum pqos_mon_event events = 0;
576
577   if (item == NULL) {
578     DEBUG(RDT_PLUGIN ": ngroups_config: Invalid argument.");
579     return -EINVAL;
580   }
581
582   DEBUG(RDT_PLUGIN ": Process names groups [%d]:", item->values_num);
583   for (int j = 0; j < item->values_num; j++) {
584     if (item->values[j].type != OCONFIG_TYPE_STRING) {
585       ERROR(RDT_PLUGIN
586             ": given process names group value is not a string [idx=%d]",
587             j);
588       return -EINVAL;
589     }
590     DEBUG(RDT_PLUGIN ":  [%d]: %s", j, item->values[j].value.string);
591   }
592
593   n = oconfig_to_ngroups(item, g_rdt->ngroups, RDT_MAX_NAMES_GROUPS);
594   if (n < 0) {
595     rdt_free_ngroups();
596     ERROR(RDT_PLUGIN ": Error parsing process name groups configuration.");
597     return -EINVAL;
598   }
599
600   /* validate configured process name values */
601   for (int group_idx = 0; group_idx < n; group_idx++) {
602     for (size_t name_idx = 0; name_idx < g_rdt->ngroups[group_idx].num_names;
603          name_idx++) {
604       if (!rdt_is_proc_name_valid(g_rdt->ngroups[group_idx].names[name_idx])) {
605         ERROR(RDT_PLUGIN ": Process name group '%s' contains invalid name '%s'",
606               g_rdt->ngroups[group_idx].desc,
607               g_rdt->ngroups[group_idx].names[name_idx]);
608         rdt_free_ngroups();
609         return -EINVAL;
610       }
611     }
612   }
613
614   if (n == 0) {
615     ERROR(RDT_PLUGIN ": Empty process name groups configured.");
616     return -EINVAL;
617   }
618
619   /* Get all available events on this platform */
620   for (unsigned i = 0; i < g_rdt->cap_mon->u.mon->num_events; i++)
621     events |= g_rdt->cap_mon->u.mon->events[i].type;
622
623   events &= ~(PQOS_PERF_EVENT_LLC_MISS);
624
625   DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
626
627   g_rdt->num_ngroups = n;
628   for (int i = 0; i < n; i++) {
629     for (int j = 0; j < i; j++) {
630       int found = ngroup_cmp(&g_rdt->ngroups[j], &g_rdt->ngroups[i]);
631       if (found != 0) {
632         rdt_free_ngroups();
633         ERROR(RDT_PLUGIN
634               ": Cannot monitor same process name in different groups.");
635         return -EINVAL;
636       }
637     }
638
639     g_rdt->ngroups[i].events = events;
640     g_rdt->pngroups[i] = calloc(1, sizeof(*g_rdt->pngroups[i]));
641     if (g_rdt->pngroups[i] == NULL) {
642       rdt_free_ngroups();
643       ERROR(RDT_PLUGIN
644             ": Failed to allocate memory for process name monitoring data.");
645       return -ENOMEM;
646     }
647   }
648
649   return 0;
650 }
651
652 /*
653  * NAME
654  *   pids_list_add_pid
655  *
656  * DESCRIPTION
657  *   Adds pid at the end of the pids list.
658  *   Allocates memory for new pid element, it is up to user to free it.
659  *
660  * PARAMETERS
661  *   `list'     Head of target pids_list.
662  *   `pid'      Pid to be added.
663  *
664  * RETURN VALUE
665  *   On success, returns 0.
666  *   -1 on memory allocation error.
667  */
668 static int pids_list_add_pid(pids_list_t **list, const pid_t pid) {
669   assert(list);
670
671   pids_list_t *new_element = calloc(1, sizeof(*new_element));
672
673   if (new_element == NULL) {
674     ERROR(RDT_PLUGIN ": Alloc error\n");
675     return -1;
676   }
677   new_element->pid = pid;
678   new_element->next = NULL;
679
680   pids_list_t **current = list;
681   while (*current != NULL) {
682     current = &((*current)->next);
683   }
684   *current = new_element;
685   return 0;
686 }
687
688 /*
689  * NAME
690  *   pids_list_contains_pid
691  *
692  * DESCRIPTION
693  *   Tests if pids list contains specific pid.
694  *
695  * PARAMETERS
696  *   `list'     Head of pids_list.
697  *   `pid'      Pid to be searched for.
698  *
699  * RETURN VALUE
700  *   If PID found in list, returns 1,
701  *   Otherwise returns 0.
702  */
703 static int pids_list_contains_pid(pids_list_t *list, const pid_t pid) {
704   assert(list);
705
706   pids_list_t *current = list;
707   while (current != NULL) {
708     if (current->pid == pid)
709       return 1;
710     current = current->next;
711   }
712   return 0;
713 }
714
715 /*
716  * NAME
717  *   pids_list_add_pids_list
718  *
719  * DESCRIPTION
720  *   Adds pids list at the end of the pids list.
721  *   Allocates memory for new pid elements, it is up to user to free it.
722  *   Increases dst_num by a number of added PIDs.
723  *
724  * PARAMETERS
725  *   `dst'      Head of target PIDs list.
726  *   `src'      Head of source PIDs list.
727  *   `dst_num'  Variable to be increased by a number of appended PIDs.
728  *
729  * RETURN VALUE
730  *   On success, returns 0.
731  *   -1 on memory allocation error.
732  */
733 static int pids_list_add_pids_list(pids_list_t **dst, pids_list_t *src,
734                                    size_t *dst_num) {
735   assert(dst);
736   assert(src);
737   assert(dst_num);
738
739   pids_list_t *current = src;
740   int ret;
741
742   while (current != NULL) {
743     ret = pids_list_add_pid(dst, current->pid);
744     if (0 != ret)
745       return ret;
746
747     ++(*dst_num);
748     current = current->next;
749   }
750
751   return 0;
752 }
753
754 /*
755  * NAME
756  *   read_proc_name
757  *
758  * DESCRIPTION
759  *   Reads process name from given pid directory.
760  *   Strips new-line character (\n).
761  *
762  * PARAMETERS
763  *   `procfs_path` Path to systems proc directory (e.g. /proc)
764  *   `pid_entry'   Dirent for PID directory
765  *   `name'        Output buffer for process name, recommended proc_comm.
766  *   `out_size'    Output buffer size, recommended sizeof(proc_comm)
767  *
768  * RETURN VALUE
769  *   On success, the number of read bytes (includes stripped \n).
770  *   -1 on file open error
771  */
772 static int read_proc_name(const char *procfs_path,
773                           const struct dirent *pid_entry, char *name,
774                           const size_t out_size) {
775   assert(procfs_path);
776   assert(pid_entry);
777   assert(name);
778   assert(out_size);
779   memset(name, 0, out_size);
780
781   const char *comm_file_name = "comm";
782
783   char *path = ssnprintf_alloc("%s/%s/%s", procfs_path, pid_entry->d_name,
784                                comm_file_name);
785
786   FILE *f = fopen(path, "r");
787   if (f == NULL) {
788     ERROR(RDT_PLUGIN ": Failed to open comm file, error: %d\n", errno);
789     sfree(path);
790     return -1;
791   }
792   size_t read_length = fread(name, sizeof(char), out_size, f);
793   fclose(f);
794   sfree(path);
795   /* strip new line ending */
796   char *newline = strchr(name, '\n');
797   if (newline) {
798     *newline = '\0';
799   }
800
801   return read_length;
802 }
803
804 /*
805  * NAME
806  *   get_pid_number
807  *
808  * DESCRIPTION
809  *   Gets pid number for given /proc/pid directory entry or
810  *   returns error if input directory does not hold PID information.
811  *
812  * PARAMETERS
813  *   `entry'    Dirent for PID directory
814  *   `pid'      PID number to be filled
815  *
816  * RETURN VALUE
817  *   0 on success. Negative number on error:
818  *   -1: given entry is not a directory
819  *   -2: PID conversion error
820  */
821 static int get_pid_number(struct dirent *entry, pid_t *pid) {
822   char *tmp_end; /* used for strtoul error check*/
823
824   if (pid == NULL || entry == NULL)
825     return -1;
826
827   if (entry->d_type != DT_DIR)
828     return -1;
829
830   /* trying to get pid number from directory name*/
831   *pid = strtoul(entry->d_name, &tmp_end, 10);
832   if (*tmp_end != '\0') {
833     return -2; /* conversion failed, not proc-pid */
834   }
835   /* all checks passed, marking as success */
836   return 0;
837 }
838
839 /*
840  * NAME
841  *   fetch_pids_for_procs
842  *
843  * DESCRIPTION
844  *   Finds PIDs matching given process's names.
845  *   Searches all PID directories in /proc fs and
846  *   allocates memory for proc_pids structs, it is up to user to free it.
847  *   Output array will have same element count as input array.
848  *
849  * PARAMETERS
850  *   `procfs_path'      Path to systems proc directory (e.g. /proc)
851  *   `procs'            Array of null-terminated strings with
852  *                      process' names to search for
853  *   `procs_size'       procs array element count
854  *   `proc_pids_array'  Address of pointer, under which new
855  *                      array of proc_pids will be allocated. Must be NULL.
856  *
857  * RETURN VALUE
858  *   0 on success. Negative number on error:
859  *   -1: could not open /proc dir
860  */
861 __attribute__((unused)) /* TODO: remove this attribute when PID monitoring is
862                            implemented */
863 static int
864 fetch_pids_for_procs(const char *procfs_path, const char **procs_names_array,
865                      const size_t procs_names_array_size,
866                      proc_pids_t **proc_pids_array) {
867   assert(procfs_path);
868   assert(procs_names_array);
869   assert(procs_names_array_size);
870   assert(proc_pids_array);
871   assert(NULL == *proc_pids_array);
872
873   DIR *proc_dir = opendir(procfs_path);
874   if (proc_dir == NULL) {
875     ERROR(RDT_PLUGIN ": Could not open %s directory, error: %d", procfs_path,
876           errno);
877     return -1;
878   }
879
880   /* Copy procs names to output array. Initialize pids list with NULL value. */
881   (*proc_pids_array) =
882       calloc(procs_names_array_size, sizeof(**proc_pids_array));
883   for (size_t i = 0; i < procs_names_array_size; ++i) {
884     sstrncpy((*proc_pids_array)[i].proccess_name, procs_names_array[i],
885              STATIC_ARRAY_SIZE((*proc_pids_array)[i].proccess_name));
886     (*proc_pids_array)[i].pids = NULL;
887   }
888
889   /* Go through procfs and find PIDS and their comms */
890   struct dirent *entry;
891   while ((entry = readdir(proc_dir)) != NULL) {
892
893     pid_t pid;
894     int pid_conversion = get_pid_number(entry, &pid);
895     if (pid_conversion < 0)
896       continue;
897
898     proc_comm_t comm;
899     int read_result =
900         read_proc_name(procfs_path, entry, comm, sizeof(proc_comm_t));
901     if (read_result <= 0) {
902       ERROR(RDT_PLUGIN ": Comm file skipped. Read result: %d", read_result);
903       continue;
904     }
905
906     /* Try to find comm in input procs array (proc_pids_array has same names) */
907     for (size_t i = 0; i < procs_names_array_size; ++i) {
908       if (0 == strncmp(comm, (*proc_pids_array)[i].proccess_name,
909                        STATIC_ARRAY_SIZE(comm)))
910         pids_list_add_pid(&((*proc_pids_array)[i].pids), pid);
911     }
912   }
913
914   int close_result = closedir(proc_dir);
915   if (0 != close_result) {
916     ERROR(RDT_PLUGIN ": failed to close %s directory, error: %d", procfs_path,
917           errno);
918     return -1;
919   }
920   return 0;
921 }
922 #endif /* LIBPQOS2 */
923
924 static void rdt_pqos_log(void *context, const size_t size, const char *msg) {
925   DEBUG(RDT_PLUGIN ": %s", msg);
926 }
927
928 static int rdt_preinit(void) {
929   int ret;
930
931   if (g_rdt != NULL) {
932     /* already initialized if config callback was called before init callback */
933     return 0;
934   }
935
936   g_rdt = calloc(1, sizeof(*g_rdt));
937   if (g_rdt == NULL) {
938     ERROR(RDT_PLUGIN ": Failed to allocate memory for rdt context.");
939     return -ENOMEM;
940   }
941
942   struct pqos_config pqos = {.fd_log = -1,
943                              .callback_log = rdt_pqos_log,
944                              .context_log = NULL,
945                              .verbose = 0};
946
947   ret = pqos_init(&pqos);
948   if (ret != PQOS_RETVAL_OK) {
949     ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
950     goto rdt_preinit_error1;
951   }
952
953   ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
954   if (ret != PQOS_RETVAL_OK) {
955     ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
956     goto rdt_preinit_error2;
957   }
958
959   ret = pqos_cap_get_type(g_rdt->pqos_cap, PQOS_CAP_TYPE_MON, &g_rdt->cap_mon);
960   if (ret == PQOS_RETVAL_PARAM) {
961     ERROR(RDT_PLUGIN ": Error retrieving monitoring capabilities.");
962     goto rdt_preinit_error2;
963   }
964
965   if (g_rdt->cap_mon == NULL) {
966     ERROR(
967         RDT_PLUGIN
968         ": Monitoring capability not detected. Nothing to do for the plugin.");
969     goto rdt_preinit_error2;
970   }
971
972   /* Reset pqos monitoring groups registers */
973   pqos_mon_reset();
974
975   return 0;
976
977 rdt_preinit_error2:
978   pqos_fini();
979
980 rdt_preinit_error1:
981
982   sfree(g_rdt);
983
984   return -1;
985 }
986
987 static int rdt_config(oconfig_item_t *ci) {
988   if (rdt_preinit() != 0) {
989     g_state = CONFIGURATION_ERROR;
990     /* if we return -1 at this point collectd
991       reports a failure in configuration and
992       aborts
993     */
994     return (0);
995   }
996
997   for (int i = 0; i < ci->children_num; i++) {
998     oconfig_item_t *child = ci->children + i;
999
1000     if (strncasecmp("Cores", child->key, (size_t)strlen("Cores")) == 0) {
1001       if (rdt_config_cgroups(child) != 0) {
1002         g_state = CONFIGURATION_ERROR;
1003         /* if we return -1 at this point collectd
1004            reports a failure in configuration and
1005            aborts
1006          */
1007         return (0);
1008       }
1009
1010 #if COLLECT_DEBUG
1011       rdt_dump_cgroups();
1012 #endif /* COLLECT_DEBUG */
1013     } else if (strncasecmp("Processes", child->key,
1014                            (size_t)strlen("Processes")) == 0) {
1015 #ifdef LIBPQOS2
1016       if (rdt_config_ngroups(child) != 0) {
1017         g_state = CONFIGURATION_ERROR;
1018         /* if we return -1 at this point collectd
1019            reports a failure in configuration and
1020            aborts
1021          */
1022         return (0);
1023       }
1024
1025 #if COLLECT_DEBUG
1026       rdt_dump_ngroups();
1027 #endif /* COLLECT_DEBUG */
1028 #else  /* !LIBPQOS2 */
1029       ERROR(RDT_PLUGIN ": Configuration parameter \"%s\" not supported, please "
1030                        "recompile collectd with libpqos version 2.0 or newer.",
1031             child->key);
1032 #endif /* LIBPQOS2 */
1033     } else {
1034       ERROR(RDT_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
1035     }
1036   }
1037
1038   return 0;
1039 }
1040
1041 static void rdt_submit_derive(const char *cgroup, const char *type,
1042                               const char *type_instance, derive_t value) {
1043   value_list_t vl = VALUE_LIST_INIT;
1044
1045   vl.values = &(value_t){.derive = value};
1046   vl.values_len = 1;
1047
1048   sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
1049   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
1050   sstrncpy(vl.type, type, sizeof(vl.type));
1051   if (type_instance)
1052     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
1053
1054   plugin_dispatch_values(&vl);
1055 }
1056
1057 static void rdt_submit_gauge(const char *cgroup, const char *type,
1058                              const char *type_instance, gauge_t value) {
1059   value_list_t vl = VALUE_LIST_INIT;
1060
1061   vl.values = &(value_t){.gauge = value};
1062   vl.values_len = 1;
1063
1064   sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
1065   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
1066   sstrncpy(vl.type, type, sizeof(vl.type));
1067   if (type_instance)
1068     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
1069
1070   plugin_dispatch_values(&vl);
1071 }
1072
1073 #ifdef LIBPQOS2
1074 static int rdt_pid_list_diff(pids_list_t *prev, pids_list_t *curr,
1075                              pids_list_t **added, size_t *added_num,
1076                              pids_list_t **removed, size_t *removed_num) {
1077   assert(prev || curr);
1078   assert(added);
1079   assert(removed);
1080
1081   if (NULL == prev) {
1082     /* append all PIDs from curr to added*/
1083     return pids_list_add_pids_list(added, curr, added_num);
1084   } else if (NULL == curr) {
1085     /* append all PIDs from prev to removed*/
1086     return pids_list_add_pids_list(removed, prev, removed_num);
1087   }
1088
1089   pids_list_t *item = prev;
1090   while (item != NULL) {
1091     if (0 == pids_list_contains_pid(curr, item->pid)) {
1092       pids_list_add_pid(removed, item->pid);
1093       ++(*removed_num);
1094     }
1095     item = item->next;
1096   }
1097
1098   item = curr;
1099   while (item != NULL) {
1100     if (0 == pids_list_contains_pid(prev, item->pid)) {
1101       pids_list_add_pid(added, item->pid);
1102       ++(*added_num);
1103     }
1104     item = item->next;
1105   }
1106
1107   return 0;
1108 }
1109
1110 static int rdt_refresh_ngroup(rdt_name_group_t *ngroup) {
1111   if (NULL == ngroup)
1112     return -1;
1113
1114   DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' process names group.",
1115         ngroup->desc);
1116
1117   proc_pids_t *pids_array_prev = ngroup->proc_pids_array;
1118   proc_pids_t *pids_array_curr = NULL;
1119
1120   int fetch_result =
1121       fetch_pids_for_procs(RDT_PROC_PATH, (const char **)ngroup->names,
1122                            ngroup->num_names, &pids_array_curr);
1123
1124   if (0 != fetch_result) {
1125     ERROR(RDT_PLUGIN ": rdt_refresh_ngroup: failed to fetch PIDs for \'%s\' "
1126                      "process names group.",
1127           ngroup->desc);
1128     return fetch_result;
1129   }
1130
1131   if (NULL == pids_array_prev) {
1132     /*no PIDs info yet, just save current one for next iteration*/
1133     ngroup->proc_pids_array = pids_array_curr;
1134     return 0;
1135   }
1136
1137   pids_list_t *added = NULL;
1138   size_t added_num = 0;
1139
1140   pids_list_t *removed = NULL;
1141   size_t removed_num = 0;
1142
1143   for (size_t i = 0; i < ngroup->num_names; ++i) {
1144     if (NULL == pids_array_prev[i].pids && NULL == pids_array_curr[i].pids)
1145       continue;
1146     rdt_pid_list_diff(pids_array_prev[i].pids, pids_array_curr[i].pids, &added,
1147                       &added_num, &removed, &removed_num);
1148   }
1149
1150   DEBUG(RDT_PLUGIN ": rdt_refresh_ngroup: \'%s\' process names group, added: "
1151                    "%u, removed: %u.",
1152         ngroup->desc, (unsigned)added_num, (unsigned)removed_num);
1153
1154   if (added_num != 0 || removed_num != 0) {
1155     ngroup->proc_pids_array = pids_array_curr;
1156
1157     /*call pqos add and remove functions here*/
1158   }
1159
1160   /*free prev PID lists, only if new was saved in ngroup struct*/
1161   if (pids_array_prev && pids_array_prev != ngroup->proc_pids_array) {
1162     for (size_t i = 0; i < ngroup->num_names; ++i) {
1163       if (NULL == pids_array_prev[i].pids)
1164         continue;
1165       pids_list_free(pids_array_prev[i].pids);
1166     }
1167
1168     sfree(pids_array_prev);
1169   }
1170
1171   return 0;
1172 }
1173 #endif /* LIBPQOS2 */
1174
1175 static int rdt_read(__attribute__((unused)) user_data_t *ud) {
1176   int ret;
1177
1178   if (g_rdt == NULL) {
1179     ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
1180     return -EINVAL;
1181   }
1182
1183   ret = pqos_mon_poll(&g_rdt->pcgroups[0], (unsigned)g_rdt->cores.num_cgroups);
1184   if (ret != PQOS_RETVAL_OK) {
1185     ERROR(RDT_PLUGIN ": Failed to poll monitoring data.");
1186     return -1;
1187   }
1188
1189 #ifdef LIBPQOS2
1190   for (size_t i = 0; i < g_rdt->num_ngroups; i++)
1191     rdt_refresh_ngroup(&g_rdt->ngroups[i]);
1192 #endif /* LIBPQOS2 */
1193
1194 #if COLLECT_DEBUG
1195   rdt_dump_data();
1196 #endif /* COLLECT_DEBUG */
1197
1198   for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
1199     core_group_t *cgroup = g_rdt->cores.cgroups + i;
1200     enum pqos_mon_event mbm_events =
1201         (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
1202          PQOS_MON_EVENT_RMEM_BW);
1203
1204     const struct pqos_event_values *pv = &g_rdt->pcgroups[i]->values;
1205
1206     /* Submit only monitored events data */
1207
1208     if (g_rdt->events[i] & PQOS_MON_EVENT_L3_OCCUP)
1209       rdt_submit_gauge(cgroup->desc, "bytes", "llc", pv->llc);
1210
1211     if (g_rdt->events[i] & PQOS_PERF_EVENT_IPC)
1212       rdt_submit_gauge(cgroup->desc, "ipc", NULL, pv->ipc);
1213
1214     if (g_rdt->events[i] & mbm_events) {
1215       rdt_submit_derive(cgroup->desc, "memory_bandwidth", "local",
1216                         pv->mbm_local_delta);
1217       rdt_submit_derive(cgroup->desc, "memory_bandwidth", "remote",
1218                         pv->mbm_remote_delta);
1219     }
1220   }
1221
1222   return 0;
1223 }
1224
1225 static int rdt_init(void) {
1226   int ret;
1227
1228   if (g_state == CONFIGURATION_ERROR)
1229     return -1;
1230
1231   ret = rdt_preinit();
1232   if (ret != 0)
1233     return ret;
1234
1235   /* Start monitoring */
1236   for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
1237     core_group_t *cg = g_rdt->cores.cgroups + i;
1238
1239     ret = pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
1240                          (void *)cg->desc, g_rdt->pcgroups[i]);
1241
1242     if (ret != PQOS_RETVAL_OK)
1243       ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
1244             cg->desc, ret);
1245   }
1246
1247   return 0;
1248 }
1249
1250 static int rdt_shutdown(void) {
1251   int ret;
1252
1253   DEBUG(RDT_PLUGIN ": rdt_shutdown.");
1254
1255   if (g_rdt == NULL)
1256     return 0;
1257
1258   /* Stop monitoring */
1259   for (size_t i = 0; i < g_rdt->cores.num_cgroups; i++) {
1260     pqos_mon_stop(g_rdt->pcgroups[i]);
1261   }
1262
1263   ret = pqos_fini();
1264   if (ret != PQOS_RETVAL_OK)
1265     ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
1266
1267   rdt_free_cgroups();
1268   sfree(g_rdt);
1269
1270   return 0;
1271 }
1272
1273 void module_register(void) {
1274   plugin_register_init(RDT_PLUGIN, rdt_init);
1275   plugin_register_complex_config(RDT_PLUGIN, rdt_config);
1276   plugin_register_complex_read(NULL, RDT_PLUGIN, rdt_read, 0, NULL);
1277   plugin_register_shutdown(RDT_PLUGIN, rdt_shutdown);
1278 }