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