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