intel_pmu: Implement performance monitoring plugin
[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 (((PERF_COUNT_HW_CACHE_OP_READ) << 8) | \
37                                 ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
38
39 #define HW_CACHE_WRITE_ACCESS (((PERF_COUNT_HW_CACHE_OP_WRITE) << 8) | \
40                                 ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
41
42 #define HW_CACHE_PREFETCH_ACCESS (((PERF_COUNT_HW_CACHE_OP_PREFETCH) << 8) | \
43                                    ((PERF_COUNT_HW_CACHE_RESULT_ACCESS) << 16))
44
45 #define HW_CACHE_READ_MISS (((PERF_COUNT_HW_CACHE_OP_READ) << 8) | \
46                                 ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
47
48 #define HW_CACHE_WRITE_MISS (((PERF_COUNT_HW_CACHE_OP_WRITE) << 8) | \
49                                 ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
50
51 #define HW_CACHE_PREFETCH_MISS (((PERF_COUNT_HW_CACHE_OP_PREFETCH) << 8) | \
52                                    ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16))
53
54 struct event_info {
55   char      *name;
56   uint64_t  config;
57 };
58 typedef struct event_info event_info_t;
59
60 struct intel_pmu_ctx_s {
61   _Bool hw_cache_events;
62   _Bool kernel_pmu_events;
63   _Bool sw_events;
64   char* hw_specific_events;
65   struct eventlist *event_list;
66 };
67 typedef struct intel_pmu_ctx_s intel_pmu_ctx_t;
68
69 event_info_t g_kernel_pmu_events[] = {
70   { .name = "cpu-cycles",
71     .config = PERF_COUNT_HW_CPU_CYCLES           },
72   { .name = "instructions",
73     .config = PERF_COUNT_HW_INSTRUCTIONS         },
74   { .name = "cache-references",
75     .config = PERF_COUNT_HW_CACHE_REFERENCES     },
76   { .name = "cache-misses",
77     .config = PERF_COUNT_HW_CACHE_MISSES         },
78   { .name = "branches",
79     .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS  },
80   { .name = "branch-misses",
81     .config = PERF_COUNT_HW_BRANCH_MISSES        },
82   { .name = "bus-cycles",
83     .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",
149     .config = PERF_COUNT_SW_CPU_CLOCK        },
150
151   { .name = "task-clock",
152     .config = PERF_COUNT_SW_TASK_CLOCK       },
153
154   { .name = "context-switches",
155     .config = PERF_COUNT_SW_CONTEXT_SWITCHES },
156
157   { .name = "cpu-migrations",
158     .config = PERF_COUNT_SW_CPU_MIGRATIONS   },
159
160   { .name = "page-faults",
161     .config = PERF_COUNT_SW_PAGE_FAULTS      },
162
163   { .name = "minor-faults",
164     .config = PERF_COUNT_SW_PAGE_FAULTS_MIN  },
165
166   { .name = "major-faults",
167     .config = PERF_COUNT_SW_PAGE_FAULTS_MAJ  },
168
169   { .name = "alignment-faults",
170     .config = PERF_COUNT_SW_ALIGNMENT_FAULTS },
171
172   { .name = "emulation-faults",
173     .config = PERF_COUNT_SW_EMULATION_FAULTS },
174 };
175
176 static intel_pmu_ctx_t g_ctx;
177
178 #if COLLECT_DEBUG
179 static void pmu_dump_events() {
180
181   DEBUG(PMU_PLUGIN ": Events:");
182
183   struct event *e;
184
185   for (e = g_ctx.event_list->eventlist; e; e = e->next) {
186     DEBUG(PMU_PLUGIN ":   event       : %s", e->event);
187     DEBUG(PMU_PLUGIN ":     group_lead: %d", e->group_leader);
188     DEBUG(PMU_PLUGIN ":     end_group : %d", e->end_group);
189     DEBUG(PMU_PLUGIN ":     type      : 0x%X", e->attr.type);
190     DEBUG(PMU_PLUGIN ":     config    : 0x%X", (int)e->attr.config);
191     DEBUG(PMU_PLUGIN ":     size      : %d", e->attr.size);
192   }
193
194   return;
195 }
196
197 static void pmu_dump_config(void) {
198
199   DEBUG(PMU_PLUGIN ": Config:");
200   DEBUG(PMU_PLUGIN ":   hw_cache_events   : %d", g_ctx.hw_cache_events);
201   DEBUG(PMU_PLUGIN ":   kernel_pmu_events : %d", g_ctx.kernel_pmu_events);
202   DEBUG(PMU_PLUGIN ":   sw_events         : %d", g_ctx.sw_events);
203   DEBUG(PMU_PLUGIN ":   hw_specific_events: %s", g_ctx.hw_specific_events);
204
205   return;
206 }
207
208 #endif /* COLLECT_DEBUG */
209
210 static int pmu_config(oconfig_item_t *ci) {
211   int ret = 0;
212
213   DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
214
215   for (int i = 0; i < ci->children_num; i++) {
216     oconfig_item_t *child = ci->children + i;
217
218     if (strcasecmp("HWCacheEvents", child->key) == 0) {
219       ret = cf_util_get_boolean(child, &g_ctx.hw_cache_events);
220     } else if (strcasecmp("KernelPMUEvents", child->key) == 0) {
221       ret = cf_util_get_boolean(child, &g_ctx.kernel_pmu_events);
222     } else if (strcasecmp("HWSpecificEvents", child->key) == 0) {
223       ret = cf_util_get_string(child, &g_ctx.hw_specific_events);
224     } else if (strcasecmp("SWEvents", child->key) == 0) {
225       ret = cf_util_get_boolean(child, &g_ctx.sw_events);
226     }else {
227       ERROR(PMU_PLUGIN ": Unknown configuration parameter \"%s\".", child->key);
228       ret = (-1);
229     }
230
231     if (ret != 0) {
232       DEBUG(PMU_PLUGIN ": %s:%d ret=%d", __FUNCTION__, __LINE__, ret);
233       return ret;
234     }
235   }
236
237 #if COLLECT_DEBUG
238   pmu_dump_config();
239 #endif
240
241   return (0);
242 }
243
244 static void pmu_submit_counter(int cpu, char *event, counter_t value) {
245   value_list_t vl = VALUE_LIST_INIT;
246
247   vl.values = &(value_t){.counter = value};
248   vl.values_len = 1;
249
250   sstrncpy(vl.plugin, PMU_PLUGIN, sizeof(vl.plugin));
251   if (cpu == -1) {
252     snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "all");
253   } else {
254     snprintf(vl.plugin_instance, sizeof(vl.plugin_instance), "%d", cpu);
255   }
256   sstrncpy(vl.type, "counter", sizeof(vl.type));
257   sstrncpy(vl.type_instance, event, sizeof(vl.type_instance));
258
259   plugin_dispatch_values(&vl);
260 }
261
262 static int pmu_dispatch_data(void) {
263
264   struct event *e;
265
266   for (e = g_ctx.event_list->eventlist; e; e = e->next) {
267     uint64_t all_value = 0;
268     int event_enabled = 0;
269     for (int i = 0; i < g_ctx.event_list->num_cpus; i++) {
270
271       if (e->efd[i].fd < 0)
272         continue;
273
274       event_enabled++;
275
276       uint64_t value = event_scaled_value(e, i);
277       all_value += value;
278
279       /* dispatch per CPU value */
280       pmu_submit_counter(i, e->event, value);
281     }
282
283     if (event_enabled > 0) {
284       DEBUG(PMU_PLUGIN ": %-20s %'10lu", e->event, all_value);
285       /* dispatch all CPU value */
286       pmu_submit_counter(-1, e->event, all_value);
287     }
288   }
289
290   return (0);
291 }
292
293 static int pmu_read(__attribute__((unused)) user_data_t *ud) {
294   int ret;
295
296   DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
297
298   ret = read_all_events(g_ctx.event_list);
299   if (ret != 0) {
300     DEBUG(PMU_PLUGIN ": Failed to read values of all events.");
301     return (0);
302   }
303
304   ret = pmu_dispatch_data();
305   if (ret != 0) {
306     DEBUG(PMU_PLUGIN ": Failed to dispatch event values.");
307     return (0);
308   }
309
310   return (0);
311 }
312
313 static int pmu_add_events(struct eventlist *el, uint32_t type,
314                           event_info_t *events, int count) {
315
316   for (int i = 0; i < count; i++) {
317     struct event *e = calloc(sizeof(struct event) +
318            sizeof(struct efd) * el->num_cpus, 1);
319     if (e == NULL) {
320       ERROR(PMU_PLUGIN ": Failed to allocate event structure");
321       return (-ENOMEM);
322     }
323
324     e->attr.type = type;
325     e->attr.config = events[i].config;
326     e->attr.size = PERF_ATTR_SIZE_VER0;
327     e->group_leader = false;
328     e->end_group = false;
329     e->next = NULL;
330     if (!el->eventlist)
331       el->eventlist = e;
332     if (el->eventlist_last)
333       el->eventlist_last->next = e;
334     el->eventlist_last = e;
335     e->event = strdup(events[i].name);
336   }
337
338   return (0);
339 }
340
341 static int pmu_parse_events(struct eventlist *el, char *events) {
342   char *s, *tmp;
343
344   events = strdup(events);
345   if (!events)
346     return -1;
347
348   for (s = strtok_r(events, ",", &tmp);
349        s;
350        s = strtok_r(NULL, ",", &tmp)) {
351     bool group_leader = false, end_group = false;
352     int len;
353
354     if (s[0] == '{') {
355       s++;
356       group_leader = true;
357     } else if (len = strlen(s), len > 0 && s[len - 1] == '}') {
358       s[len - 1] = 0;
359       end_group = true;
360     }
361
362     struct event *e = calloc(sizeof(struct event) +
363            sizeof(struct efd) * el->num_cpus, 1);
364     if (e == NULL) {
365       free(events);
366       return (-ENOMEM);
367     }
368
369     if (resolve_event(s, &e->attr) == 0) {
370       e->group_leader = group_leader;
371       e->end_group = end_group;
372       e->next = NULL;
373       if (!el->eventlist)
374         el->eventlist = e;
375       if (el->eventlist_last)
376         el->eventlist_last->next = e;
377       el->eventlist_last = e;
378       e->event = strdup(s);
379     } else {
380       DEBUG(PMU_PLUGIN ": Cannot resolve %s", s);
381       sfree(e);
382     }
383   }
384
385   free(events);
386
387   return (0);
388 }
389
390 static void pmu_free_events(struct eventlist *el) {
391
392   if (el == NULL)
393     return;
394
395   struct event *e = el->eventlist;
396
397   while (e) {
398     struct event *next = e->next;
399     sfree(e);
400     e = next;
401   }
402
403   el->eventlist = NULL;
404 }
405
406 static int pmu_setup_events(struct eventlist *el, bool measure_all,
407                             int measure_pid) {
408   struct event *e, *leader = NULL;
409   int ret = -1;
410
411   for (e = el->eventlist; e; e = e->next) {
412
413     for (int i = 0; i < el->num_cpus; i++) {
414       if (setup_event(e, i, leader, measure_all, measure_pid) < 0) {
415         WARNING(PMU_PLUGIN ": perf event '%s' is not available (cpu=%d).",
416                 e->event, i);
417       } else {
418         /* success if at least one event was set */
419         ret = 0;
420       }
421     }
422
423     if (e->group_leader)
424       leader = e;
425     if (e->end_group)
426       leader = NULL;
427   }
428
429   return ret;
430 }
431
432 static int pmu_init(void) {
433   int ret;
434
435   DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
436
437   g_ctx.event_list = alloc_eventlist();
438   if (g_ctx.event_list == NULL) {
439     ERROR(PMU_PLUGIN ": Failed to allocate event list.");
440     return (-ENOMEM);
441   }
442
443   if (g_ctx.hw_cache_events) {
444     ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_HW_CACHE,
445                        g_hw_cache_events, STATIC_ARRAY_SIZE(g_hw_cache_events));
446     if (ret != 0) {
447       ERROR(PMU_PLUGIN ": Failed to add hw cache events.");
448       goto init_error;
449     }
450   }
451
452   if (g_ctx.kernel_pmu_events) {
453     ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_HARDWARE,
454                    g_kernel_pmu_events, STATIC_ARRAY_SIZE(g_kernel_pmu_events));
455     if (ret != 0) {
456       ERROR(PMU_PLUGIN ": Failed to parse kernel PMU events.");
457       goto init_error;
458     }
459   }
460
461   /* parse events names if config option is present and is not empty */
462   if (g_ctx.hw_specific_events && (strlen(g_ctx.hw_specific_events) != 0)) {
463     ret = pmu_parse_events(g_ctx.event_list, g_ctx.hw_specific_events);
464     if (ret != 0) {
465       ERROR(PMU_PLUGIN ": Failed to parse hw specific events.");
466       goto init_error;
467     }
468   }
469
470   if (g_ctx.sw_events) {
471     ret = pmu_add_events(g_ctx.event_list, PERF_TYPE_SOFTWARE,
472                        g_sw_events, STATIC_ARRAY_SIZE(g_sw_events));
473     if (ret != 0) {
474       ERROR(PMU_PLUGIN ": Failed to add software events.");
475       goto init_error;
476     }
477   }
478
479 #if COLLECT_DEBUG
480   pmu_dump_events();
481 #endif
482
483   if (g_ctx.event_list->eventlist != NULL) {
484     /* measure all processes */
485     ret = pmu_setup_events(g_ctx.event_list, true, -1);
486     if (ret != 0) {
487       ERROR(PMU_PLUGIN ": Failed to setup perf events for the event list.");
488       goto init_error;
489     }
490   } else {
491     WARNING(PMU_PLUGIN
492             ": Events list is empty. No events were setup for monitoring.");
493   }
494
495   return (0);
496
497 init_error:
498
499   pmu_free_events(g_ctx.event_list);
500   sfree(g_ctx.event_list);
501   sfree(g_ctx.hw_specific_events);
502
503   return ret;
504 }
505
506 static int pmu_shutdown(void) {
507
508   DEBUG(PMU_PLUGIN ": %s:%d", __FUNCTION__, __LINE__);
509
510   pmu_free_events(g_ctx.event_list);
511   sfree(g_ctx.event_list);
512   sfree(g_ctx.hw_specific_events);
513
514   return (0);
515 }
516
517 void module_register(void) {
518   plugin_register_init(PMU_PLUGIN, pmu_init);
519   plugin_register_complex_config(PMU_PLUGIN, pmu_config);
520   plugin_register_complex_read(NULL, PMU_PLUGIN, pmu_read, 0, NULL);
521   plugin_register_shutdown(PMU_PLUGIN, pmu_shutdown);
522 }