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 **/
28 #include "common.h"
29 #include "collectd.h"
31 #include <pqos.h>
33 #define RDT_PLUGIN "intel_rdt"
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)
39 typedef enum {
40 UNKNOWN = 0,
41 CONFIGURATION_ERROR,
42 } rdt_config_status;
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;
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;
62 static rdt_ctx_t *g_rdt = NULL;
64 static rdt_config_status g_state = UNKNOWN;
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 }
73 static int strtouint64(const char *s, uint64_t *n) {
74 char *endptr = NULL;
76 assert(s != NULL);
77 assert(n != NULL);
79 *n = strtoull(s, &endptr, 0);
81 if (!(*s != '\0' && *endptr == '\0')) {
82 DEBUG(RDT_PLUGIN ": Error converting '%s' to unsigned number.", s);
83 return -EINVAL;
84 }
86 return 0;
87 }
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;
115 if (s == NULL || nums == NULL || max == 0)
116 return index;
118 for (;;) {
119 char *p = NULL;
120 char *token = NULL;
122 token = strtok_r(s, ",", &saveptr);
123 if (token == NULL)
124 break;
126 s = NULL;
128 while (isspace(*token))
129 token++;
130 if (*token == '\0')
131 continue;
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;
157 ret = strtouint64(token, &val);
158 if (ret < 0)
159 return 0;
161 if (!(isdup(nums, index, val))) {
162 nums[index] = val;
163 index++;
164 }
165 if (index >= max)
166 return index;
167 }
168 }
170 return index;
171 }
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;
193 assert(cg_a != NULL);
194 assert(cg_b != NULL);
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;
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 }
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);
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 }
236 for (size_t i = 0; i < num_cores; i++)
237 cg->cores[i] = (unsigned)cores[i];
239 return 0;
240 }
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;
264 assert(groups != NULL);
265 assert(max_groups > 0);
266 assert(item != NULL);
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];
274 if ((item->values[j].value.string == NULL) ||
275 (strlen(item->values[j].value.string) == 0))
276 continue;
278 sstrncpy(value, item->values[j].value.string, sizeof(value));
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 }
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;
292 index++;
294 if (index >= max_groups) {
295 WARNING(RDT_PLUGIN ": Too many core groups configured");
296 return index;
297 }
298 }
300 return index;
301 }
303 #if COLLECT_DEBUG
304 static void rdt_dump_cgroups(void) {
305 char cores[RDT_MAX_CORES * 4];
307 if (g_rdt == NULL)
308 return;
310 DEBUG(RDT_PLUGIN ": Core Groups Dump");
311 DEBUG(RDT_PLUGIN ": groups count: %zu", g_rdt->num_groups);
313 for (int i = 0; i < g_rdt->num_groups; i++) {
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 }
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 }
327 return;
328 }
330 static inline double bytes_to_kb(const double bytes) { return bytes / 1024.0; }
332 static inline double bytes_to_mb(const double bytes) {
333 return bytes / (1024.0 * 1024.0);
334 }
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++) {
347 const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
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);
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 */
359 static void rdt_free_cgroups(void) {
360 for (int i = 0; i < RDT_MAX_CORES; i++) {
361 sfree(g_rdt->cgroups[i].desc);
363 sfree(g_rdt->cgroups[i].cores);
364 g_rdt->cgroups[i].num_cores = 0;
366 sfree(g_rdt->pgroups[i]);
367 }
368 }
370 static int rdt_default_cgroups(void) {
371 int ret;
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;
378 snprintf(desc, sizeof(desc), "%d", g_rdt->pqos_cpu->cores[i].lcore);
380 /* set core group info */
381 ret = cgroup_set(&g_rdt->cgroups[i], desc, &core, 1);
382 if (ret < 0)
383 return ret;
384 }
386 return g_rdt->pqos_cpu->num_cores;
387 }
389 static int rdt_is_core_id_valid(int core_id) {
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;
395 return 0;
396 }
398 static int rdt_config_cgroups(oconfig_item_t *item) {
399 int n = 0;
400 enum pqos_mon_event events = 0;
402 if (item == NULL) {
403 DEBUG(RDT_PLUGIN ": cgroups_config: Invalid argument.");
404 return -EINVAL;
405 }
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 }
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 }
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 }
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 }
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;
453 events &= ~(PQOS_PERF_EVENT_LLC_MISS);
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);
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 }
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 }
480 return 0;
481 }
483 static void rdt_pqos_log(void *context, const size_t size, const char *msg) {
484 DEBUG(RDT_PLUGIN ": %s", msg);
485 }
487 static int rdt_preinit(void) {
488 int ret;
490 if (g_rdt != NULL) {
491 /* already initialized if config callback was called before init callback */
492 return 0;
493 }
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 }
501 struct pqos_config pqos = {.fd_log = -1,
502 .callback_log = rdt_pqos_log,
503 .context_log = NULL,
504 .verbose = 0};
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 }
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 }
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 }
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 }
531 /* Reset pqos monitoring groups registers */
532 pqos_mon_reset();
534 return 0;
536 rdt_preinit_error2:
537 pqos_fini();
539 rdt_preinit_error1:
541 sfree(g_rdt);
543 return -1;
544 }
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 }
556 for (int i = 0; i < ci->children_num; i++) {
557 oconfig_item_t *child = ci->children + i;
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 }
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 }
577 return 0;
578 }
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;
584 vl.values = &(value_t){.derive = value};
585 vl.values_len = 1;
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));
593 plugin_dispatch_values(&vl);
594 }
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;
600 vl.values = &(value_t){.gauge = value};
601 vl.values_len = 1;
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));
609 plugin_dispatch_values(&vl);
610 }
612 static int rdt_read(__attribute__((unused)) user_data_t *ud) {
613 int ret;
615 if (g_rdt == NULL) {
616 ERROR(RDT_PLUGIN ": rdt_read: plugin not initialized.");
617 return -EINVAL;
618 }
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 }
626 #if COLLECT_DEBUG
627 rdt_dump_data();
628 #endif /* COLLECT_DEBUG */
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);
635 const struct pqos_event_values *pv = &g_rdt->pgroups[i]->values;
637 /* Submit only monitored events data */
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);
642 if (g_rdt->cgroups[i].events & PQOS_PERF_EVENT_IPC)
643 rdt_submit_gauge(g_rdt->cgroups[i].desc, "ipc", NULL, pv->ipc);
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 }
653 return 0;
654 }
656 static int rdt_init(void) {
657 int ret;
659 if(g_state == CONFIGURATION_ERROR)
660 return -1;
662 ret = rdt_preinit();
663 if (ret != 0)
664 return ret;
666 /* Start monitoring */
667 for (int i = 0; i < g_rdt->num_groups; i++) {
668 rdt_core_group_t *cg = &g_rdt->cgroups[i];
670 ret = pqos_mon_start(cg->num_cores, cg->cores, cg->events, (void *)cg->desc,
671 g_rdt->pgroups[i]);
673 if (ret != PQOS_RETVAL_OK)
674 ERROR(RDT_PLUGIN ": Error starting monitoring group %s (pqos status=%d)",
675 cg->desc, ret);
676 }
678 return 0;
679 }
681 static int rdt_shutdown(void) {
682 int ret;
684 DEBUG(RDT_PLUGIN ": rdt_shutdown.");
686 if (g_rdt == NULL)
687 return 0;
689 /* Stop monitoring */
690 for (int i = 0; i < g_rdt->num_groups; i++) {
691 pqos_mon_stop(g_rdt->pgroups[i]);
692 }
694 ret = pqos_fini();
695 if (ret != PQOS_RETVAL_OK)
696 ERROR(RDT_PLUGIN ": Error shutting down PQoS library.");
698 rdt_free_cgroups();
699 sfree(g_rdt);
701 return 0;
702 }
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 }