Merge branch 'collectd-5.7' into collectd-5.8
[collectd.git] / src / intel_rdt.c
1 /**
2  * collectd - src/intel_rdt.c
3  *
4  * Copyright(c) 2016 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  **/
27
28 #include "common.h"
29 #include "collectd.h"
30
31 #include <pqos.h>
32
33 #define RDT_PLUGIN "intel_rdt"
34
35 #define RDT_MAX_SOCKETS 8
36 #define RDT_MAX_SOCKET_CORES 64
37 #define RDT_MAX_CORES (RDT_MAX_SOCKET_CORES * RDT_MAX_SOCKETS)
38
39 typedef enum {
40   UNKNOWN = 0,
41   CONFIGURATION_ERROR,
42 } rdt_config_status;
43
44 struct rdt_core_group_s {
45   char *desc;
46   size_t num_cores;
47   unsigned *cores;
48   enum pqos_mon_event events;
49 };
50 typedef struct rdt_core_group_s rdt_core_group_t;
51
52 struct rdt_ctx_s {
53   rdt_core_group_t cgroups[RDT_MAX_CORES];
54   struct pqos_mon_data *pgroups[RDT_MAX_CORES];
55   size_t num_groups;
56   const struct pqos_cpuinfo *pqos_cpu;
57   const struct pqos_cap *pqos_cap;
58   const struct pqos_capability *cap_mon;
59 };
60 typedef struct rdt_ctx_s rdt_ctx_t;
61
62 static rdt_ctx_t *g_rdt = NULL;
63
64 static rdt_config_status g_state = UNKNOWN;
65
66 static int isdup(const uint64_t *nums, size_t size, uint64_t val) {
67   for (size_t i = 0; i < size; i++)
68     if (nums[i] == val)
69       return 1;
70   return 0;
71 }
72
73 static int strtouint64(const char *s, uint64_t *n) {
74   char *endptr = NULL;
75
76   assert(s != NULL);
77   assert(n != NULL);
78
79   *n = strtoull(s, &endptr, 0);
80
81   if (!(*s != '\0' && *endptr == '\0')) {
82     DEBUG(RDT_PLUGIN ": Error converting '%s' to unsigned number.", s);
83     return -EINVAL;
84   }
85
86   return 0;
87 }
88
89 /*
90  * NAME
91  *   strlisttonums
92  *
93  * DESCRIPTION
94  *   Converts string of characters representing list of numbers into array of
95  *   numbers. Allowed formats are:
96  *     0,1,2,3
97  *     0-10,20-18
98  *     1,3,5-8,10,0x10-12
99  *
100  *   Numbers can be in decimal or hexadecimal format.
101  *
102  * PARAMETERS
103  *   `s'         String representing list of unsigned numbers.
104  *   `nums'      Array to put converted numeric values into.
105  *   `max'       Maximum number of elements that nums can accommodate.
106  *
107  * RETURN VALUE
108  *    Number of elements placed into nums.
109  */
110 static size_t strlisttonums(char *s, uint64_t *nums, size_t max) {
111   int ret;
112   size_t index = 0;
113   char *saveptr = NULL;
114
115   if (s == NULL || nums == NULL || max == 0)
116     return index;
117
118   for (;;) {
119     char *p = NULL;
120     char *token = NULL;
121
122     token = strtok_r(s, ",", &saveptr);
123     if (token == NULL)
124       break;
125
126     s = NULL;
127
128     while (isspace(*token))
129       token++;
130     if (*token == '\0')
131       continue;
132
133     p = strchr(token, '-');
134     if (p != NULL) {
135       uint64_t n, start, end;
136       *p = '\0';
137       ret = strtouint64(token, &start);
138       if (ret < 0)
139         return 0;
140       ret = strtouint64(p + 1, &end);
141       if (ret < 0)
142         return 0;
143       if (start > end) {
144         return 0;
145       }
146       for (n = start; n <= end; n++) {
147         if (!(isdup(nums, index, n))) {
148           nums[index] = n;
149           index++;
150         }
151         if (index >= max)
152           return index;
153       }
154     } else {
155       uint64_t val;
156
157       ret = strtouint64(token, &val);
158       if (ret < 0)
159         return 0;
160
161       if (!(isdup(nums, index, val))) {
162         nums[index] = val;
163         index++;
164       }
165       if (index >= max)
166         return index;
167     }
168   }
169
170   return index;
171 }
172
173 /*
174  * NAME
175  *   cgroup_cmp
176  *
177  * DESCRIPTION
178  *   Function to compare cores in 2 core groups.
179  *
180  * PARAMETERS
181  *   `cg_a'      Pointer to core group a.
182  *   `cg_b'      Pointer to core group b.
183  *
184  * RETURN VALUE
185  *    1 if both groups contain the same cores
186  *    0 if none of their cores match
187  *    -1 if some but not all cores match
188  */
189 static int cgroup_cmp(const rdt_core_group_t *cg_a,
190                       const rdt_core_group_t *cg_b) {
191   int found = 0;
192
193   assert(cg_a != NULL);
194   assert(cg_b != NULL);
195
196   const int sz_a = cg_a->num_cores;
197   const int sz_b = cg_b->num_cores;
198   const unsigned *tab_a = cg_a->cores;
199   const unsigned *tab_b = cg_b->cores;
200
201   for (int i = 0; i < sz_a; i++) {
202     for (int j = 0; j < sz_b; j++)
203       if (tab_a[i] == tab_b[j])
204         found++;
205   }
206   /* if no cores are the same */
207   if (!found)
208     return 0;
209   /* if group contains same cores */
210   if (sz_a == sz_b && sz_b == found)
211     return 1;
212   /* if not all cores are the same */
213   return -1;
214 }
215
216 static int cgroup_set(rdt_core_group_t *cg, char *desc, uint64_t *cores,
217                       size_t num_cores) {
218   assert(cg != NULL);
219   assert(desc != NULL);
220   assert(cores != NULL);
221   assert(num_cores > 0);
222
223   cg->cores = calloc(num_cores, sizeof(unsigned));
224   if (cg->cores == NULL) {
225     ERROR(RDT_PLUGIN ": Error allocating core group table");
226     return -ENOMEM;
227   }
228   cg->num_cores = num_cores;
229   cg->desc = strdup(desc);
230   if (cg->desc == NULL) {
231     ERROR(RDT_PLUGIN ": Error allocating core group description");
232     sfree(cg->cores);
233     return -ENOMEM;
234   }
235
236   for (size_t i = 0; i < num_cores; i++)
237     cg->cores[i] = (unsigned)cores[i];
238
239   return 0;
240 }
241
242 /*
243  * NAME
244  *   oconfig_to_cgroups
245  *
246  * DESCRIPTION
247  *   Function to set the descriptions and cores for each core group.
248  *   Takes a config option containing list of strings that are used to set
249  *   core group values.
250  *
251  * PARAMETERS
252  *   `item'        Config option containing core groups.
253  *   `groups'      Table of core groups to set values in.
254  *   `max_groups'  Maximum number of core groups allowed.
255  *
256  * RETURN VALUE
257  *   On success, the number of core groups set up. On error, appropriate
258  *   negative error value.
259  */
260 static int oconfig_to_cgroups(oconfig_item_t *item, rdt_core_group_t *groups,
261                               size_t max_groups) {
262   int index = 0;
263
264   assert(groups != NULL);
265   assert(max_groups > 0);
266   assert(item != NULL);
267
268   for (int j = 0; j < item->values_num; j++) {
269     int ret;
270     size_t n;
271     uint64_t cores[RDT_MAX_CORES] = {0};
272     char value[DATA_MAX_NAME_LEN];
273
274     if ((item->values[j].value.string == NULL) ||
275         (strlen(item->values[j].value.string) == 0))
276       continue;
277
278     sstrncpy(value, item->values[j].value.string, sizeof(value));
279
280     n = strlisttonums(value, cores, STATIC_ARRAY_SIZE(cores));
281     if (n == 0) {
282       ERROR(RDT_PLUGIN ": Error parsing core group (%s)",
283             item->values[j].value.string);
284       return -EINVAL;
285     }
286
287     /* set core group info */
288     ret = cgroup_set(&groups[index], item->values[j].value.string, cores, n);
289     if (ret < 0)
290       return ret;
291
292     index++;
293
294     if (index >= max_groups) {
295       WARNING(RDT_PLUGIN ": Too many core groups configured");
296       return index;
297     }
298   }
299
300   return index;
301 }
302
303 #if COLLECT_DEBUG
304 static void rdt_dump_cgroups(void) {
305   char cores[RDT_MAX_CORES * 4];
306
307   if (g_rdt == NULL)
308     return;
309
310   DEBUG(RDT_PLUGIN ": Core Groups Dump");
311   DEBUG(RDT_PLUGIN ":  groups count: %zu", g_rdt->num_groups);
312
313   for (int i = 0; i < g_rdt->num_groups; i++) {
314
315     memset(cores, 0, sizeof(cores));
316     for (int j = 0; j < g_rdt->cgroups[i].num_cores; j++) {
317       snprintf(cores + strlen(cores), sizeof(cores) - strlen(cores) - 1, " %d",
318                g_rdt->cgroups[i].cores[j]);
319     }
320
321     DEBUG(RDT_PLUGIN ":  group[%d]:", i);
322     DEBUG(RDT_PLUGIN ":    description: %s", g_rdt->cgroups[i].desc);
323     DEBUG(RDT_PLUGIN ":    cores: %s", cores);
324     DEBUG(RDT_PLUGIN ":    events: 0x%X", g_rdt->cgroups[i].events);
325   }
326
327   return;
328 }
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    * RMID - Resource Monitoring ID associated with the monitored group
340    * LLC - last level cache occupancy
341    * MBL - local memory bandwidth
342    * MBR - remote memory bandwidth
343    */
344   DEBUG("  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
345   for (int i = 0; i < g_rdt->num_groups; i++) {
346
347     const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
348
349     double llc = bytes_to_kb(pv->llc);
350     double mbr = bytes_to_mb(pv->mbm_remote_delta);
351     double mbl = bytes_to_mb(pv->mbm_local_delta);
352
353     DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cgroups[i].desc,
354           g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
355   }
356 }
357 #endif /* COLLECT_DEBUG */
358
359 static void rdt_free_cgroups(void) {
360   for (int i = 0; i < RDT_MAX_CORES; i++) {
361     sfree(g_rdt->cgroups[i].desc);
362
363     sfree(g_rdt->cgroups[i].cores);
364     g_rdt->cgroups[i].num_cores = 0;
365
366     sfree(g_rdt->pgroups[i]);
367   }
368 }
369
370 static int rdt_default_cgroups(void) {
371   int ret;
372
373   /* configure each core in separate group */
374   for (unsigned i = 0; i < g_rdt->pqos_cpu->num_cores; i++) {
375     char desc[DATA_MAX_NAME_LEN];
376     uint64_t core = i;
377
378     snprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore);
379
380     /* set core group info */
381     ret = cgroup_set(&g_rdt->cgroups[i], desc, &core, 1);
382     if (ret < 0)
383       return ret;
384   }
385
386   return g_rdt->pqos_cpu->num_cores;
387 }
388
389 static int rdt_is_core_id_valid(int core_id) {
390
391   for (int i = 0; i < g_rdt->pqos_cpu->num_cores; i++)
392     if (core_id == g_rdt->pqos_cpu->cores[i].lcore)
393       return 1;
394
395   return 0;
396 }
397
398 static int rdt_config_cgroups(oconfig_item_t *item) {
399   int n = 0;
400   enum pqos_mon_event events = 0;
401
402   if (item == NULL) {
403     DEBUG(RDT_PLUGIN ": cgroups_config: Invalid argument.");
404     return -EINVAL;
405   }
406
407   DEBUG(RDT_PLUGIN ": Core groups [%d]:", item->values_num);
408   for (int j = 0; j < item->values_num; j++) {
409     if (item->values[j].type != OCONFIG_TYPE_STRING) {
410       ERROR(RDT_PLUGIN ": given core group value is not a string [idx=%d]", j);
411       return -EINVAL;
412     }
413     DEBUG(RDT_PLUGIN ":  [%d]: %s", j, item->values[j].value.string);
414   }
415
416   n = oconfig_to_cgroups(item, g_rdt->cgroups, g_rdt->pqos_cpu->num_cores);
417   if (n < 0) {
418     rdt_free_cgroups();
419     ERROR(RDT_PLUGIN ": Error parsing core groups configuration.");
420     return -EINVAL;
421   }
422
423   /* validate configured core id values */
424   for (int group_idx = 0; group_idx < n; group_idx++) {
425     for (int core_idx = 0; core_idx < g_rdt->cgroups[group_idx].num_cores;
426          core_idx++) {
427       if (!rdt_is_core_id_valid(g_rdt->cgroups[group_idx].cores[core_idx])) {
428         ERROR(RDT_PLUGIN ": Core group '%s' contains invalid core id '%d'",
429                 g_rdt->cgroups[group_idx].desc,
430                 (int)g_rdt->cgroups[group_idx].cores[core_idx]);
431         rdt_free_cgroups();
432         return -EINVAL;
433       }
434     }
435   }
436
437   if (n == 0) {
438     /* create default core groups if "Cores" config option is empty */
439     n = rdt_default_cgroups();
440     if (n < 0) {
441       rdt_free_cgroups();
442       ERROR(RDT_PLUGIN ": Error creating default core groups configuration.");
443       return n;
444     }
445     INFO(RDT_PLUGIN
446          ": No core groups configured. Default core groups created.");
447   }
448
449   /* Get all available events on this platform */
450   for (int i = 0; i < g_rdt->cap_mon->u.mon->num_events; i++)
451     events |= g_rdt->cap_mon->u.mon->events[i].type;
452
453   events &= ~(PQOS_PERF_EVENT_LLC_MISS);
454
455   DEBUG(RDT_PLUGIN ": Number of cores in the system: %u",
456         g_rdt->pqos_cpu->num_cores);
457   DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
458
459   g_rdt->num_groups = n;
460   for (int i = 0; i < n; i++) {
461     for (int j = 0; j < i; j++) {
462       int found = 0;
463       found = cgroup_cmp(&g_rdt->cgroups[j], &g_rdt->cgroups[i]);
464       if (found != 0) {
465         rdt_free_cgroups();
466         ERROR(RDT_PLUGIN ": Cannot monitor same cores in different groups.");
467         return -EINVAL;
468       }
469     }
470
471     g_rdt->cgroups[i].events = events;
472     g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i]));
473     if (g_rdt->pgroups[i] == NULL) {
474       rdt_free_cgroups();
475       ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
476       return -ENOMEM;
477     }
478   }
479
480   return 0;
481 }
482
483 static void rdt_pqos_log(void *context, const size_t size, const char *msg) {
484   DEBUG(RDT_PLUGIN ": %s", msg);
485 }
486
487 static int rdt_preinit(void) {
488   int ret;
489
490   if (g_rdt != NULL) {
491     /* already initialized if config callback was called before init callback */
492     return 0;
493   }
494
495   g_rdt = calloc(1, sizeof(*g_rdt));
496   if (g_rdt == NULL) {
497     ERROR(RDT_PLUGIN ": Failed to allocate memory for rdt context.");
498     return -ENOMEM;
499   }
500
501   struct pqos_config pqos = {.fd_log = -1,
502                              .callback_log = rdt_pqos_log,
503                              .context_log = NULL,
504                              .verbose = 0};
505
506   ret = pqos_init(&pqos);
507   if (ret != PQOS_RETVAL_OK) {
508     ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
509     goto rdt_preinit_error1;
510   }
511
512   ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
513   if (ret != PQOS_RETVAL_OK) {
514     ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
515     goto rdt_preinit_error2;
516   }
517
518   ret = pqos_cap_get_type(g_rdt->pqos_cap, PQOS_CAP_TYPE_MON, &g_rdt->cap_mon);
519   if (ret == PQOS_RETVAL_PARAM) {
520     ERROR(RDT_PLUGIN ": Error retrieving monitoring capabilities.");
521     goto rdt_preinit_error2;
522   }
523
524   if (g_rdt->cap_mon == NULL) {
525     ERROR(
526         RDT_PLUGIN
527         ": Monitoring capability not detected. Nothing to do for the plugin.");
528     goto rdt_preinit_error2;
529   }
530
531   /* Reset pqos monitoring groups registers */
532   pqos_mon_reset();
533
534   return 0;
535
536 rdt_preinit_error2:
537   pqos_fini();
538
539 rdt_preinit_error1:
540
541   sfree(g_rdt);
542
543   return -1;
544 }
545
546 static int rdt_config(oconfig_item_t *ci) {
547   if (rdt_preinit() != 0) {
548     g_state = CONFIGURATION_ERROR;
549     /* if we return -1 at this point collectd
550       reports a failure in configuration and
551       aborts
552     */
553     return (0);
554   }
555
556   for (int i = 0; i < ci->children_num; i++) {
557     oconfig_item_t *child = ci->children + i;
558
559     if (strcasecmp("Cores", child->key) == 0) {
560       if (rdt_config_cgroups(child) != 0) {
561         g_state = CONFIGURATION_ERROR;
562         /* if we return -1 at this point collectd
563            reports a failure in configuration and
564            aborts
565          */
566         return (0);
567       }
568
569 #if COLLECT_DEBUG
570       rdt_dump_cgroups();
571 #endif /* COLLECT_DEBUG */
572     } else {
573       ERROR(RDT_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
574     }
575   }
576
577   return 0;
578 }
579
580 static void rdt_submit_derive(char *cgroup, char *type, char *type_instance,
581                               derive_t value) {
582   value_list_t vl = VALUE_LIST_INIT;
583
584   vl.values = &(value_t){.derive = value};
585   vl.values_len = 1;
586
587   sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
588   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
589   sstrncpy(vl.type, type, sizeof(vl.type));
590   if (type_instance)
591     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
592
593   plugin_dispatch_values(&vl);
594 }
595
596 static void rdt_submit_gauge(char *cgroup, char *type, char *type_instance,
597                              gauge_t value) {
598   value_list_t vl = VALUE_LIST_INIT;
599
600   vl.values = &(value_t){.gauge = value};
601   vl.values_len = 1;
602
603   sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
604   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
605   sstrncpy(vl.type, type, sizeof(vl.type));
606   if (type_instance)
607     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
608
609   plugin_dispatch_values(&vl);
610 }
611
612 static int rdt_read(__attribute__((unused)) user_data_t *ud) {
613   int ret;
614
615   if (g_rdt == NULL) {
616     ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
617     return -EINVAL;
618   }
619
620   ret = pqos_mon_poll(&g_rdt->pgroups[0], (unsigned)g_rdt->num_groups);
621   if (ret != PQOS_RETVAL_OK) {
622     ERROR(RDT_PLUGIN ": Failed to poll monitoring data.");
623     return -1;
624   }
625
626 #if COLLECT_DEBUG
627   rdt_dump_data();
628 #endif /* COLLECT_DEBUG */
629
630   for (int i = 0; i < g_rdt->num_groups; i++) {
631     enum pqos_mon_event mbm_events =
632         (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
633          PQOS_MON_EVENT_RMEM_BW);
634
635     const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
636
637     /* Submit only monitored events data */
638
639     if (g_rdt->cgroups[i].events & PQOS_MON_EVENT_L3_OCCUP)
640       rdt_submit_gauge(g_rdt->cgroups[i].desc, "bytes", "llc", pv->llc);
641
642     if (g_rdt->cgroups[i].events & PQOS_PERF_EVENT_IPC)
643       rdt_submit_gauge(g_rdt->cgroups[i].desc, "ipc", NULL, pv->ipc);
644
645     if (g_rdt->cgroups[i].events & mbm_events) {
646       rdt_submit_derive(g_rdt->cgroups[i].desc, "memory_bandwidth", "local",
647                         pv->mbm_local_delta);
648       rdt_submit_derive(g_rdt->cgroups[i].desc, "memory_bandwidth", "remote",
649                         pv->mbm_remote_delta);
650     }
651   }
652
653   return 0;
654 }
655
656 static int rdt_init(void) {
657   int ret;
658
659   if (g_state == CONFIGURATION_ERROR)
660     return -1;
661
662   ret = rdt_preinit();
663   if (ret != 0)
664     return ret;
665
666   /* Start monitoring */
667   for (int i = 0; i < g_rdt->num_groups; i++) {
668     rdt_core_group_t *cg = &g_rdt->cgroups[i];
669
670     ret = pqos_mon_start(cg->num_cores, cg->cores, cg->events, (void *)cg->desc,
671                          g_rdt->pgroups[i]);
672
673     if (ret != PQOS_RETVAL_OK)
674       ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
675             cg->desc, ret);
676   }
677
678   return 0;
679 }
680
681 static int rdt_shutdown(void) {
682   int ret;
683
684   DEBUG(RDT_PLUGIN ": rdt_shutdown.");
685
686   if (g_rdt == NULL)
687     return 0;
688
689   /* Stop monitoring */
690   for (int i = 0; i < g_rdt->num_groups; i++) {
691     pqos_mon_stop(g_rdt->pgroups[i]);
692   }
693
694   ret = pqos_fini();
695   if (ret != PQOS_RETVAL_OK)
696     ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
697
698   rdt_free_cgroups();
699   sfree(g_rdt);
700
701   return 0;
702 }
703
704 void module_register(void) {
705   plugin_register_init(RDT_PLUGIN, rdt_init);
706   plugin_register_complex_config(RDT_PLUGIN, rdt_config);
707   plugin_register_complex_read(NULL, RDT_PLUGIN, rdt_read, 0, NULL);
708   plugin_register_shutdown(RDT_PLUGIN, rdt_shutdown);
709 }