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