Merge pull request #2613 from elfiesmelfie/update_dpdk_note
[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  *   `max_core'    Maximum allowed core value.
256  *
257  * RETURN VALUE
258  *   On success, the number of core groups set up. On error, appropriate
259  *   negative error value.
260  */
261 static int oconfig_to_cgroups(oconfig_item_t *item, rdt_core_group_t *groups,
262                               size_t max_groups, uint64_t max_core) {
263   int index = 0;
264
265   assert(groups != NULL);
266   assert(max_groups > 0);
267   assert(item != NULL);
268
269   for (int j = 0; j < item->values_num; j++) {
270     int ret;
271     size_t n;
272     uint64_t cores[RDT_MAX_CORES] = {0};
273     char value[DATA_MAX_NAME_LEN];
274
275     if ((item->values[j].value.string == NULL) ||
276         (strlen(item->values[j].value.string) == 0))
277       continue;
278
279     sstrncpy(value, item->values[j].value.string, sizeof(value));
280
281     n = strlisttonums(value, cores, STATIC_ARRAY_SIZE(cores));
282     if (n == 0) {
283       ERROR(RDT_PLUGIN ": Error parsing core group (%s)",
284             item->values[j].value.string);
285       return (-EINVAL);
286     }
287
288     for (int i = 0; i < n; i++) {
289       if (cores[i] > max_core) {
290         ERROR(RDT_PLUGIN ": Core group (%s) contains invalid core id (%d)",
291               item->values[j].value.string, (int)cores[i]);
292         return (-EINVAL);
293       }
294     }
295
296     /* set core group info */
297     ret = cgroup_set(&groups[index], item->values[j].value.string, cores, n);
298     if (ret < 0)
299       return ret;
300
301     index++;
302
303     if (index >= max_groups) {
304       WARNING(RDT_PLUGIN ": Too many core groups configured");
305       return index;
306     }
307   }
308
309   return index;
310 }
311
312 #if COLLECT_DEBUG
313 static void rdt_dump_cgroups(void) {
314   char cores[RDT_MAX_CORES * 4];
315
316   if (g_rdt == NULL)
317     return;
318
319   DEBUG(RDT_PLUGIN ": Core Groups Dump");
320   DEBUG(RDT_PLUGIN ":  groups count: %zu", g_rdt->num_groups);
321
322   for (int i = 0; i < g_rdt->num_groups; i++) {
323
324     memset(cores, 0, sizeof(cores));
325     for (int j = 0; j < g_rdt->cgroups[i].num_cores; j++) {
326       snprintf(cores + strlen(cores), sizeof(cores) - strlen(cores) - 1, " %d",
327                g_rdt->cgroups[i].cores[j]);
328     }
329
330     DEBUG(RDT_PLUGIN ":  group[%d]:", i);
331     DEBUG(RDT_PLUGIN ":    description: %s", g_rdt->cgroups[i].desc);
332     DEBUG(RDT_PLUGIN ":    cores: %s", cores);
333     DEBUG(RDT_PLUGIN ":    events: 0x%X", g_rdt->cgroups[i].events);
334   }
335
336   return;
337 }
338
339 static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
340
341 static inline double bytes_to_mb(const double bytes) {
342   return bytes / (1024.0 * 1024.0);
343 }
344
345 static void rdt_dump_data(void) {
346   /*
347    * CORE - monitored group of cores
348    * RMID - Resource Monitoring ID associated with the monitored group
349    * LLC - last level cache occupancy
350    * MBL - local memory bandwidth
351    * MBR - remote memory bandwidth
352    */
353   DEBUG("  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
354   for (int i = 0; i < g_rdt->num_groups; i++) {
355
356     const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
357
358     double llc = bytes_to_kb(pv->llc);
359     double mbr = bytes_to_mb(pv->mbm_remote_delta);
360     double mbl = bytes_to_mb(pv->mbm_local_delta);
361
362     DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cgroups[i].desc,
363           g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
364   }
365 }
366 #endif /* COLLECT_DEBUG */
367
368 static void rdt_free_cgroups(void) {
369   for (int i = 0; i < RDT_MAX_CORES; i++) {
370     sfree(g_rdt->cgroups[i].desc);
371
372     sfree(g_rdt->cgroups[i].cores);
373     g_rdt->cgroups[i].num_cores = 0;
374
375     sfree(g_rdt->pgroups[i]);
376   }
377 }
378
379 static int rdt_default_cgroups(void) {
380   int ret;
381
382   /* configure each core in separate group */
383   for (unsigned i = 0; i < g_rdt->pqos_cpu->num_cores; i++) {
384     char desc[DATA_MAX_NAME_LEN];
385     uint64_t core = i;
386
387     ssnprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore);
388
389     /* set core group info */
390     ret = cgroup_set(&g_rdt->cgroups[i], desc, &core, 1);
391     if (ret < 0)
392       return ret;
393   }
394
395   return g_rdt->pqos_cpu->num_cores;
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, RDT_MAX_CORES,
417                          g_rdt->pqos_cpu->num_cores - 1);
418   if (n < 0) {
419     rdt_free_cgroups();
420     ERROR(RDT_PLUGIN ": Error parsing core groups configuration.");
421     return (-EINVAL);
422   }
423
424   if (n == 0) {
425     /* create default core groups if "Cores" config option is empty */
426     n = rdt_default_cgroups();
427     if (n < 0) {
428       rdt_free_cgroups();
429       ERROR(RDT_PLUGIN ": Error creating default core groups configuration.");
430       return n;
431     }
432     INFO(RDT_PLUGIN
433          ": No core groups configured. Default core groups created.");
434   }
435
436   /* Get all available events on this platform */
437   for (int i = 0; i < g_rdt->cap_mon->u.mon->num_events; i++)
438     events |= g_rdt->cap_mon->u.mon->events[i].type;
439
440   events &= ~(PQOS_PERF_EVENT_LLC_MISS);
441
442   DEBUG(RDT_PLUGIN ": Number of cores in the system: %u",
443         g_rdt->pqos_cpu->num_cores);
444   DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
445
446   g_rdt->num_groups = n;
447   for (int i = 0; i < n; i++) {
448     for (int j = 0; j < i; j++) {
449       int found = 0;
450       found = cgroup_cmp(&g_rdt->cgroups[j], &g_rdt->cgroups[i]);
451       if (found != 0) {
452         rdt_free_cgroups();
453         ERROR(RDT_PLUGIN ": Cannot monitor same cores in different groups.");
454         return (-EINVAL);
455       }
456     }
457
458     g_rdt->cgroups[i].events = events;
459     g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i]));
460     if (g_rdt->pgroups[i] == NULL) {
461       rdt_free_cgroups();
462       ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
463       return (-ENOMEM);
464     }
465   }
466
467   return (0);
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   /* In case previous instance of the application was not closed properly
485    * call fini and ignore return code. */
486   pqos_fini();
487
488   /* TODO:
489    * stdout should not be used here. Will be reworked when support of log
490    * callback is added to PQoS library.
491   */
492   ret = pqos_init(&(struct pqos_config){.fd_log = STDOUT_FILENO});
493   if (ret != PQOS_RETVAL_OK) {
494     ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
495     goto rdt_preinit_error1;
496   }
497
498   ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
499   if (ret != PQOS_RETVAL_OK) {
500     ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
501     goto rdt_preinit_error2;
502   }
503
504   ret = pqos_cap_get_type(g_rdt->pqos_cap, PQOS_CAP_TYPE_MON, &g_rdt->cap_mon);
505   if (ret == PQOS_RETVAL_PARAM) {
506     ERROR(RDT_PLUGIN ": Error retrieving monitoring capabilities.");
507     goto rdt_preinit_error2;
508   }
509
510   if (g_rdt->cap_mon == NULL) {
511     ERROR(
512         RDT_PLUGIN
513         ": Monitoring capability not detected. Nothing to do for the plugin.");
514     goto rdt_preinit_error2;
515   }
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(char *cgroup, char *type, char *type_instance,
564                               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(char *cgroup, char *type, char *type_instance,
580                              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 (int i = 0; i < g_rdt->num_groups; i++) {
614     enum pqos_mon_event mbm_events =
615         (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
616          PQOS_MON_EVENT_RMEM_BW);
617
618     const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
619
620     /* Submit only monitored events data */
621
622     if (g_rdt->cgroups[i].events & PQOS_MON_EVENT_L3_OCCUP)
623       rdt_submit_gauge(g_rdt->cgroups[i].desc, "bytes", "llc", pv->llc);
624
625     if (g_rdt->cgroups[i].events & PQOS_PERF_EVENT_IPC)
626       rdt_submit_gauge(g_rdt->cgroups[i].desc, "ipc", NULL, pv->ipc);
627
628     if (g_rdt->cgroups[i].events & mbm_events) {
629       rdt_submit_derive(g_rdt->cgroups[i].desc, "memory_bandwidth", "local",
630                         pv->mbm_local_delta);
631       rdt_submit_derive(g_rdt->cgroups[i].desc, "memory_bandwidth", "remote",
632                         pv->mbm_remote_delta);
633     }
634   }
635
636   return (0);
637 }
638
639 static int rdt_init(void) {
640   int ret;
641
642   if(g_state == CONFIGURATION_ERROR)
643     return (-1);
644
645   ret = rdt_preinit();
646   if (ret != 0)
647     return ret;
648
649   /* Start monitoring */
650   for (int i = 0; i < g_rdt->num_groups; i++) {
651     rdt_core_group_t *cg = &g_rdt->cgroups[i];
652
653     ret = pqos_mon_start(cg->num_cores, cg->cores, cg->events, (void *)cg->desc,
654                          g_rdt->pgroups[i]);
655
656     if (ret != PQOS_RETVAL_OK)
657       ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
658             cg->desc, ret);
659   }
660
661   return (0);
662 }
663
664 static int rdt_shutdown(void) {
665   int ret;
666
667   DEBUG(RDT_PLUGIN ": rdt_shutdown.");
668
669   if (g_rdt == NULL)
670     return (0);
671
672   /* Stop monitoring */
673   for (int i = 0; i < g_rdt->num_groups; i++) {
674     pqos_mon_stop(g_rdt->pgroups[i]);
675   }
676
677   ret = pqos_fini();
678   if (ret != PQOS_RETVAL_OK)
679     ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
680
681   rdt_free_cgroups();
682   sfree(g_rdt);
683
684   return (0);
685 }
686
687 void module_register(void) {
688   plugin_register_init(RDT_PLUGIN, rdt_init);
689   plugin_register_complex_config(RDT_PLUGIN, rdt_config);
690   plugin_register_complex_read(NULL, RDT_PLUGIN, rdt_read, 0, NULL);
691   plugin_register_shutdown(RDT_PLUGIN, rdt_shutdown);
692 }