intel_pmu: address PR comments
[collectd.git] / src / intel_pmu.c
1 /**
2  * collectd - src/intel_pmu.c
3  *
4  * Copyright(c) 2017 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 <jevents.h>
32 #include <jsession.h>
33
34 #define PMU_PLUGIN "intel_pmu"
35
36 #define HW_CACHE_READ_ACCESS                                                   \
37   (((PERF_COUNT_HW_CACHE_OP_READ) << 8) |                                      \
38    ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
39
40 #define HW_CACHE_WRITE_ACCESS                                                  \
41   (((PERF_COUNT_HW_CACHE_OP_WRITE) << 8) |                                     \
42    ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
43
44 #define HW_CACHE_PREFETCH_ACCESS                                               \
45   (((PERF_COUNT_HW_CACHE_OP_PREFETCH) << 8) |                                  \
46    ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
47
48 #define HW_CACHE_READ_MISS                                                     \
49   (((PERF_COUNT_HW_CACHE_OP_READ) << 8) |                                      \
50    ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
51
52 #define HW_CACHE_WRITE_MISS                                                    \
53   (((PERF_COUNT_HW_CACHE_OP_WRITE) << 8) |                                     \
54    ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
55
56 #define HW_CACHE_PREFETCH_MISS                                                 \
57   (((PERF_COUNT_HW_CACHE_OP_PREFETCH) << 8) |                                  \
58    ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
59
60 struct event_info {
61   char *name;
62   uint64_t config;
63 };
64 typedef struct event_info event_info_t;
65
66 struct intel_pmu_ctx_s {
67   _Bool hw_cache_events;
68   _Bool kernel_pmu_events;
69   _Bool sw_events;
70   char **hw_events;
71   size_t hw_events_count;
72   struct eventlist *event_list;
73 };
74 typedef struct intel_pmu_ctx_s intel_pmu_ctx_t;
75
76 event_info_t g_kernel_pmu_events[] = {
77     {.name = "cpu-cycles", .config = PERF_COUNT_HW_CPU_CYCLES},
78     {.name = "instructions", .config = PERF_COUNT_HW_INSTRUCTIONS},
79     {.name = "cache-references", .config = PERF_COUNT_HW_CACHE_REFERENCES},
80     {.name = "cache-misses", .config = PERF_COUNT_HW_CACHE_MISSES},
81     {.name = "branches", .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS},
82     {.name = "branch-misses", .config = PERF_COUNT_HW_BRANCH_MISSES},
83     {.name = "bus-cycles", .config = PERF_COUNT_HW_BUS_CYCLES},
84 };
85
86 event_info_t g_hw_cache_events[] = {
87
88     {.name = "L1-dcache-loads",
89      .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_READ_ACCESS)},
90     {.name = "L1-dcache-load-misses",
91      .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_READ_MISS)},
92     {.name = "L1-dcache-stores",
93      .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_WRITE_ACCESS)},
94     {.name = "L1-dcache-store-misses",
95      .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_WRITE_MISS)},
96     {.name = "L1-dcache-prefetches",
97      .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_PREFETCH_ACCESS)},
98     {.name = "L1-dcache-prefetch-misses",
99      .config = (PERF_COUNT_HW_CACHE_L1D | HW_CACHE_PREFETCH_MISS)},
100
101     {.name = "L1-icache-loads",
102      .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_READ_ACCESS)},
103     {.name = "L1-icache-load-misses",
104      .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_READ_MISS)},
105     {.name = "L1-icache-prefetches",
106      .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_PREFETCH_ACCESS)},
107     {.name = "L1-icache-prefetch-misses",
108      .config = (PERF_COUNT_HW_CACHE_L1I | HW_CACHE_PREFETCH_MISS)},
109
110     {.name = "LLC-loads",
111      .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_READ_ACCESS)},
112     {.name = "LLC-load-misses",
113      .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_READ_MISS)},
114     {.name = "LLC-stores",
115      .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_WRITE_ACCESS)},
116     {.name = "LLC-store-misses",
117      .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_WRITE_MISS)},
118     {.name = "LLC-prefetches",
119      .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_PREFETCH_ACCESS)},
120     {.name = "LLC-prefetch-misses",
121      .config = (PERF_COUNT_HW_CACHE_LL | HW_CACHE_PREFETCH_MISS)},
122
123     {.name = "dTLB-loads",
124      .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_READ_ACCESS)},
125     {.name = "dTLB-load-misses",
126      .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_READ_MISS)},
127     {.name = "dTLB-stores",
128      .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_WRITE_ACCESS)},
129     {.name = "dTLB-store-misses",
130      .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_WRITE_MISS)},
131     {.name = "dTLB-prefetches",
132      .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_PREFETCH_ACCESS)},
133     {.name = "dTLB-prefetch-misses",
134      .config = (PERF_COUNT_HW_CACHE_DTLB | HW_CACHE_PREFETCH_MISS)},
135
136     {.name = "iTLB-loads",
137      .config = (PERF_COUNT_HW_CACHE_ITLB | HW_CACHE_READ_ACCESS)},
138     {.name = "iTLB-load-misses",
139      .config = (PERF_COUNT_HW_CACHE_ITLB | HW_CACHE_READ_MISS)},
140
141     {.name = "branch-loads",
142      .config = (PERF_COUNT_HW_CACHE_BPU | HW_CACHE_READ_ACCESS)},
143     {.name = "branch-load-misses",
144      .config = (PERF_COUNT_HW_CACHE_BPU | HW_CACHE_READ_MISS)},
145 };
146
147 event_info_t g_sw_events[] = {
148     {.name = "cpu-clock", .config = PERF_COUNT_SW_CPU_CLOCK},
149
150     {.name = "task-clock", .config = PERF_COUNT_SW_TASK_CLOCK},
151
152     {.name = "context-switches", .config = PERF_COUNT_SW_CONTEXT_SWITCHES},
153
154     {.name = "cpu-migrations", .config = PERF_COUNT_SW_CPU_MIGRATIONS},
155
156     {.name = "page-faults", .config = PERF_COUNT_SW_PAGE_FAULTS},
157
158     {.name = "minor-faults", .config = PERF_COUNT_SW_PAGE_FAULTS_MIN},
159
160     {.name = "major-faults", .config = PERF_COUNT_SW_PAGE_FAULTS_MAJ},
161
162     {.name = "alignment-faults", .config = PERF_COUNT_SW_ALIGNMENT_FAULTS},
163
164     {.name = "emulation-faults", .config = PERF_COUNT_SW_EMULATION_FAULTS},
165 };
166
167 static intel_pmu_ctx_t g_ctx;
168
169 #if COLLECT_DEBUG
170 static void pmu_dump_events() {
171
172   DEBUG(PMU_PLUGIN ": Events:");
173
174   struct event *e;
175
176   for (e = g_ctx.event_list->eventlist; e; e = e->next) {
177     DEBUG(PMU_PLUGIN ":   event       : %s", e->event);
178     DEBUG(PMU_PLUGIN ":     group_lead: %d", e->group_leader);
179     DEBUG(PMU_PLUGIN ":     end_group : %d", e->end_group);
180     DEBUG(PMU_PLUGIN ":     type      : %#x", e->attr.type);
181     DEBUG(PMU_PLUGIN ":     config    : %#x", (unsigned)e->attr.config);
182     DEBUG(PMU_PLUGIN ":     size      : %d", e->attr.size);
183   }
184
185   return;
186 }
187
188 static void pmu_dump_config(void) {
189
190   DEBUG(PMU_PLUGIN ": Config:");
191   DEBUG(PMU_PLUGIN ":   hw_cache_events   : %d", g_ctx.hw_cache_events);
192   DEBUG(PMU_PLUGIN ":   kernel_pmu_events : %d", g_ctx.kernel_pmu_events);
193   DEBUG(PMU_PLUGIN ":   software_events   : %d", g_ctx.sw_events);
194
195   for (size_t i = 0; i < g_ctx.hw_events_count; i++) {
196     DEBUG(PMU_PLUGIN ":   hardware_events[%zu]: %s", i, g_ctx.hw_events[i]);
197   }
198
199   return;
200 }
201
202 #endif /* COLLECT_DEBUG */
203
204 static int pmu_config_hw_events(oconfig_item_t *ci) {
205
206   if (strcasecmp("HardwareEvents", ci->key) != 0) {
207     return -EINVAL;
208   }
209
210   g_ctx.hw_events = calloc(ci->values_num, sizeof(char *));
211   if (g_ctx.hw_events == NULL) {
212     ERROR(PMU_PLUGIN ": Failed to allocate hw events.");
213     return -ENOMEM;
214   }
215
216   for (int i = 0; i < ci->values_num; i++) {
217     if (ci->values[i].type != OCONFIG_TYPE_STRING) {
218       WARNING(PMU_PLUGIN ": The %s option requires string arguments.", ci->key);
219       continue;
220     }
221
222     g_ctx.hw_events[g_ctx.hw_events_count] = strdup(ci->values[i].value.string);
223     if (g_ctx.hw_events[g_ctx.hw_events_count] == NULL) {
224       ERROR(PMU_PLUGIN ": Failed to allocate hw events entry.");
225       return -ENOMEM;
226     }
227
228     g_ctx.hw_events_count++;
229   }
230
231   return 0;
232 }
233
234 static int pmu_config(oconfig_item_t *ci) {
235   int ret = 0;
236
237   DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
238
239   for (int i = 0; i < ci->children_num; i++) {
240     oconfig_item_t *child = ci->children + i;
241
242     if (strcasecmp("ReportHardwareCacheEvents", child->key) == 0) {
243       ret = cf_util_get_boolean(child, &g_ctx.hw_cache_events);
244     } else if (strcasecmp("ReportKernelPMUEvents", child->key) == 0) {
245       ret = cf_util_get_boolean(child, &g_ctx.kernel_pmu_events);
246     } else if (strcasecmp("HardwareEvents", child->key) == 0) {
247       ret = pmu_config_hw_events(child);
248     } else if (strcasecmp("ReportSoftwareEvents", child->key) == 0) {
249       ret = cf_util_get_boolean(child, &g_ctx.sw_events);
250     } else {
251       ERROR(PMU_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
252       ret = (-1);
253     }
254
255     if (ret != 0) {
256       DEBUG(PMU_PLUGIN ": %s:%d ret=%d", __FUNCTION__, __LINE__, ret);
257       return ret;
258     }
259   }
260
261 #if COLLECT_DEBUG
262   pmu_dump_config();
263 #endif
264
265   return 0;
266 }
267
268 static void pmu_submit_counter(int cpu, char *event, counter_t value) {
269   value_list_t vl = VALUE_LIST_INIT;
270
271   vl.values = &(value_t){.counter = value};
272   vl.values_len = 1;
273
274   sstrncpy(vl.plugin, PMU_PLUGIN, sizeof(vl.plugin));
275   if (cpu == -1) {
276     ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "all");
277   } else {
278     ssnprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%d", cpu);
279   }
280   sstrncpy(vl.type, "counter", sizeof(vl.type));
281   sstrncpy(vl.type_instance, event, sizeof(vl.type_instance));
282
283   plugin_dispatch_values(&vl);
284 }
285
286 static int pmu_dispatch_data(void) {
287
288   struct event *e;
289
290   for (e = g_ctx.event_list->eventlist; e; e = e->next) {
291     uint64_t all_value = 0;
292     int event_enabled = 0;
293     for (int i = 0; i < g_ctx.event_list->num_cpus; i++) {
294
295       if (e->efd[i].fd < 0)
296         continue;
297
298       event_enabled++;
299
300       uint64_t value = event_scaled_value(e, i);
301       all_value += value;
302
303       /* dispatch per CPU value */
304       pmu_submit_counter(i, e->event, value);
305     }
306
307     if (event_enabled > 0) {
308       DEBUG(PMU_PLUGIN ": %-20s %'10lu", e->event, all_value);
309       /* dispatch all CPU value */
310       pmu_submit_counter(-1, e->event, all_value);
311     }
312   }
313
314   return 0;
315 }
316
317 static int pmu_read(__attribute__((unused)) user_data_t *ud) {
318   int ret;
319
320   DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
321
322   ret = read_all_events(g_ctx.event_list);
323   if (ret != 0) {
324     ERROR(PMU_PLUGIN ": Failed to read values of all events.");
325     return 0;
326   }
327
328   ret = pmu_dispatch_data();
329   if (ret != 0) {
330     ERROR(PMU_PLUGIN ": Failed to dispatch event values.");
331     return 0;
332   }
333
334   return 0;
335 }
336
337 static int pmu_add_events(struct eventlist *el, uint32_t type,
338                           event_info_t *events, int count) {
339
340   for (int i = 0; i < count; i++) {
341     /* Allocate memory for event struct that contains array of efd structs
342        for all cores */
343     struct event *e =
344         calloc(sizeof(struct event) + sizeof(struct efd) * el->num_cpus, 1);
345     if (e == NULL) {
346       ERROR(PMU_PLUGIN ": Failed to allocate event structure");
347       return -ENOMEM;
348     }
349
350     e->attr.type = type;
351     e->attr.config = events[i].config;
352     e->attr.size = PERF_ATTR_SIZE_VER0;
353     e->next = NULL;
354     if (!el->eventlist)
355       el->eventlist = e;
356     if (el->eventlist_last)
357       el->eventlist_last->next = e;
358     el->eventlist_last = e;
359     e->event = strdup(events[i].name);
360   }
361
362   return 0;
363 }
364
365 static int pmu_add_hw_events(struct eventlist *el, char **e, size_t count) {
366
367   for (size_t i = 0; i < count; i++) {
368
369     size_t group_events_count = 0;
370
371     char *events = strdup(e[i]);
372     if (!events)
373       return -1;
374
375     char *s, *tmp;
376     for (s = strtok_r(events, ",", &tmp); s; s = strtok_r(NULL, ",", &tmp)) {
377
378       /* Multiple events parsed in one entry */
379       if (group_events_count == 1) {
380         /* Mark previously added event as group leader */
381         el->eventlist_last->group_leader = 1;
382       }
383
384       /* Allocate memory for event struct that contains array of efd structs
385          for all cores */
386       struct event *e =
387           calloc(sizeof(struct event) + sizeof(struct efd) * el->num_cpus, 1);
388       if (e == NULL) {
389         free(events);
390         return -ENOMEM;
391       }
392
393       if (resolve_event(s, &e->attr) == 0) {
394         e->next = NULL;
395         if (!el->eventlist)
396           el->eventlist = e;
397         if (el->eventlist_last)
398           el->eventlist_last->next = e;
399         el->eventlist_last = e;
400         e->event = strdup(s);
401       } else {
402         DEBUG(PMU_PLUGIN ": Cannot resolve %s", s);
403         sfree(e);
404       }
405
406       group_events_count++;
407     }
408
409     /* Multiple events parsed in one entry */
410     if (group_events_count > 1) {
411       /* Mark last added event as group end */
412       el->eventlist_last->end_group = 1;
413     }
414
415     free(events);
416   }
417
418   return 0;
419 }
420
421 static void pmu_free_events(struct eventlist *el) {
422
423   if (el == NULL)
424     return;
425
426   struct event *e = el->eventlist;
427
428   while (e) {
429     struct event *next = e->next;
430     sfree(e);
431     e = next;
432   }
433
434   el->eventlist = NULL;
435 }
436
437 static int pmu_setup_events(struct eventlist *el, bool measure_all,
438                             int measure_pid) {
439   struct event *e, *leader = NULL;
440   int ret = -1;
441
442   for (e = el->eventlist; e; e = e->next) {
443
444     for (int i = 0; i < el->num_cpus; i++) {
445       if (setup_event(e, i, leader, measure_all, measure_pid) < 0) {
446         WARNING(PMU_PLUGIN ": perf event '%s' is not available (cpu=%d).",
447                 e->event, i);
448       } else {
449         /* success if at least one event was set */
450         ret = 0;
451       }
452     }
453
454     if (e->group_leader)
455       leader = e;
456     if (e->end_group)
457       leader = NULL;
458   }
459
460   return ret;
461 }
462
463 static int pmu_init(void) {
464   int ret;
465
466   DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
467
468   g_ctx.event_list = alloc_eventlist();
469   if (g_ctx.event_list == NULL) {
470     ERROR(PMU_PLUGIN ": Failed to allocate event list.");
471     return -ENOMEM;
472   }
473
474   if (g_ctx.hw_cache_events) {
475     ret =
476         pmu_add_events(g_ctx.event_list, PERF_TYPE_HW_CACHE, g_hw_cache_events,
477                        STATIC_ARRAY_SIZE(g_hw_cache_events));
478     if (ret != 0) {
479       ERROR(PMU_PLUGIN ": Failed to add hw cache events.");
480       goto init_error;
481     }
482   }
483
484   if (g_ctx.kernel_pmu_events) {
485     ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_HARDWARE,
486                          g_kernel_pmu_events,
487                          STATIC_ARRAY_SIZE(g_kernel_pmu_events));
488     if (ret != 0) {
489       ERROR(PMU_PLUGIN ": Failed to add kernel PMU events.");
490       goto init_error;
491     }
492   }
493
494   /* parse events names if config option is present and is not empty */
495   if (g_ctx.hw_events_count) {
496     ret = pmu_add_hw_events(g_ctx.event_list, g_ctx.hw_events,
497                             g_ctx.hw_events_count);
498     if (ret != 0) {
499       ERROR(PMU_PLUGIN ": Failed to add hardware events.");
500       goto init_error;
501     }
502   }
503
504   if (g_ctx.sw_events) {
505     ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_SOFTWARE, g_sw_events,
506                          STATIC_ARRAY_SIZE(g_sw_events));
507     if (ret != 0) {
508       ERROR(PMU_PLUGIN ": Failed to add software events.");
509       goto init_error;
510     }
511   }
512
513 #if COLLECT_DEBUG
514   pmu_dump_events();
515 #endif
516
517   if (g_ctx.event_list->eventlist != NULL) {
518     /* measure all processes */
519     ret = pmu_setup_events(g_ctx.event_list, true, -1);
520     if (ret != 0) {
521       ERROR(PMU_PLUGIN ": Failed to setup perf events for the event list.");
522       goto init_error;
523     }
524   } else {
525     WARNING(PMU_PLUGIN
526             ": Events list is empty. No events were setup for monitoring.");
527   }
528
529   return 0;
530
531 init_error:
532
533   pmu_free_events(g_ctx.event_list);
534   sfree(g_ctx.event_list);
535   for (size_t i = 0; i < g_ctx.hw_events_count; i++) {
536     sfree(g_ctx.hw_events[i]);
537   }
538   sfree(g_ctx.hw_events);
539   g_ctx.hw_events_count = 0;
540
541
542   return ret;
543 }
544
545 static int pmu_shutdown(void) {
546
547   DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
548
549   pmu_free_events(g_ctx.event_list);
550   sfree(g_ctx.event_list);
551   for (size_t i = 0; i < g_ctx.hw_events_count; i++) {
552     sfree(g_ctx.hw_events[i]);
553   }
554   sfree(g_ctx.hw_events);
555   g_ctx.hw_events_count = 0;
556
557   return 0;
558 }
559
560 void module_register(void) {
561   plugin_register_init(PMU_PLUGIN, pmu_init);
562   plugin_register_complex_config(PMU_PLUGIN, pmu_config);
563   plugin_register_complex_read(NULL, PMU_PLUGIN, pmu_read, 0, NULL);
564   plugin_register_shutdown(PMU_PLUGIN, pmu_shutdown);
565 }