446dc6559d6d722b3bef7d7fd5e9ac60f2864267
[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  **/
28
29 #include "collectd.h"
30 #include "utils/common/common.h"
31 #include "utils/config_cores/config_cores.h"
32 #include <pqos.h>
33
34 #define RDT_PLUGIN "intel_rdt"
35
36 #define RDT_MAX_SOCKETS 8
37 #define RDT_MAX_SOCKET_CORES 64
38 #define RDT_MAX_CORES (RDT_MAX_SOCKET_CORES * RDT_MAX_SOCKETS)
39 /*
40  * Process name inside comm file is limited to 16 chars.
41  * More info here: http://man7.org/linux/man-pages/man5/proc.5.html
42  */
43 #define RDT_MAX_PROC_COMM_LENGTH 16
44
45 typedef enum {
46   UNKNOWN = 0,
47   CONFIGURATION_ERROR,
48 } rdt_config_status;
49
50 struct rdt_ctx_s {
51   core_groups_list_t cores;
52   enum pqos_mon_event events[RDT_MAX_CORES];
53   struct pqos_mon_data *pgroups[RDT_MAX_CORES];
54   size_t num_groups;
55   const struct pqos_cpuinfo *pqos_cpu;
56   const struct pqos_cap *pqos_cap;
57   const struct pqos_capability *cap_mon;
58 };
59 typedef struct rdt_ctx_s rdt_ctx_t;
60
61 static rdt_ctx_t *g_rdt;
62
63 static rdt_config_status g_state = UNKNOWN;
64
65 #if COLLECT_DEBUG
66 static void rdt_dump_cgroups(void) {
67   char cores[RDT_MAX_CORES * 4];
68
69   if (g_rdt == NULL)
70     return;
71
72   DEBUG(RDT_PLUGIN ": Core Groups Dump");
73   DEBUG(RDT_PLUGIN ":  groups count: %" PRIsz, g_rdt->num_groups);
74
75   for (size_t i = 0; i < g_rdt->num_groups; i++) {
76     core_group_t *cgroup = g_rdt->cores.cgroups + i;
77
78     memset(cores, 0, sizeof(cores));
79     for (size_t j = 0; j < cgroup->num_cores; j++) {
80       snprintf(cores + strlen(cores), sizeof(cores) - strlen(cores) - 1, " %d",
81                cgroup->cores[j]);
82     }
83
84     DEBUG(RDT_PLUGIN ":  group[%zu]:", i);
85     DEBUG(RDT_PLUGIN ":    description: %s", cgroup->desc);
86     DEBUG(RDT_PLUGIN ":    cores: %s", cores);
87     DEBUG(RDT_PLUGIN ":    events: 0x%X", g_rdt->events[i]);
88   }
89
90   return;
91 }
92
93 static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
94
95 static inline double bytes_to_mb(const double bytes) {
96   return bytes / (1024.0 * 1024.0);
97 }
98
99 static void rdt_dump_data(void) {
100   /*
101    * CORE - monitored group of cores
102    * RMID - Resource Monitoring ID associated with the monitored group
103    * LLC - last level cache occupancy
104    * MBL - local memory bandwidth
105    * MBR - remote memory bandwidth
106    */
107   DEBUG("  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
108   for (size_t i = 0; i < g_rdt->num_groups; i++) {
109     const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
110
111     double llc = bytes_to_kb(pv->llc);
112     double mbr = bytes_to_mb(pv->mbm_remote_delta);
113     double mbl = bytes_to_mb(pv->mbm_local_delta);
114
115     DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cores.cgroups[i].desc,
116           g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
117   }
118 }
119 #endif /* COLLECT_DEBUG */
120
121 static void rdt_free_cgroups(void) {
122   config_cores_cleanup(&g_rdt->cores);
123   for (int i = 0; i < RDT_MAX_CORES; i++) {
124     sfree(g_rdt->pgroups[i]);
125   }
126 }
127
128 static int rdt_default_cgroups(void) {
129   unsigned num_cores = g_rdt->pqos_cpu->num_cores;
130
131   g_rdt->cores.cgroups = calloc(num_cores, sizeof(*g_rdt->cores.cgroups));
132   if (g_rdt->cores.cgroups == NULL) {
133     ERROR(RDT_PLUGIN ": Error allocating core groups array");
134     return -ENOMEM;
135   }
136   g_rdt->cores.num_cgroups = num_cores;
137
138   /* configure each core in separate group */
139   for (unsigned i = 0; i < num_cores; i++) {
140     core_group_t *cgroup = g_rdt->cores.cgroups + i;
141     char desc[DATA_MAX_NAME_LEN];
142
143     /* set core group info */
144     cgroup->cores = calloc(1, sizeof(*cgroup->cores));
145     if (cgroup->cores == NULL) {
146       ERROR(RDT_PLUGIN ": Error allocating cores array");
147       rdt_free_cgroups();
148       return -ENOMEM;
149     }
150     cgroup->num_cores = 1;
151     cgroup->cores[0] = i;
152
153     snprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore);
154     cgroup->desc = strdup(desc);
155     if (cgroup->desc == NULL) {
156       ERROR(RDT_PLUGIN ": Error allocating core group description");
157       rdt_free_cgroups();
158       return -ENOMEM;
159     }
160   }
161
162   return num_cores;
163 }
164
165 static int rdt_is_core_id_valid(unsigned int core_id) {
166
167   for (unsigned int i = 0; i < g_rdt->pqos_cpu->num_cores; i++)
168     if (core_id == g_rdt->pqos_cpu->cores[i].lcore)
169       return 1;
170
171   return 0;
172 }
173
174 static int rdt_config_cgroups(oconfig_item_t *item) {
175   size_t n = 0;
176   enum pqos_mon_event events = 0;
177
178   if (config_cores_parse(item, &g_rdt->cores) < 0) {
179     rdt_free_cgroups();
180     ERROR(RDT_PLUGIN ": Error parsing core groups configuration.");
181     return -EINVAL;
182   }
183   n = g_rdt->cores.num_cgroups;
184
185   /* validate configured core id values */
186   for (size_t group_idx = 0; group_idx < n; group_idx++) {
187     core_group_t *cgroup = g_rdt->cores.cgroups + group_idx;
188     for (size_t core_idx = 0; core_idx < cgroup->num_cores; core_idx++) {
189       if (!rdt_is_core_id_valid(cgroup->cores[core_idx])) {
190         ERROR(RDT_PLUGIN ": Core group '%s' contains invalid core id '%u'",
191               cgroup->desc, cgroup->cores[core_idx]);
192         rdt_free_cgroups();
193         return -EINVAL;
194       }
195     }
196   }
197
198   if (n == 0) {
199     /* create default core groups if "Cores" config option is empty */
200     int ret = rdt_default_cgroups();
201     if (ret < 0) {
202       rdt_free_cgroups();
203       ERROR(RDT_PLUGIN ": Error creating default core groups configuration.");
204       return ret;
205     }
206     n = (size_t)ret;
207     INFO(RDT_PLUGIN
208          ": No core groups configured. Default core groups created.");
209   }
210
211   /* Get all available events on this platform */
212   for (unsigned int i = 0; i < g_rdt->cap_mon->u.mon->num_events; i++)
213     events |= g_rdt->cap_mon->u.mon->events[i].type;
214
215   events &= ~(PQOS_PERF_EVENT_LLC_MISS);
216
217   DEBUG(RDT_PLUGIN ": Number of cores in the system: %u",
218         g_rdt->pqos_cpu->num_cores);
219   DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
220
221   g_rdt->num_groups = n;
222   for (size_t i = 0; i < n; i++) {
223     for (size_t j = 0; j < i; j++) {
224       int found = 0;
225       found = config_cores_cmp_cgroups(&g_rdt->cores.cgroups[j],
226                                        &g_rdt->cores.cgroups[i]);
227       if (found != 0) {
228         rdt_free_cgroups();
229         ERROR(RDT_PLUGIN ": Cannot monitor same cores in different groups.");
230         return -EINVAL;
231       }
232     }
233
234     g_rdt->events[i] = events;
235     g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i]));
236     if (g_rdt->pgroups[i] == NULL) {
237       rdt_free_cgroups();
238       ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
239       return -ENOMEM;
240     }
241   }
242
243   return 0;
244 }
245
246 /* Helper typedef for process name array
247  * Extra 1 char is added for string null termination.
248  */
249 typedef char proc_comm_t[RDT_MAX_PROC_COMM_LENGTH + 1];
250
251 /* Linked one-way list of pids. */
252 typedef struct pids_list_s {
253   pid_t pid;
254   struct pids_list_s *next;
255 } pids_list_t;
256
257 /* Holds process name and list of pids assigned to that name */
258 typedef struct proc_pids_s {
259   proc_comm_t proccess_name;
260   pids_list_t *pids;
261 } proc_pids_t;
262
263 /*
264  * NAME
265  *   pids_list_add_pid
266  *
267  * DESCRIPTION
268  *   Adds pid at the end of the pids list.
269  *   Allocates memory for new pid element, it is up to user to free it.
270  *
271  * PARAMETERS
272  *   `list'     Head of target pids_list.
273  *   `pid'      Pid to be added.
274  *
275  * RETURN VALUE
276  *   On success, returns 0.
277  *   -1 on memory allocation error.
278  */
279 static int pids_list_add_pid(pids_list_t **list, const pid_t pid) {
280   pids_list_t *new_element = calloc(1, sizeof(*new_element));
281
282   if (new_element == NULL) {
283     ERROR(RDT_PLUGIN ": Alloc error\n");
284     return -1;
285   }
286   new_element->pid = pid;
287   new_element->next = NULL;
288
289   pids_list_t **current = list;
290   while (*current != NULL) {
291     current = &((*current)->next);
292   }
293   *current = new_element;
294   return 0;
295 }
296
297 /*
298  * NAME
299  *   read_proc_name
300  *
301  * DESCRIPTION
302  *   Reads process name from given pid directory.
303  *   Strips new-line character (\n).
304  *
305  * PARAMETERS
306  *   `procfs_path` Path to systems proc directory (e.g. /proc)
307  *   `pid_entry'   Dirent for PID directory
308  *   `name'        Output buffer for process name, recommended proc_comm.
309  *   `out_size'    Output buffer size, recommended sizeof(proc_comm)
310  *
311  * RETURN VALUE
312  *   On success, the number of read bytes (includes stripped \n).
313  *   -1 on file open error
314  */
315 static int read_proc_name(const char *procfs_path,
316                           const struct dirent *pid_entry, char *name,
317                           const size_t out_size) {
318   assert(procfs_path);
319   assert(pid_entry);
320   assert(name);
321   assert(out_size);
322   memset(name, 0, out_size);
323
324   const char *comm_file_name = "comm";
325
326   char *path = ssnprintf_alloc("%s/%s/%s", procfs_path, pid_entry->d_name,
327                                comm_file_name);
328
329   FILE *f = fopen(path, "r");
330   if (f == NULL) {
331     ERROR(RDT_PLUGIN ": Failed to open comm file, error: %d\n", errno);
332     sfree(path);
333     return -1;
334   }
335   size_t read_length = fread(name, sizeof(char), out_size, f);
336   fclose(f);
337   sfree(path);
338   /* strip new line ending */
339   char *newline = strchr(name, '\n');
340   if (newline) {
341     *newline = '\0';
342   }
343
344   return read_length;
345 }
346
347 /*
348  * NAME
349  *   get_pid_number
350  *
351  * DESCRIPTION
352  *   Gets pid number for given /proc/pid directory entry or
353  *   returns error if input directory does not hold PID information.
354  *
355  * PARAMETERS
356  *   `entry'    Dirent for PID directory
357  *   `pid'      PID number to be filled
358  *
359  * RETURN VALUE
360  *   0 on success. Negative number on error:
361  *   -1: given entry is not a directory
362  *   -2: PID conversion error
363  */
364 static int get_pid_number(struct dirent *entry, pid_t *pid) {
365   char *tmp_end; /* used for strtoul error check*/
366
367   if (pid == NULL || entry == NULL)
368     return -1;
369
370   if (entry->d_type != DT_DIR)
371     return -1;
372
373   /* trying to get pid number from directory name*/
374   *pid = strtoul(entry->d_name, &tmp_end, 10);
375   if (*tmp_end != '\0') {
376     return -2; /* conversion failed, not proc-pid */
377   }
378   /* all checks passed, marking as success */
379   return 0;
380 }
381
382 /*
383  * NAME
384  *   fetch_pids_for_procs
385  *
386  * DESCRIPTION
387  *   Finds PIDs matching given process's names.
388  *   Searches all PID directories in /proc fs and
389  *   allocates memory for proc_pids structs, it is up to user to free it.
390  *   Output array will have same element count as input array.
391  *
392  * PARAMETERS
393  *   `procfs_path'      Path to systems proc directory (e.g. /proc)
394  *   `procs'            Array of null-terminated strings with
395  *                      process' names to search for
396  *   `procs_size'       procs array element count
397  *   `proc_pids_array'  Address of pointer, under which new
398  *                      array of proc_pids will be allocated. Must be NULL.
399  *
400  * RETURN VALUE
401  *   0 on success. Negative number on error:
402  *   -1: could not open /proc dir
403  */
404 __attribute__((unused)) /* TODO: remove this attribute when PID monitoring is
405                            implemented */
406 static int
407 fetch_pids_for_procs(const char *procfs_path, const char **procs_names_array,
408                      const size_t procs_names_array_size,
409                      proc_pids_t **proc_pids_array) {
410   assert(procfs_path);
411   assert(procs_names_array);
412   assert(procs_names_array_size);
413   assert(proc_pids_array);
414   assert(NULL == *proc_pids_array);
415
416   DIR *proc_dir = opendir(procfs_path);
417   if (proc_dir == NULL) {
418     ERROR(RDT_PLUGIN ": Could not open %s directory, error: %d", procfs_path,
419           errno);
420     return -1;
421   }
422
423   /* Copy procs names to output array. Initialize pids list with NULL value. */
424   (*proc_pids_array) =
425       calloc(procs_names_array_size, sizeof(**proc_pids_array));
426   for (size_t i = 0; i < procs_names_array_size; ++i) {
427     sstrncpy((*proc_pids_array)[i].proccess_name, procs_names_array[i],
428              STATIC_ARRAY_SIZE((*proc_pids_array)[i].proccess_name));
429     (*proc_pids_array)[i].pids = NULL;
430   }
431
432   /* Go through procfs and find PIDS and their comms */
433   struct dirent *entry;
434   while ((entry = readdir(proc_dir)) != NULL) {
435
436     pid_t pid;
437     int pid_conversion = get_pid_number(entry, &pid);
438     if (pid_conversion < 0)
439       continue;
440
441     proc_comm_t comm;
442     int read_result =
443         read_proc_name(procfs_path, entry, comm, sizeof(proc_comm_t));
444     if (read_result <= 0) {
445       ERROR(RDT_PLUGIN ": Comm file skipped. Read result: %d", read_result);
446       continue;
447     }
448
449     /* Try to find comm in input procs array (proc_pids_array has same names) */
450     for (size_t i = 0; i < procs_names_array_size; ++i) {
451       if (0 == strncmp(comm, (*proc_pids_array)[i].proccess_name,
452                        STATIC_ARRAY_SIZE(comm)))
453         pids_list_add_pid(&((*proc_pids_array)[i].pids), pid);
454     }
455   }
456
457   int close_result = closedir(proc_dir);
458   if (0 != close_result) {
459     ERROR(RDT_PLUGIN ": failed to close %s directory, error: %d", procfs_path,
460           errno);
461     return -1;
462   }
463   return 0;
464 }
465
466 static void rdt_pqos_log(void *context, const size_t size, const char *msg) {
467   DEBUG(RDT_PLUGIN ": %s", msg);
468 }
469
470 static int rdt_preinit(void) {
471   int ret;
472
473   if (g_rdt != NULL) {
474     /* already initialized if config callback was called before init callback */
475     return 0;
476   }
477
478   g_rdt = calloc(1, sizeof(*g_rdt));
479   if (g_rdt == NULL) {
480     ERROR(RDT_PLUGIN ": Failed to allocate memory for rdt context.");
481     return -ENOMEM;
482   }
483
484   struct pqos_config pqos = {.fd_log = -1,
485                              .callback_log = rdt_pqos_log,
486                              .context_log = NULL,
487                              .verbose = 0};
488
489   ret = pqos_init(&pqos);
490   if (ret != PQOS_RETVAL_OK) {
491     ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
492     goto rdt_preinit_error1;
493   }
494
495   ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
496   if (ret != PQOS_RETVAL_OK) {
497     ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
498     goto rdt_preinit_error2;
499   }
500
501   ret = pqos_cap_get_type(g_rdt->pqos_cap, PQOS_CAP_TYPE_MON, &g_rdt->cap_mon);
502   if (ret == PQOS_RETVAL_PARAM) {
503     ERROR(RDT_PLUGIN ": Error retrieving monitoring capabilities.");
504     goto rdt_preinit_error2;
505   }
506
507   if (g_rdt->cap_mon == NULL) {
508     ERROR(
509         RDT_PLUGIN
510         ": Monitoring capability not detected. Nothing to do for the plugin.");
511     goto rdt_preinit_error2;
512   }
513
514   /* Reset pqos monitoring groups registers */
515   pqos_mon_reset();
516
517   return 0;
518
519 rdt_preinit_error2:
520   pqos_fini();
521
522 rdt_preinit_error1:
523
524   sfree(g_rdt);
525
526   return -1;
527 }
528
529 static int rdt_config(oconfig_item_t *ci) {
530   if (rdt_preinit() != 0) {
531     g_state = CONFIGURATION_ERROR;
532     /* if we return -1 at this point collectd
533       reports a failure in configuration and
534       aborts
535     */
536     return (0);
537   }
538
539   for (int i = 0; i < ci->children_num; i++) {
540     oconfig_item_t *child = ci->children + i;
541
542     if (strcasecmp("Cores", child->key) == 0) {
543       if (rdt_config_cgroups(child) != 0) {
544         g_state = CONFIGURATION_ERROR;
545         /* if we return -1 at this point collectd
546            reports a failure in configuration and
547            aborts
548          */
549         return (0);
550       }
551
552 #if COLLECT_DEBUG
553       rdt_dump_cgroups();
554 #endif /* COLLECT_DEBUG */
555     } else {
556       ERROR(RDT_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
557     }
558   }
559
560   return 0;
561 }
562
563 static void rdt_submit_derive(const char *cgroup, const char *type,
564                               const char *type_instance, derive_t value) {
565   value_list_t vl = VALUE_LIST_INIT;
566
567   vl.values = &(value_t){.derive = value};
568   vl.values_len = 1;
569
570   sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
571   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
572   sstrncpy(vl.type, type, sizeof(vl.type));
573   if (type_instance)
574     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
575
576   plugin_dispatch_values(&vl);
577 }
578
579 static void rdt_submit_gauge(const char *cgroup, const char *type,
580                              const char *type_instance, gauge_t value) {
581   value_list_t vl = VALUE_LIST_INIT;
582
583   vl.values = &(value_t){.gauge = value};
584   vl.values_len = 1;
585
586   sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
587   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
588   sstrncpy(vl.type, type, sizeof(vl.type));
589   if (type_instance)
590     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
591
592   plugin_dispatch_values(&vl);
593 }
594
595 static int rdt_read(__attribute__((unused)) user_data_t *ud) {
596   int ret;
597
598   if (g_rdt == NULL) {
599     ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
600     return -EINVAL;
601   }
602
603   ret = pqos_mon_poll(&g_rdt->pgroups[0], (unsigned)g_rdt->num_groups);
604   if (ret != PQOS_RETVAL_OK) {
605     ERROR(RDT_PLUGIN ": Failed to poll monitoring data.");
606     return -1;
607   }
608
609 #if COLLECT_DEBUG
610   rdt_dump_data();
611 #endif /* COLLECT_DEBUG */
612
613   for (size_t i = 0; i < g_rdt->num_groups; i++) {
614     core_group_t *cgroup = g_rdt->cores.cgroups + i;
615
616     enum pqos_mon_event mbm_events =
617         (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
618          PQOS_MON_EVENT_RMEM_BW);
619
620     const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
621
622     /* Submit only monitored events data */
623
624     if (g_rdt->events[i] & PQOS_MON_EVENT_L3_OCCUP)
625       rdt_submit_gauge(cgroup->desc, "bytes", "llc", pv->llc);
626
627     if (g_rdt->events[i] & PQOS_PERF_EVENT_IPC)
628       rdt_submit_gauge(cgroup->desc, "ipc", NULL, pv->ipc);
629
630     if (g_rdt->events[i] & mbm_events) {
631       rdt_submit_derive(cgroup->desc, "memory_bandwidth", "local",
632                         pv->mbm_local_delta);
633       rdt_submit_derive(cgroup->desc, "memory_bandwidth", "remote",
634                         pv->mbm_remote_delta);
635     }
636   }
637
638   return 0;
639 }
640
641 static int rdt_init(void) {
642   int ret;
643
644   if (g_state == CONFIGURATION_ERROR)
645     return -1;
646
647   ret = rdt_preinit();
648   if (ret != 0)
649     return ret;
650
651   /* Start monitoring */
652   for (size_t i = 0; i < g_rdt->num_groups; i++) {
653     core_group_t *cg = g_rdt->cores.cgroups + i;
654
655     ret = pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
656                          (void *)cg->desc, g_rdt->pgroups[i]);
657
658     if (ret != PQOS_RETVAL_OK)
659       ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
660             cg->desc, ret);
661   }
662
663   return 0;
664 }
665
666 static int rdt_shutdown(void) {
667   int ret;
668
669   DEBUG(RDT_PLUGIN ": rdt_shutdown.");
670
671   if (g_rdt == NULL)
672     return 0;
673
674   /* Stop monitoring */
675   for (size_t i = 0; i < g_rdt->num_groups; i++) {
676     pqos_mon_stop(g_rdt->pgroups[i]);
677   }
678
679   ret = pqos_fini();
680   if (ret != PQOS_RETVAL_OK)
681     ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
682
683   rdt_free_cgroups();
684   sfree(g_rdt);
685
686   return 0;
687 }
688
689 void module_register(void) {
690   plugin_register_init(RDT_PLUGIN, rdt_init);
691   plugin_register_complex_config(RDT_PLUGIN, rdt_config);
692   plugin_register_complex_read(NULL, RDT_PLUGIN, rdt_read, 0, NULL);
693   plugin_register_shutdown(RDT_PLUGIN, rdt_shutdown);
694 }