diff options
Diffstat (limited to 'tools/perf/util/pmus.c')
| -rw-r--r-- | tools/perf/util/pmus.c | 785 |
1 files changed, 549 insertions, 236 deletions
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c index c58ba9fb6a36..98be2eb8f1f0 100644 --- a/tools/perf/util/pmus.c +++ b/tools/perf/util/pmus.c @@ -1,17 +1,26 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/list.h> +#include <linux/list_sort.h> +#include <linux/string.h> #include <linux/zalloc.h> +#include <api/io_dir.h> #include <subcmd/pager.h> #include <sys/types.h> -#include <dirent.h> +#include <ctype.h> #include <pthread.h> #include <string.h> #include <unistd.h> +#include "cpumap.h" #include "debug.h" +#include "drm_pmu.h" #include "evsel.h" #include "pmus.h" #include "pmu.h" +#include "hwmon_pmu.h" +#include "tool_pmu.h" #include "print-events.h" +#include "strbuf.h" +#include "string2.h" /* * core_pmus: A PMU belongs to core_pmus if it's name is "cpu" or it's sysfs @@ -30,8 +39,74 @@ */ static LIST_HEAD(core_pmus); static LIST_HEAD(other_pmus); -static bool read_sysfs_core_pmus; -static bool read_sysfs_all_pmus; +enum perf_tool_pmu_type { + PERF_TOOL_PMU_TYPE_PE_CORE, + PERF_TOOL_PMU_TYPE_PE_OTHER, + PERF_TOOL_PMU_TYPE_TOOL, + PERF_TOOL_PMU_TYPE_HWMON, + PERF_TOOL_PMU_TYPE_DRM, + +#define PERF_TOOL_PMU_TYPE_PE_CORE_MASK (1 << PERF_TOOL_PMU_TYPE_PE_CORE) +#define PERF_TOOL_PMU_TYPE_PE_OTHER_MASK (1 << PERF_TOOL_PMU_TYPE_PE_OTHER) +#define PERF_TOOL_PMU_TYPE_TOOL_MASK (1 << PERF_TOOL_PMU_TYPE_TOOL) +#define PERF_TOOL_PMU_TYPE_HWMON_MASK (1 << PERF_TOOL_PMU_TYPE_HWMON) +#define PERF_TOOL_PMU_TYPE_DRM_MASK (1 << PERF_TOOL_PMU_TYPE_DRM) + +#define PERF_TOOL_PMU_TYPE_ALL_MASK (PERF_TOOL_PMU_TYPE_PE_CORE_MASK | \ + PERF_TOOL_PMU_TYPE_PE_OTHER_MASK | \ + PERF_TOOL_PMU_TYPE_TOOL_MASK | \ + PERF_TOOL_PMU_TYPE_HWMON_MASK | \ + PERF_TOOL_PMU_TYPE_DRM_MASK) +}; +static unsigned int read_pmu_types; + +static void pmu_read_sysfs(unsigned int to_read_pmus); + +size_t pmu_name_len_no_suffix(const char *str) +{ + int orig_len, len; + bool has_hex_digits = false; + + orig_len = len = strlen(str); + + /* Count trailing digits. */ + while (len > 0 && isxdigit(str[len - 1])) { + if (!isdigit(str[len - 1])) + has_hex_digits = true; + len--; + } + + if (len > 0 && len != orig_len && str[len - 1] == '_') { + /* + * There is a '_{num}' suffix. For decimal suffixes any length + * will do, for hexadecimal ensure more than 2 hex digits so + * that S390's cpum_cf PMU doesn't match. + */ + if (!has_hex_digits || (orig_len - len) > 2) + return len - 1; + } + /* Use the full length. */ + return orig_len; +} + +int pmu_name_cmp(const char *lhs_pmu_name, const char *rhs_pmu_name) +{ + unsigned long long lhs_num = 0, rhs_num = 0; + size_t lhs_pmu_name_len = pmu_name_len_no_suffix(lhs_pmu_name); + size_t rhs_pmu_name_len = pmu_name_len_no_suffix(rhs_pmu_name); + int ret = strncmp(lhs_pmu_name, rhs_pmu_name, + lhs_pmu_name_len < rhs_pmu_name_len ? lhs_pmu_name_len : rhs_pmu_name_len); + + if (lhs_pmu_name_len != rhs_pmu_name_len || ret != 0 || lhs_pmu_name_len == 0) + return ret; + + if (lhs_pmu_name_len + 1 < strlen(lhs_pmu_name)) + lhs_num = strtoull(&lhs_pmu_name[lhs_pmu_name_len + 1], NULL, 16); + if (rhs_pmu_name_len + 1 < strlen(rhs_pmu_name)) + rhs_num = strtoull(&rhs_pmu_name[rhs_pmu_name_len + 1], NULL, 16); + + return lhs_num < rhs_num ? -1 : (lhs_num > rhs_num ? 1 : 0); +} void perf_pmus__destroy(void) { @@ -47,8 +122,7 @@ void perf_pmus__destroy(void) perf_pmu__delete(pmu); } - read_sysfs_core_pmus = false; - read_sysfs_all_pmus = false; + read_pmu_types = 0; } static struct perf_pmu *pmu_find(const char *name) @@ -74,6 +148,7 @@ struct perf_pmu *perf_pmus__find(const char *name) struct perf_pmu *pmu; int dirfd; bool core_pmu; + unsigned int to_read_pmus = 0; /* * Once PMU is loaded it stays in the list, @@ -84,18 +159,41 @@ struct perf_pmu *perf_pmus__find(const char *name) if (pmu) return pmu; - if (read_sysfs_all_pmus) + if (read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK) return NULL; core_pmu = is_pmu_core(name); - if (core_pmu && read_sysfs_core_pmus) + if (core_pmu && (read_pmu_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK)) return NULL; dirfd = perf_pmu__event_source_devices_fd(); - pmu = perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name); + pmu = perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name, + /*eager_load=*/false); close(dirfd); - return pmu; + if (pmu) + return pmu; + + /* Looking up an individual perf event PMU failed, check if a tool PMU should be read. */ + if (!strncmp(name, "hwmon_", 6)) + to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK; + else if (!strncmp(name, "drm_", 4)) + to_read_pmus |= PERF_TOOL_PMU_TYPE_DRM_MASK; + else if (!strcmp(name, "tool")) + to_read_pmus |= PERF_TOOL_PMU_TYPE_TOOL_MASK; + + if (to_read_pmus) { + pmu_read_sysfs(to_read_pmus); + pmu = pmu_find(name); + if (pmu) + return pmu; + } + /* Read all necessary PMUs from sysfs and see if the PMU is found. */ + to_read_pmus = PERF_TOOL_PMU_TYPE_PE_CORE_MASK; + if (!core_pmu) + to_read_pmus |= PERF_TOOL_PMU_TYPE_PE_OTHER_MASK; + pmu_read_sysfs(to_read_pmus); + return pmu_find(name); } static struct perf_pmu *perf_pmu__find2(int dirfd, const char *name) @@ -112,55 +210,82 @@ static struct perf_pmu *perf_pmu__find2(int dirfd, const char *name) if (pmu) return pmu; - if (read_sysfs_all_pmus) + if (read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK) return NULL; core_pmu = is_pmu_core(name); - if (core_pmu && read_sysfs_core_pmus) + if (core_pmu && (read_pmu_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK)) return NULL; - return perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name); + return perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name, + /*eager_load=*/false); } -/* Add all pmus in sysfs to pmu list: */ -static void pmu_read_sysfs(bool core_only) +static int pmus_cmp(void *priv __maybe_unused, + const struct list_head *lhs, const struct list_head *rhs) { - int fd; - DIR *dir; - struct dirent *dent; + struct perf_pmu *lhs_pmu = container_of(lhs, struct perf_pmu, list); + struct perf_pmu *rhs_pmu = container_of(rhs, struct perf_pmu, list); - if (read_sysfs_all_pmus || (core_only && read_sysfs_core_pmus)) - return; + return pmu_name_cmp(lhs_pmu->name ?: "", rhs_pmu->name ?: ""); +} - fd = perf_pmu__event_source_devices_fd(); - if (fd < 0) - return; +/* Add all pmus in sysfs to pmu list: */ +static void pmu_read_sysfs(unsigned int to_read_types) +{ + struct perf_pmu *tool_pmu; - dir = fdopendir(fd); - if (!dir) { - close(fd); + if ((read_pmu_types & to_read_types) == to_read_types) { + /* All requested PMU types have been read. */ return; } - while ((dent = readdir(dir))) { - if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) - continue; - if (core_only && !is_pmu_core(dent->d_name)) - continue; - /* add to static LIST_HEAD(core_pmus) or LIST_HEAD(other_pmus): */ - perf_pmu__find2(fd, dent->d_name); - } + if (to_read_types & (PERF_TOOL_PMU_TYPE_PE_CORE_MASK | PERF_TOOL_PMU_TYPE_PE_OTHER_MASK)) { + int fd = perf_pmu__event_source_devices_fd(); + struct io_dir dir; + struct io_dirent64 *dent; + bool core_only = (to_read_types & PERF_TOOL_PMU_TYPE_PE_OTHER_MASK) == 0; + + if (fd < 0) + goto skip_pe_pmus; + + io_dir__init(&dir, fd); - closedir(dir); - if (list_empty(&core_pmus)) { + while ((dent = io_dir__readdir(&dir)) != NULL) { + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) + continue; + if (core_only && !is_pmu_core(dent->d_name)) + continue; + /* add to static LIST_HEAD(core_pmus) or LIST_HEAD(other_pmus): */ + perf_pmu__find2(fd, dent->d_name); + } + + close(fd); + } +skip_pe_pmus: + if ((to_read_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK) && list_empty(&core_pmus)) { if (!perf_pmu__create_placeholder_core_pmu(&core_pmus)) pr_err("Failure to set up any core PMUs\n"); } - if (!list_empty(&core_pmus)) { - read_sysfs_core_pmus = true; - if (!core_only) - read_sysfs_all_pmus = true; + list_sort(NULL, &core_pmus, pmus_cmp); + + if ((to_read_types & PERF_TOOL_PMU_TYPE_TOOL_MASK) != 0 && + (read_pmu_types & PERF_TOOL_PMU_TYPE_TOOL_MASK) == 0) { + tool_pmu = tool_pmu__new(); + if (tool_pmu) + list_add_tail(&tool_pmu->list, &other_pmus); } + if ((to_read_types & PERF_TOOL_PMU_TYPE_HWMON_MASK) != 0 && + (read_pmu_types & PERF_TOOL_PMU_TYPE_HWMON_MASK) == 0) + perf_pmus__read_hwmon_pmus(&other_pmus); + + if ((to_read_types & PERF_TOOL_PMU_TYPE_DRM_MASK) != 0 && + (read_pmu_types & PERF_TOOL_PMU_TYPE_DRM_MASK) == 0) + perf_pmus__read_drm_pmus(&other_pmus); + + list_sort(NULL, &other_pmus, pmus_cmp); + + read_pmu_types |= to_read_types; } static struct perf_pmu *__perf_pmus__find_by_type(unsigned int type) @@ -181,12 +306,23 @@ static struct perf_pmu *__perf_pmus__find_by_type(unsigned int type) struct perf_pmu *perf_pmus__find_by_type(unsigned int type) { + unsigned int to_read_pmus; struct perf_pmu *pmu = __perf_pmus__find_by_type(type); - if (pmu || read_sysfs_all_pmus) + if (pmu || (read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK)) return pmu; - pmu_read_sysfs(/*core_only=*/false); + if (type >= PERF_PMU_TYPE_PE_START && type <= PERF_PMU_TYPE_PE_END) { + to_read_pmus = PERF_TOOL_PMU_TYPE_PE_CORE_MASK | + PERF_TOOL_PMU_TYPE_PE_OTHER_MASK; + } else if (type >= PERF_PMU_TYPE_DRM_START && type <= PERF_PMU_TYPE_DRM_END) { + to_read_pmus = PERF_TOOL_PMU_TYPE_DRM_MASK; + } else if (type >= PERF_PMU_TYPE_HWMON_START && type <= PERF_PMU_TYPE_HWMON_END) { + to_read_pmus = PERF_TOOL_PMU_TYPE_HWMON_MASK; + } else { + to_read_pmus = PERF_TOOL_PMU_TYPE_TOOL_MASK; + } + pmu_read_sysfs(to_read_pmus); pmu = __perf_pmus__find_by_type(type); return pmu; } @@ -200,7 +336,7 @@ struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu) bool use_core_pmus = !pmu || pmu->is_core; if (!pmu) { - pmu_read_sysfs(/*core_only=*/false); + pmu_read_sysfs(PERF_TOOL_PMU_TYPE_ALL_MASK); pmu = list_prepare_entry(pmu, &core_pmus, list); } if (use_core_pmus) { @@ -218,8 +354,8 @@ struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu) struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu) { if (!pmu) { - pmu_read_sysfs(/*core_only=*/true); - pmu = list_prepare_entry(pmu, &core_pmus, list); + pmu_read_sysfs(PERF_TOOL_PMU_TYPE_PE_CORE_MASK); + return list_first_entry_or_null(&core_pmus, typeof(*pmu), list); } list_for_each_entry_continue(pmu, &core_pmus, list) return pmu; @@ -227,6 +363,129 @@ struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu) return NULL; } +struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmu *pmu, const char *event) +{ + bool use_core_pmus = !pmu || pmu->is_core; + + if (!pmu) { + /* Hwmon filename values that aren't used. */ + enum hwmon_type type; + int number; + /* + * Core PMUs, other sysfs PMUs and tool PMU can take all event + * types or aren't wother optimizing for. + */ + unsigned int to_read_pmus = PERF_TOOL_PMU_TYPE_PE_CORE_MASK | + PERF_TOOL_PMU_TYPE_PE_OTHER_MASK | + PERF_TOOL_PMU_TYPE_TOOL_MASK; + + /* Could the event be a hwmon event? */ + if (parse_hwmon_filename(event, &type, &number, /*item=*/NULL, /*alarm=*/NULL)) + to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK; + + /* Could the event be a DRM event? */ + if (strlen(event) > 4 && strncmp("drm-", event, 4) == 0) + to_read_pmus |= PERF_TOOL_PMU_TYPE_DRM_MASK; + + pmu_read_sysfs(to_read_pmus); + pmu = list_prepare_entry(pmu, &core_pmus, list); + } + if (use_core_pmus) { + list_for_each_entry_continue(pmu, &core_pmus, list) + return pmu; + + pmu = NULL; + pmu = list_prepare_entry(pmu, &other_pmus, list); + } + list_for_each_entry_continue(pmu, &other_pmus, list) + return pmu; + return NULL; +} + +struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const char *wildcard) +{ + bool use_core_pmus = !pmu || pmu->is_core; + + if (!pmu) { + /* + * Core PMUs, other sysfs PMUs and tool PMU can have any name or + * aren't wother optimizing for. + */ + unsigned int to_read_pmus = PERF_TOOL_PMU_TYPE_PE_CORE_MASK | + PERF_TOOL_PMU_TYPE_PE_OTHER_MASK | + PERF_TOOL_PMU_TYPE_TOOL_MASK; + + /* + * Hwmon PMUs have an alias from a sysfs name like hwmon0, + * hwmon1, etc. or have a name of hwmon_<name>. They therefore + * can only have a wildcard match if the wildcard begins with + * "hwmon". Similarly drm PMUs must start "drm_", avoid reading + * such events unless the PMU could match. + */ + if (strisglob(wildcard)) { + to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK | + PERF_TOOL_PMU_TYPE_DRM_MASK; + } else if (strlen(wildcard) >= 4 && strncmp("drm_", wildcard, 4) == 0) { + to_read_pmus |= PERF_TOOL_PMU_TYPE_DRM_MASK; + } else if (strlen(wildcard) >= 5 && strncmp("hwmon", wildcard, 5) == 0) { + to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK; + } + + pmu_read_sysfs(to_read_pmus); + pmu = list_prepare_entry(pmu, &core_pmus, list); + } + if (use_core_pmus) { + list_for_each_entry_continue(pmu, &core_pmus, list) { + if (perf_pmu__wildcard_match(pmu, wildcard)) + return pmu; + } + pmu = NULL; + pmu = list_prepare_entry(pmu, &other_pmus, list); + } + list_for_each_entry_continue(pmu, &other_pmus, list) { + if (perf_pmu__wildcard_match(pmu, wildcard)) + return pmu; + } + return NULL; +} + +static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu) +{ + bool use_core_pmus = !pmu || pmu->is_core; + int last_pmu_name_len = 0; + const char *last_pmu_name = (pmu && pmu->name) ? pmu->name : ""; + + if (!pmu) { + pmu_read_sysfs(PERF_TOOL_PMU_TYPE_ALL_MASK); + pmu = list_prepare_entry(pmu, &core_pmus, list); + } else + last_pmu_name_len = pmu_name_len_no_suffix(pmu->name ?: ""); + + if (use_core_pmus) { + list_for_each_entry_continue(pmu, &core_pmus, list) { + int pmu_name_len = pmu_name_len_no_suffix(pmu->name ?: ""); + + if (last_pmu_name_len == pmu_name_len && + !strncmp(last_pmu_name, pmu->name ?: "", pmu_name_len)) + continue; + + return pmu; + } + pmu = NULL; + pmu = list_prepare_entry(pmu, &other_pmus, list); + } + list_for_each_entry_continue(pmu, &other_pmus, list) { + int pmu_name_len = pmu_name_len_no_suffix(pmu->name ?: ""); + + if (last_pmu_name_len == pmu_name_len && + !strncmp(last_pmu_name, pmu->name ?: "", pmu_name_len)) + continue; + + return pmu; + } + return NULL; +} + const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(const char *str) { struct perf_pmu *pmu = NULL; @@ -248,229 +507,163 @@ const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(const char *str) return NULL; } -int __weak perf_pmus__num_mem_pmus(void) -{ - /* All core PMUs are for mem events. */ - return perf_pmus__num_core_pmus(); -} - /** Struct for ordering events as output in perf list. */ struct sevent { /** PMU for event. */ const struct perf_pmu *pmu; - /** - * Optional event for name, desc, etc. If not present then this is a - * selectable PMU and the event name is shown as "//". - */ - const struct perf_pmu_alias *event; - /** Is the PMU for the CPU? */ - bool is_cpu; + const char *name; + const char* alias; + const char *scale_unit; + const char *desc; + const char *long_desc; + const char *encoding_desc; + const char *topic; + const char *pmu_name; + const char *event_type_desc; + bool deprecated; }; static int cmp_sevent(const void *a, const void *b) { const struct sevent *as = a; const struct sevent *bs = b; - const char *a_pmu_name = NULL, *b_pmu_name = NULL; - const char *a_name = "//", *a_desc = NULL, *a_topic = ""; - const char *b_name = "//", *b_desc = NULL, *b_topic = ""; + bool a_iscpu, b_iscpu; int ret; - if (as->event) { - a_name = as->event->name; - a_desc = as->event->desc; - a_topic = as->event->topic ?: ""; - a_pmu_name = as->event->pmu_name; - } - if (bs->event) { - b_name = bs->event->name; - b_desc = bs->event->desc; - b_topic = bs->event->topic ?: ""; - b_pmu_name = bs->event->pmu_name; - } /* Put extra events last. */ - if (!!a_desc != !!b_desc) - return !!a_desc - !!b_desc; + if (!!as->desc != !!bs->desc) + return !!as->desc - !!bs->desc; /* Order by topics. */ - ret = strcmp(a_topic, b_topic); + ret = strcmp(as->topic ?: "", bs->topic ?: ""); if (ret) return ret; /* Order CPU core events to be first */ - if (as->is_cpu != bs->is_cpu) - return as->is_cpu ? -1 : 1; + a_iscpu = as->pmu ? as->pmu->is_core : true; + b_iscpu = bs->pmu ? bs->pmu->is_core : true; + if (a_iscpu != b_iscpu) + return a_iscpu ? -1 : 1; /* Order by PMU name. */ if (as->pmu != bs->pmu) { - a_pmu_name = a_pmu_name ?: (as->pmu->name ?: ""); - b_pmu_name = b_pmu_name ?: (bs->pmu->name ?: ""); - ret = strcmp(a_pmu_name, b_pmu_name); + ret = strcmp(as->pmu_name ?: "", bs->pmu_name ?: ""); if (ret) return ret; } /* Order by event name. */ - return strcmp(a_name, b_name); + return strcmp(as->name, bs->name); } -static bool pmu_alias_is_duplicate(struct sevent *alias_a, - struct sevent *alias_b) +static bool pmu_alias_is_duplicate(struct sevent *a, struct sevent *b) { - const char *a_pmu_name = NULL, *b_pmu_name = NULL; - const char *a_name = "//", *b_name = "//"; - - - if (alias_a->event) { - a_name = alias_a->event->name; - a_pmu_name = alias_a->event->pmu_name; - } - if (alias_b->event) { - b_name = alias_b->event->name; - b_pmu_name = alias_b->event->pmu_name; - } - /* Different names -> never duplicates */ - if (strcmp(a_name, b_name)) + if (strcmp(a->name ?: "//", b->name ?: "//")) return false; /* Don't remove duplicates for different PMUs */ - a_pmu_name = a_pmu_name ?: (alias_a->pmu->name ?: ""); - b_pmu_name = b_pmu_name ?: (alias_b->pmu->name ?: ""); - return strcmp(a_pmu_name, b_pmu_name) == 0; + return strcmp(a->pmu_name, b->pmu_name) == 0; } -static int sub_non_neg(int a, int b) -{ - if (b > a) - return 0; - return a - b; -} +struct events_callback_state { + struct sevent *aliases; + size_t aliases_len; + size_t index; +}; -static char *format_alias(char *buf, int len, const struct perf_pmu *pmu, - const struct perf_pmu_alias *alias) +static int perf_pmus__print_pmu_events__callback(void *vstate, + struct pmu_event_info *info) { - struct parse_events_term *term; - int used = snprintf(buf, len, "%s/%s", pmu->name, alias->name); + struct events_callback_state *state = vstate; + struct sevent *s; - list_for_each_entry(term, &alias->terms, list) { - if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR) - used += snprintf(buf + used, sub_non_neg(len, used), - ",%s=%s", term->config, - term->val.str); + if (state->index >= state->aliases_len) { + pr_err("Unexpected event %s/%s/\n", info->pmu->name, info->name); + return 1; } - - if (sub_non_neg(len, used) > 0) { - buf[used] = '/'; - used++; - } - if (sub_non_neg(len, used) > 0) { - buf[used] = '\0'; - used++; - } else - buf[len - 1] = '\0'; - - return buf; + assert(info->pmu != NULL || info->name != NULL); + s = &state->aliases[state->index]; + s->pmu = info->pmu; +#define COPY_STR(str) s->str = info->str ? strdup(info->str) : NULL + COPY_STR(name); + COPY_STR(alias); + COPY_STR(scale_unit); + COPY_STR(desc); + COPY_STR(long_desc); + COPY_STR(encoding_desc); + COPY_STR(topic); + COPY_STR(pmu_name); + COPY_STR(event_type_desc); +#undef COPY_STR + s->deprecated = info->deprecated; + state->index++; + return 0; } void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *print_state) { struct perf_pmu *pmu; - struct perf_pmu_alias *event; - char buf[1024]; int printed = 0; - int len, j; + int len; struct sevent *aliases; + struct events_callback_state state; + bool skip_duplicate_pmus = print_cb->skip_duplicate_pmus(print_state); + struct perf_pmu *(*scan_fn)(struct perf_pmu *); + + if (skip_duplicate_pmus) + scan_fn = perf_pmus__scan_skip_duplicates; + else + scan_fn = perf_pmus__scan; pmu = NULL; len = 0; - while ((pmu = perf_pmus__scan(pmu)) != NULL) { - list_for_each_entry(event, &pmu->aliases, list) - len++; - if (pmu->selectable) - len++; - } + while ((pmu = scan_fn(pmu)) != NULL) + len += perf_pmu__num_events(pmu); + aliases = zalloc(sizeof(struct sevent) * len); if (!aliases) { pr_err("FATAL: not enough memory to print PMU events\n"); return; } pmu = NULL; - j = 0; - while ((pmu = perf_pmus__scan(pmu)) != NULL) { - bool is_cpu = pmu->is_core; - - list_for_each_entry(event, &pmu->aliases, list) { - aliases[j].event = event; - aliases[j].pmu = pmu; - aliases[j].is_cpu = is_cpu; - j++; - } - if (pmu->selectable) { - aliases[j].event = NULL; - aliases[j].pmu = pmu; - aliases[j].is_cpu = is_cpu; - j++; - } + state = (struct events_callback_state) { + .aliases = aliases, + .aliases_len = len, + .index = 0, + }; + while ((pmu = scan_fn(pmu)) != NULL) { + perf_pmu__for_each_event(pmu, skip_duplicate_pmus, &state, + perf_pmus__print_pmu_events__callback); } - len = j; qsort(aliases, len, sizeof(struct sevent), cmp_sevent); - for (j = 0; j < len; j++) { - const char *name, *alias = NULL, *scale_unit = NULL, - *desc = NULL, *long_desc = NULL, - *encoding_desc = NULL, *topic = NULL, - *pmu_name = NULL; - bool deprecated = false; - size_t buf_used; - + for (int j = 0; j < len; j++) { /* Skip duplicates */ - if (j > 0 && pmu_alias_is_duplicate(&aliases[j], &aliases[j - 1])) - continue; + if (j < len - 1 && pmu_alias_is_duplicate(&aliases[j], &aliases[j + 1])) + goto free; - if (!aliases[j].event) { - /* A selectable event. */ - pmu_name = aliases[j].pmu->name; - buf_used = snprintf(buf, sizeof(buf), "%s//", pmu_name) + 1; - name = buf; - } else { - if (aliases[j].event->desc) { - name = aliases[j].event->name; - buf_used = 0; - } else { - name = format_alias(buf, sizeof(buf), aliases[j].pmu, - aliases[j].event); - if (aliases[j].is_cpu) { - alias = name; - name = aliases[j].event->name; - } - buf_used = strlen(buf) + 1; - } - pmu_name = aliases[j].event->pmu_name ?: (aliases[j].pmu->name ?: ""); - if (strlen(aliases[j].event->unit) || aliases[j].event->scale != 1.0) { - scale_unit = buf + buf_used; - buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used, - "%G%s", aliases[j].event->scale, - aliases[j].event->unit) + 1; - } - desc = aliases[j].event->desc; - long_desc = aliases[j].event->long_desc; - topic = aliases[j].event->topic; - encoding_desc = buf + buf_used; - buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used, - "%s/%s/", pmu_name, aliases[j].event->str) + 1; - deprecated = aliases[j].event->deprecated; - } print_cb->print_event(print_state, - pmu_name, - topic, - name, - alias, - scale_unit, - deprecated, - "Kernel PMU event", - desc, - long_desc, - encoding_desc); + aliases[j].topic, + aliases[j].pmu_name, + aliases[j].pmu->type, + aliases[j].name, + aliases[j].alias, + aliases[j].scale_unit, + aliases[j].deprecated, + aliases[j].event_type_desc, + aliases[j].desc, + aliases[j].long_desc, + aliases[j].encoding_desc); +free: + zfree(&aliases[j].name); + zfree(&aliases[j].alias); + zfree(&aliases[j].scale_unit); + zfree(&aliases[j].desc); + zfree(&aliases[j].long_desc); + zfree(&aliases[j].encoding_desc); + zfree(&aliases[j].topic); + zfree(&aliases[j].pmu_name); + zfree(&aliases[j].event_type_desc); } if (printed && pager_in_use()) printf("\n"); @@ -478,6 +671,100 @@ void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *p zfree(&aliases); } +struct build_format_string_args { + struct strbuf short_string; + struct strbuf long_string; + int num_formats; +}; + +static int build_format_string(void *state, const char *name, int config, + const unsigned long *bits) +{ + struct build_format_string_args *args = state; + unsigned int num_bits; + int ret1, ret2 = 0; + + (void)config; + args->num_formats++; + if (args->num_formats > 1) { + strbuf_addch(&args->long_string, ','); + if (args->num_formats < 4) + strbuf_addch(&args->short_string, ','); + } + num_bits = bits ? bitmap_weight(bits, PERF_PMU_FORMAT_BITS) : 0; + if (num_bits <= 1) { + ret1 = strbuf_addf(&args->long_string, "%s", name); + if (args->num_formats < 4) + ret2 = strbuf_addf(&args->short_string, "%s", name); + } else if (num_bits > 8) { + ret1 = strbuf_addf(&args->long_string, "%s=0..0x%llx", name, + ULLONG_MAX >> (64 - num_bits)); + if (args->num_formats < 4) { + ret2 = strbuf_addf(&args->short_string, "%s=0..0x%llx", name, + ULLONG_MAX >> (64 - num_bits)); + } + } else { + ret1 = strbuf_addf(&args->long_string, "%s=0..%llu", name, + ULLONG_MAX >> (64 - num_bits)); + if (args->num_formats < 4) { + ret2 = strbuf_addf(&args->short_string, "%s=0..%llu", name, + ULLONG_MAX >> (64 - num_bits)); + } + } + return ret1 < 0 ? ret1 : (ret2 < 0 ? ret2 : 0); +} + +void perf_pmus__print_raw_pmu_events(const struct print_callbacks *print_cb, void *print_state) +{ + bool skip_duplicate_pmus = print_cb->skip_duplicate_pmus(print_state); + struct perf_pmu *(*scan_fn)(struct perf_pmu *); + struct perf_pmu *pmu = NULL; + + if (skip_duplicate_pmus) + scan_fn = perf_pmus__scan_skip_duplicates; + else + scan_fn = perf_pmus__scan; + + while ((pmu = scan_fn(pmu)) != NULL) { + struct build_format_string_args format_args = { + .short_string = STRBUF_INIT, + .long_string = STRBUF_INIT, + .num_formats = 0, + }; + int len = pmu_name_len_no_suffix(pmu->name); + const char *desc = "(see 'man perf-list' or 'man perf-record' on how to encode it)"; + + if (!pmu->is_core) + desc = NULL; + + strbuf_addf(&format_args.short_string, "%.*s/", len, pmu->name); + strbuf_addf(&format_args.long_string, "%.*s/", len, pmu->name); + perf_pmu__for_each_format(pmu, &format_args, build_format_string); + + if (format_args.num_formats > 3) + strbuf_addf(&format_args.short_string, ",.../modifier"); + else + strbuf_addf(&format_args.short_string, "/modifier"); + + strbuf_addf(&format_args.long_string, "/modifier"); + print_cb->print_event(print_state, + /*topic=*/NULL, + /*pmu_name=*/NULL, + pmu->type, + format_args.short_string.buf, + /*event_alias=*/NULL, + /*scale_unit=*/NULL, + /*deprecated=*/false, + "Raw event descriptor", + desc, + /*long_desc=*/NULL, + format_args.long_string.buf); + + strbuf_release(&format_args.short_string); + strbuf_release(&format_args.long_string); + } +} + bool perf_pmus__have_event(const char *pname, const char *name) { struct perf_pmu *pmu = perf_pmus__find(pname); @@ -529,46 +816,72 @@ bool perf_pmus__supports_extended_type(void) return perf_pmus__do_support_extended_type; } -char *perf_pmus__default_pmu_name(void) +struct perf_pmu *perf_pmus__find_by_attr(const struct perf_event_attr *attr) { - int fd; - DIR *dir; - struct dirent *dent; - char *result = NULL; + struct perf_pmu *pmu = perf_pmus__find_by_type(attr->type); + u32 type = attr->type; + bool legacy_core_type = type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE; - if (!list_empty(&core_pmus)) - return strdup(list_first_entry(&core_pmus, struct perf_pmu, list)->name); + if (!pmu && legacy_core_type && perf_pmus__supports_extended_type()) { + type = attr->config >> PERF_PMU_TYPE_SHIFT; - fd = perf_pmu__event_source_devices_fd(); - if (fd < 0) - return strdup("cpu"); - - dir = fdopendir(fd); - if (!dir) { - close(fd); - return strdup("cpu"); + pmu = perf_pmus__find_by_type(type); } - - while ((dent = readdir(dir))) { - if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) - continue; - if (is_pmu_core(dent->d_name)) { - result = strdup(dent->d_name); - break; - } + if (!pmu && (legacy_core_type || type == PERF_TYPE_RAW)) { + /* + * For legacy events, if there was no extended type info then + * assume the PMU is the first core PMU. + * + * On architectures like ARM there is no sysfs PMU with type + * PERF_TYPE_RAW, assume the RAW events are going to be handled + * by the first core PMU. + */ + pmu = perf_pmus__find_core_pmu(); } - - closedir(dir); - return result ?: strdup("cpu"); + return pmu; } struct perf_pmu *evsel__find_pmu(const struct evsel *evsel) { struct perf_pmu *pmu = evsel->pmu; - if (!pmu) { - pmu = perf_pmus__find_by_type(evsel->core.attr.type); - ((struct evsel *)evsel)->pmu = pmu; - } + if (pmu) + return pmu; + + pmu = perf_pmus__find_by_attr(&evsel->core.attr); + ((struct evsel *)evsel)->pmu = pmu; return pmu; } + +struct perf_pmu *perf_pmus__find_core_pmu(void) +{ + return perf_pmus__scan_core(NULL); +} + +struct perf_pmu *perf_pmus__add_test_pmu(int test_sysfs_dirfd, const char *name) +{ + /* + * Some PMU functions read from the sysfs mount point, so care is + * needed, hence passing the eager_load flag to load things like the + * format files. + */ + return perf_pmu__lookup(&other_pmus, test_sysfs_dirfd, name, /*eager_load=*/true); +} + +struct perf_pmu *perf_pmus__add_test_hwmon_pmu(const char *hwmon_dir, + const char *sysfs_name, + const char *name) +{ + return hwmon_pmu__new(&other_pmus, hwmon_dir, sysfs_name, name); +} + +struct perf_pmu *perf_pmus__fake_pmu(void) +{ + static struct perf_pmu fake = { + .name = "fake", + .type = PERF_PMU_TYPE_FAKE, + .format = LIST_HEAD_INIT(fake.format), + }; + + return &fake; +} |
