Merge pull request #2681 from elfiesmelfie/feat_pmu_cores
[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  **/
27
28 #include "collectd.h"
29 #include "common.h"
30
31 #include "utils_config_cores.h"
32
33 #include <pqos.h>
34
35 #define RDT_PLUGIN "intel_rdt"
36
37 #define RDT_MAX_SOCKETS 8
38 #define RDT_MAX_SOCKET_CORES 64
39 #define RDT_MAX_CORES (RDT_MAX_SOCKET_CORES * RDT_MAX_SOCKETS)
40
41 typedef enum {
42   UNKNOWN = 0,
43   CONFIGURATION_ERROR,
44 } rdt_config_status;
45
46 struct rdt_ctx_s {
47   core_groups_list_t cores;
48   enum pqos_mon_event events[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 rdt_config_status g_state = UNKNOWN;
60
61 #if COLLECT_DEBUG
62 static void rdt_dump_cgroups(void) {
63   char cores[RDT_MAX_CORES * 4];
64
65   if (g_rdt == NULL)
66     return;
67
68   DEBUG(RDT_PLUGIN ": Core Groups Dump");
69   DEBUG(RDT_PLUGIN ":  groups count: %zu", g_rdt->num_groups);
70
71   for (int i = 0; i < g_rdt->num_groups; i++) {
72     core_group_t *cgroup = g_rdt->cores.cgroups + i;
73
74     memset(cores, 0, sizeof(cores));
75     for (int j = 0; j < cgroup->num_cores; j++) {
76       snprintf(cores + strlen(cores), sizeof(cores) - strlen(cores) - 1, " %d",
77                cgroup->cores[j]);
78     }
79
80     DEBUG(RDT_PLUGIN ":  group[%d]:", i);
81     DEBUG(RDT_PLUGIN ":    description: %s", cgroup->desc);
82     DEBUG(RDT_PLUGIN ":    cores: %s", cores);
83     DEBUG(RDT_PLUGIN ":    events: 0x%X", g_rdt->events[i]);
84   }
85
86   return;
87 }
88
89 static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
90
91 static inline double bytes_to_mb(const double bytes) {
92   return bytes / (1024.0 * 1024.0);
93 }
94
95 static void rdt_dump_data(void) {
96   /*
97    * CORE - monitored group of cores
98    * RMID - Resource Monitoring ID associated with the monitored group
99    * LLC - last level cache occupancy
100    * MBL - local memory bandwidth
101    * MBR - remote memory bandwidth
102    */
103   DEBUG("  CORE     RMID    LLC[KB]   MBL[MB]    MBR[MB]");
104   for (int i = 0; i < g_rdt->num_groups; i++) {
105
106     const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
107
108     double llc = bytes_to_kb(pv->llc);
109     double mbr = bytes_to_mb(pv->mbm_remote_delta);
110     double mbl = bytes_to_mb(pv->mbm_local_delta);
111
112     DEBUG(" [%s] %8u %10.1f %10.1f %10.1f", g_rdt->cores.cgroups[i].desc,
113           g_rdt->pgroups[i]->poll_ctx[0].rmid, llc, mbl, mbr);
114   }
115 }
116 #endif /* COLLECT_DEBUG */
117
118 static void rdt_free_cgroups(void) {
119   config_cores_cleanup(&g_rdt->cores);
120   for (int i = 0; i < RDT_MAX_CORES; i++) {
121     sfree(g_rdt->pgroups[i]);
122   }
123 }
124
125 static int rdt_default_cgroups(void) {
126   unsigned num_cores = g_rdt->pqos_cpu->num_cores;
127
128   g_rdt->cores.cgroups = calloc(num_cores, sizeof(*(g_rdt->cores.cgroups)));
129   if (g_rdt->cores.cgroups == NULL) {
130     ERROR(RDT_PLUGIN ": Error allocating core groups array");
131     return -ENOMEM;
132   }
133   g_rdt->cores.num_cgroups = num_cores;
134
135   /* configure each core in separate group */
136   for (unsigned i = 0; i < num_cores; i++) {
137     core_group_t *cgroup = g_rdt->cores.cgroups + i;
138     char desc[DATA_MAX_NAME_LEN];
139
140     /* set core group info */
141     cgroup->cores = calloc(1, sizeof(*(cgroup->cores)));
142     if (cgroup->cores == NULL) {
143       ERROR(RDT_PLUGIN ": Error allocating cores array");
144       rdt_free_cgroups();
145       return -ENOMEM;
146     }
147     cgroup->num_cores = 1;
148     cgroup->cores[0] = i;
149
150     snprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore);
151     cgroup->desc = strdup(desc);
152     if (cgroup->desc == NULL) {
153       ERROR(RDT_PLUGIN ": Error allocating core group description");
154       rdt_free_cgroups();
155       return -ENOMEM;
156     }
157   }
158
159   return num_cores;
160 }
161
162 static int rdt_is_core_id_valid(int core_id) {
163
164   for (int i = 0; i < g_rdt->pqos_cpu->num_cores; i++)
165     if (core_id == g_rdt->pqos_cpu->cores[i].lcore)
166       return 1;
167
168   return 0;
169 }
170
171 static int rdt_config_cgroups(oconfig_item_t *item) {
172   size_t n = 0;
173   enum pqos_mon_event events = 0;
174
175   if (config_cores_parse(item, &g_rdt->cores) < 0) {
176     rdt_free_cgroups();
177     ERROR(RDT_PLUGIN ": Error parsing core groups configuration.");
178     return -EINVAL;
179   }
180   n = g_rdt->cores.num_cgroups;
181
182   /* validate configured core id values */
183   for (size_t group_idx = 0; group_idx < n; group_idx++) {
184     core_group_t *cgroup = g_rdt->cores.cgroups + group_idx;
185     for (size_t core_idx = 0; core_idx < cgroup->num_cores; core_idx++) {
186       if (!rdt_is_core_id_valid((int)cgroup->cores[core_idx])) {
187         ERROR(RDT_PLUGIN ": Core group '%s' contains invalid core id '%d'",
188               cgroup->desc, (int)cgroup->cores[core_idx]);
189         rdt_free_cgroups();
190         return -EINVAL;
191       }
192     }
193   }
194
195   if (n == 0) {
196     /* create default core groups if "Cores" config option is empty */
197     int ret = rdt_default_cgroups();
198     if (ret < 0) {
199       rdt_free_cgroups();
200       ERROR(RDT_PLUGIN ": Error creating default core groups configuration.");
201       return ret;
202     }
203     n = (size_t)ret;
204     INFO(RDT_PLUGIN
205          ": No core groups configured. Default core groups created.");
206   }
207
208   /* Get all available events on this platform */
209   for (int i = 0; i < g_rdt->cap_mon->u.mon->num_events; i++)
210     events |= g_rdt->cap_mon->u.mon->events[i].type;
211
212   events &= ~(PQOS_PERF_EVENT_LLC_MISS);
213
214   DEBUG(RDT_PLUGIN ": Number of cores in the system: %u",
215         g_rdt->pqos_cpu->num_cores);
216   DEBUG(RDT_PLUGIN ": Available events to monitor: %#x", events);
217
218   g_rdt->num_groups = n;
219   for (size_t i = 0; i < n; i++) {
220     for (size_t j = 0; j < i; j++) {
221       int found = 0;
222       found = config_cores_cmp_cgroups(&g_rdt->cores.cgroups[j],
223                                        &g_rdt->cores.cgroups[i]);
224       if (found != 0) {
225         rdt_free_cgroups();
226         ERROR(RDT_PLUGIN ": Cannot monitor same cores in different groups.");
227         return -EINVAL;
228       }
229     }
230
231     g_rdt->events[i] = events;
232     g_rdt->pgroups[i] = calloc(1, sizeof(*g_rdt->pgroups[i]));
233     if (g_rdt->pgroups[i] == NULL) {
234       rdt_free_cgroups();
235       ERROR(RDT_PLUGIN ": Failed to allocate memory for monitoring data.");
236       return -ENOMEM;
237     }
238   }
239
240   return 0;
241 }
242
243 static void rdt_pqos_log(void *context, const size_t size, const char *msg) {
244   DEBUG(RDT_PLUGIN ": %s", msg);
245 }
246
247 static int rdt_preinit(void) {
248   int ret;
249
250   if (g_rdt != NULL) {
251     /* already initialized if config callback was called before init callback */
252     return 0;
253   }
254
255   g_rdt = calloc(1, sizeof(*g_rdt));
256   if (g_rdt == NULL) {
257     ERROR(RDT_PLUGIN ": Failed to allocate memory for rdt context.");
258     return -ENOMEM;
259   }
260
261   struct pqos_config pqos = {.fd_log = -1,
262                              .callback_log = rdt_pqos_log,
263                              .context_log = NULL,
264                              .verbose = 0};
265
266   ret = pqos_init(&pqos);
267   if (ret != PQOS_RETVAL_OK) {
268     ERROR(RDT_PLUGIN ": Error initializing PQoS library!");
269     goto rdt_preinit_error1;
270   }
271
272   ret = pqos_cap_get(&g_rdt->pqos_cap, &g_rdt->pqos_cpu);
273   if (ret != PQOS_RETVAL_OK) {
274     ERROR(RDT_PLUGIN ": Error retrieving PQoS capabilities.");
275     goto rdt_preinit_error2;
276   }
277
278   ret = pqos_cap_get_type(g_rdt->pqos_cap, PQOS_CAP_TYPE_MON, &g_rdt->cap_mon);
279   if (ret == PQOS_RETVAL_PARAM) {
280     ERROR(RDT_PLUGIN ": Error retrieving monitoring capabilities.");
281     goto rdt_preinit_error2;
282   }
283
284   if (g_rdt->cap_mon == NULL) {
285     ERROR(
286         RDT_PLUGIN
287         ": Monitoring capability not detected. Nothing to do for the plugin.");
288     goto rdt_preinit_error2;
289   }
290
291   /* Reset pqos monitoring groups registers */
292   pqos_mon_reset();
293
294   return 0;
295
296 rdt_preinit_error2:
297   pqos_fini();
298
299 rdt_preinit_error1:
300
301   sfree(g_rdt);
302
303   return -1;
304 }
305
306 static int rdt_config(oconfig_item_t *ci) {
307   if (rdt_preinit() != 0) {
308     g_state = CONFIGURATION_ERROR;
309     /* if we return -1 at this point collectd
310       reports a failure in configuration and
311       aborts
312     */
313     return (0);
314   }
315
316   for (int i = 0; i < ci->children_num; i++) {
317     oconfig_item_t *child = ci->children + i;
318
319     if (strcasecmp("Cores", child->key) == 0) {
320       if (rdt_config_cgroups(child) != 0) {
321         g_state = CONFIGURATION_ERROR;
322         /* if we return -1 at this point collectd
323            reports a failure in configuration and
324            aborts
325          */
326         return (0);
327       }
328
329 #if COLLECT_DEBUG
330       rdt_dump_cgroups();
331 #endif /* COLLECT_DEBUG */
332     } else {
333       ERROR(RDT_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
334     }
335   }
336
337   return 0;
338 }
339
340 static void rdt_submit_derive(char *cgroup, char *type, char *type_instance,
341                               derive_t value) {
342   value_list_t vl = VALUE_LIST_INIT;
343
344   vl.values = &(value_t){.derive = value};
345   vl.values_len = 1;
346
347   sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
348   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
349   sstrncpy(vl.type, type, sizeof(vl.type));
350   if (type_instance)
351     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
352
353   plugin_dispatch_values(&vl);
354 }
355
356 static void rdt_submit_gauge(char *cgroup, char *type, char *type_instance,
357                              gauge_t value) {
358   value_list_t vl = VALUE_LIST_INIT;
359
360   vl.values = &(value_t){.gauge = value};
361   vl.values_len = 1;
362
363   sstrncpy(vl.plugin, RDT_PLUGIN, sizeof(vl.plugin));
364   snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%s", cgroup);
365   sstrncpy(vl.type, type, sizeof(vl.type));
366   if (type_instance)
367     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
368
369   plugin_dispatch_values(&vl);
370 }
371
372 static int rdt_read(__attribute__((unused)) user_data_t *ud) {
373   int ret;
374
375   if (g_rdt == NULL) {
376     ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
377     return -EINVAL;
378   }
379
380   ret = pqos_mon_poll(&g_rdt->pgroups[0], (unsigned)g_rdt->num_groups);
381   if (ret != PQOS_RETVAL_OK) {
382     ERROR(RDT_PLUGIN ": Failed to poll monitoring data.");
383     return -1;
384   }
385
386 #if COLLECT_DEBUG
387   rdt_dump_data();
388 #endif /* COLLECT_DEBUG */
389
390   for (int i = 0; i < g_rdt->num_groups; i++) {
391     core_group_t *cgroup = g_rdt->cores.cgroups + i;
392
393     enum pqos_mon_event mbm_events =
394         (PQOS_MON_EVENT_LMEM_BW | PQOS_MON_EVENT_TMEM_BW |
395          PQOS_MON_EVENT_RMEM_BW);
396
397     const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
398
399     /* Submit only monitored events data */
400
401     if (g_rdt->events[i] & PQOS_MON_EVENT_L3_OCCUP)
402       rdt_submit_gauge(cgroup->desc, "bytes", "llc", pv->llc);
403
404     if (g_rdt->events[i] & PQOS_PERF_EVENT_IPC)
405       rdt_submit_gauge(cgroup->desc, "ipc", NULL, pv->ipc);
406
407     if (g_rdt->events[i] & mbm_events) {
408       rdt_submit_derive(cgroup->desc, "memory_bandwidth", "local",
409                         pv->mbm_local_delta);
410       rdt_submit_derive(cgroup->desc, "memory_bandwidth", "remote",
411                         pv->mbm_remote_delta);
412     }
413   }
414
415   return 0;
416 }
417
418 static int rdt_init(void) {
419   int ret;
420
421   if (g_state == CONFIGURATION_ERROR)
422     return -1;
423
424   ret = rdt_preinit();
425   if (ret != 0)
426     return ret;
427
428   /* Start monitoring */
429   for (int i = 0; i < g_rdt->num_groups; i++) {
430     core_group_t *cg = g_rdt->cores.cgroups + i;
431
432     ret = pqos_mon_start(cg->num_cores, cg->cores, g_rdt->events[i],
433                          (void *)cg->desc, g_rdt->pgroups[i]);
434
435     if (ret != PQOS_RETVAL_OK)
436       ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
437             cg->desc, ret);
438   }
439
440   return 0;
441 }
442
443 static int rdt_shutdown(void) {
444   int ret;
445
446   DEBUG(RDT_PLUGIN ": rdt_shutdown.");
447
448   if (g_rdt == NULL)
449     return 0;
450
451   /* Stop monitoring */
452   for (int i = 0; i < g_rdt->num_groups; i++) {
453     pqos_mon_stop(g_rdt->pgroups[i]);
454   }
455
456   ret = pqos_fini();
457   if (ret != PQOS_RETVAL_OK)
458     ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
459
460   rdt_free_cgroups();
461   sfree(g_rdt);
462
463   return 0;
464 }
465
466 void module_register(void) {
467   plugin_register_init(RDT_PLUGIN, rdt_init);
468   plugin_register_complex_config(RDT_PLUGIN, rdt_config);
469   plugin_register_complex_read(NULL, RDT_PLUGIN, rdt_read, 0, NULL);
470   plugin_register_shutdown(RDT_PLUGIN, rdt_shutdown);
471 }