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