diff options
Diffstat (limited to 'tools/perf/util/pmu.c')
| -rw-r--r-- | tools/perf/util/pmu.c | 1187 |
1 files changed, 849 insertions, 338 deletions
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 888ce9912275..956ea273c2c7 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -12,17 +12,24 @@ #include <stdbool.h> #include <dirent.h> #include <api/fs/fs.h> +#include <api/io.h> +#include <api/io_dir.h> #include <locale.h> #include <fnmatch.h> #include <math.h> #include "debug.h" #include "evsel.h" #include "pmu.h" +#include "drm_pmu.h" +#include "hwmon_pmu.h" #include "pmus.h" +#include "tool_pmu.h" +#include "tp_pmu.h" #include <util/pmu-bison.h> #include <util/pmu-flex.h> #include "parse-events.h" #include "print-events.h" +#include "hashmap.h" #include "header.h" #include "string2.h" #include "strbuf.h" @@ -30,19 +37,15 @@ #include "util/evsel_config.h" #include <regex.h> -struct perf_pmu perf_pmu__fake = { - .name = "fake", -}; - #define UNIT_MAX_LEN 31 /* max length for event unit name */ enum event_source { - /* An event loaded from /sys/devices/<pmu>/events. */ + /* An event loaded from /sys/bus/event_source/devices/<pmu>/events. */ EVENT_SRC_SYSFS, /* An event loaded from a CPUID matched json file. */ EVENT_SRC_CPU_JSON, /* - * An event loaded from a /sys/devices/<pmu>/identifier matched json + * An event loaded from a /sys/bus/event_source/devices/<pmu>/identifier matched json * file. */ EVENT_SRC_SYS_JSON, @@ -64,10 +67,13 @@ struct perf_pmu_alias { * json events. */ char *topic; - /** @terms: Owned list of the original parsed parameters. */ - struct parse_events_terms terms; - /** @list: List element of struct perf_pmu aliases. */ - struct list_head list; + /** @terms: Owned copy of the event terms. */ + char *terms; + /** + * @legacy_terms: If the event aliases a legacy event, holds a copy + * ofthe legacy event string. + */ + char *legacy_terms; /** * @pmu_name: The name copied from the json struct pmu_event. This can * differ from the PMU name as it won't have suffixes. @@ -77,6 +83,12 @@ struct perf_pmu_alias { char unit[UNIT_MAX_LEN+1]; /** @scale: Value to scale read counter values by. */ double scale; + /** @retirement_latency_mean: Value to be given for unsampled retirement latency mean. */ + double retirement_latency_mean; + /** @retirement_latency_min: Value to be given for unsampled retirement latency min. */ + double retirement_latency_min; + /** @retirement_latency_max: Value to be given for unsampled retirement latency max. */ + double retirement_latency_max; /** * @per_pkg: Does the file * <sysfs>/bus/event_source/devices/<pmu_name>/events/<name>.per-pkg or @@ -94,6 +106,12 @@ struct perf_pmu_alias { * default. */ bool deprecated; + /** + * @legacy_deprecated_checked: Legacy events may not be supported by the + * PMU need to be checked. If they aren't supported they are marked + * deprecated. + */ + bool legacy_deprecated_checked; /** @from_sysfs: Was the alias from sysfs or a json event? */ bool from_sysfs; /** @info_loaded: Have the scale, unit and other values been read from disk? */ @@ -196,19 +214,17 @@ static void perf_pmu_format__load(const struct perf_pmu *pmu, struct perf_pmu_fo */ static int perf_pmu__format_parse(struct perf_pmu *pmu, int dirfd, bool eager_load) { - struct dirent *evt_ent; - DIR *format_dir; + struct io_dirent64 *evt_ent; + struct io_dir format_dir; int ret = 0; - format_dir = fdopendir(dirfd); - if (!format_dir) - return -EINVAL; + io_dir__init(&format_dir, dirfd); - while ((evt_ent = readdir(format_dir)) != NULL) { + while ((evt_ent = io_dir__readdir(&format_dir)) != NULL) { struct perf_pmu_format *format; char *name = evt_ent->d_name; - if (!strcmp(name, ".") || !strcmp(name, "..")) + if (io_dir__is_dir(&format_dir, evt_ent)) continue; format = perf_pmu__new_format(&pmu->format, name); @@ -235,7 +251,7 @@ static int perf_pmu__format_parse(struct perf_pmu *pmu, int dirfd, bool eager_lo } } - closedir(format_dir); + close(format_dir.dirfd); return ret; } @@ -259,7 +275,7 @@ static int pmu_format(struct perf_pmu *pmu, int dirfd, const char *name, bool ea return 0; } -int perf_pmu__convert_scale(const char *scale, char **end, double *sval) +static int parse_double(const char *scale, char **end, double *sval) { char *lc; int ret = 0; @@ -296,6 +312,11 @@ out: return ret; } +int perf_pmu__convert_scale(const char *scale, char **end, double *sval) +{ + return parse_double(scale, end, sval); +} + static int perf_pmu__parse_scale(struct perf_pmu *pmu, struct perf_pmu_alias *alias) { struct stat st; @@ -367,8 +388,8 @@ error: return -1; } -static int -perf_pmu__parse_per_pkg(struct perf_pmu *pmu, struct perf_pmu_alias *alias) +static bool perf_pmu__parse_event_source_bool(const char *pmu_name, const char *event_name, + const char *suffix) { char path[PATH_MAX]; size_t len; @@ -376,59 +397,67 @@ perf_pmu__parse_per_pkg(struct perf_pmu *pmu, struct perf_pmu_alias *alias) len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path)); if (!len) - return 0; - scnprintf(path + len, sizeof(path) - len, "%s/events/%s.per-pkg", pmu->name, alias->name); + return false; + + scnprintf(path + len, sizeof(path) - len, "%s/events/%s.%s", pmu_name, event_name, suffix); fd = open(path, O_RDONLY); if (fd == -1) - return -1; + return false; - close(fd); +#ifndef NDEBUG + { + char buf[8]; - alias->per_pkg = true; - return 0; + len = read(fd, buf, sizeof(buf)); + assert(len == 1 || len == 2); + assert(buf[0] == '1'); + } +#endif + + close(fd); + return true; } -static int perf_pmu__parse_snapshot(struct perf_pmu *pmu, struct perf_pmu_alias *alias) +static void perf_pmu__parse_per_pkg(struct perf_pmu *pmu, struct perf_pmu_alias *alias) { - char path[PATH_MAX]; - size_t len; - int fd; - - len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path)); - if (!len) - return 0; - scnprintf(path + len, sizeof(path) - len, "%s/events/%s.snapshot", pmu->name, alias->name); - - fd = open(path, O_RDONLY); - if (fd == -1) - return -1; + alias->per_pkg = perf_pmu__parse_event_source_bool(pmu->name, alias->name, "per-pkg"); +} - alias->snapshot = true; - close(fd); - return 0; +static void perf_pmu__parse_snapshot(struct perf_pmu *pmu, struct perf_pmu_alias *alias) +{ + alias->snapshot = perf_pmu__parse_event_source_bool(pmu->name, alias->name, "snapshot"); } /* Delete an alias entry. */ -static void perf_pmu_free_alias(struct perf_pmu_alias *newalias) +static void perf_pmu_free_alias(struct perf_pmu_alias *alias) { - zfree(&newalias->name); - zfree(&newalias->desc); - zfree(&newalias->long_desc); - zfree(&newalias->topic); - zfree(&newalias->pmu_name); - parse_events_terms__exit(&newalias->terms); - free(newalias); + if (!alias) + return; + + zfree(&alias->name); + zfree(&alias->desc); + zfree(&alias->long_desc); + zfree(&alias->topic); + zfree(&alias->pmu_name); + zfree(&alias->terms); + zfree(&alias->legacy_terms); + free(alias); } static void perf_pmu__del_aliases(struct perf_pmu *pmu) { - struct perf_pmu_alias *alias, *tmp; + struct hashmap_entry *entry; + size_t bkt; - list_for_each_entry_safe(alias, tmp, &pmu->aliases, list) { - list_del(&alias->list); - perf_pmu_free_alias(alias); - } + if (!pmu->aliases) + return; + + hashmap__for_each_entry(pmu->aliases, entry, bkt) + perf_pmu_free_alias(entry->pvalue); + + hashmap__free(pmu->aliases); + pmu->aliases = NULL; } static struct perf_pmu_alias *perf_pmu__find_alias(struct perf_pmu *pmu, @@ -436,35 +465,37 @@ static struct perf_pmu_alias *perf_pmu__find_alias(struct perf_pmu *pmu, bool load) { struct perf_pmu_alias *alias; + bool has_sysfs_event; + char event_file_name[NAME_MAX + 8]; - if (load && !pmu->sysfs_aliases_loaded) { - bool has_sysfs_event; - char event_file_name[FILENAME_MAX + 8]; + if (hashmap__find(pmu->aliases, name, &alias)) + return alias; - /* - * Test if alias/event 'name' exists in the PMU's sysfs/events - * directory. If not skip parsing the sysfs aliases. Sysfs event - * name must be all lower or all upper case. - */ - scnprintf(event_file_name, sizeof(event_file_name), "events/%s", name); - for (size_t i = 7, n = 7 + strlen(name); i < n; i++) - event_file_name[i] = tolower(event_file_name[i]); + if (!load || pmu->sysfs_aliases_loaded) + return NULL; - has_sysfs_event = perf_pmu__file_exists(pmu, event_file_name); - if (!has_sysfs_event) { - for (size_t i = 7, n = 7 + strlen(name); i < n; i++) - event_file_name[i] = toupper(event_file_name[i]); + /* + * Test if alias/event 'name' exists in the PMU's sysfs/events + * directory. If not skip parsing the sysfs aliases. Sysfs event + * name must be all lower or all upper case. + */ + scnprintf(event_file_name, sizeof(event_file_name), "events/%s", name); + for (size_t i = 7, n = 7 + strlen(name); i < n; i++) + event_file_name[i] = tolower(event_file_name[i]); - has_sysfs_event = perf_pmu__file_exists(pmu, event_file_name); - } - if (has_sysfs_event) - pmu_aliases_parse(pmu); + has_sysfs_event = perf_pmu__file_exists(pmu, event_file_name); + if (!has_sysfs_event) { + for (size_t i = 7, n = 7 + strlen(name); i < n; i++) + event_file_name[i] = toupper(event_file_name[i]); + has_sysfs_event = perf_pmu__file_exists(pmu, event_file_name); } - list_for_each_entry(alias, &pmu->aliases, list) { - if (!strcasecmp(alias->name, name)) + if (has_sysfs_event) { + pmu_aliases_parse(pmu); + if (hashmap__find(pmu->aliases, name, &alias)) return alias; } + return NULL; } @@ -503,6 +534,7 @@ static void read_alias_info(struct perf_pmu *pmu, struct perf_pmu_alias *alias) struct update_alias_data { struct perf_pmu *pmu; struct perf_pmu_alias *alias; + bool legacy; }; static int update_alias(const struct pmu_event *pe, @@ -518,8 +550,13 @@ static int update_alias(const struct pmu_event *pe, assign_str(pe->name, "topic", &data->alias->topic, pe->topic); data->alias->per_pkg = pe->perpkg; if (pe->event) { - parse_events_terms__exit(&data->alias->terms); - ret = parse_events_terms(&data->alias->terms, pe->event, /*input=*/NULL); + if (data->legacy) { + zfree(&data->alias->legacy_terms); + data->alias->legacy_terms = strdup(pe->event); + } else { + zfree(&data->alias->terms); + data->alias->terms = strdup(pe->event); + } } if (!ret && pe->unit) { char *unit; @@ -528,15 +565,27 @@ static int update_alias(const struct pmu_event *pe, if (!ret) snprintf(data->alias->unit, sizeof(data->alias->unit), "%s", unit); } + if (!ret && pe->retirement_latency_mean) { + ret = parse_double(pe->retirement_latency_mean, NULL, + &data->alias->retirement_latency_mean); + } + if (!ret && pe->retirement_latency_min) { + ret = parse_double(pe->retirement_latency_min, NULL, + &data->alias->retirement_latency_min); + } + if (!ret && pe->retirement_latency_max) { + ret = parse_double(pe->retirement_latency_max, NULL, + &data->alias->retirement_latency_max); + } return ret; } static int perf_pmu__new_alias(struct perf_pmu *pmu, const char *name, - const char *desc, const char *val, FILE *val_fd, + const char *desc, const char *val, int val_fd, const struct pmu_event *pe, enum event_source src) { - struct perf_pmu_alias *alias; - int ret; + struct perf_pmu_alias *alias, *old_alias; + int ret = 0; const char *long_desc = NULL, *topic = NULL, *unit = NULL, *pmu_name = NULL; bool deprecated = false, perpkg = false; @@ -559,24 +608,49 @@ static int perf_pmu__new_alias(struct perf_pmu *pmu, const char *name, if (!alias) return -ENOMEM; - parse_events_terms__init(&alias->terms); alias->scale = 1.0; alias->unit[0] = '\0'; alias->per_pkg = perpkg; alias->snapshot = false; alias->deprecated = deprecated; + alias->retirement_latency_mean = 0.0; + alias->retirement_latency_min = 0.0; + alias->retirement_latency_max = 0.0; - ret = parse_events_terms(&alias->terms, val, val_fd); - if (ret) { - pr_err("Cannot parse alias %s: %d\n", val, ret); - free(alias); - return ret; + if (!ret && pe && pe->retirement_latency_mean) { + ret = parse_double(pe->retirement_latency_mean, NULL, + &alias->retirement_latency_mean); } + if (!ret && pe && pe->retirement_latency_min) { + ret = parse_double(pe->retirement_latency_min, NULL, + &alias->retirement_latency_min); + } + if (!ret && pe && pe->retirement_latency_max) { + ret = parse_double(pe->retirement_latency_max, NULL, + &alias->retirement_latency_max); + } + if (ret) + return ret; + if (val_fd < 0) { + alias->terms = strdup(val); + } else { + char buf[256]; + struct io io; + size_t line_len; + + io__init(&io, val_fd, buf, sizeof(buf)); + ret = io__getline(&io, &alias->terms, &line_len) < 0 ? -errno : 0; + if (ret) { + pr_err("Failed to read alias %s\n", name); + return ret; + } + if (line_len >= 1 && alias->terms[line_len - 1] == '\n') + alias->terms[line_len - 1] = '\0'; + } alias->name = strdup(name); alias->desc = desc ? strdup(desc) : NULL; - alias->long_desc = long_desc ? strdup(long_desc) : - desc ? strdup(desc) : NULL; + alias->long_desc = long_desc ? strdup(long_desc) : NULL; alias->topic = topic ? strdup(topic) : NULL; alias->pmu_name = pmu_name ? strdup(pmu_name) : NULL; if (unit) { @@ -590,15 +664,29 @@ static int perf_pmu__new_alias(struct perf_pmu *pmu, const char *name, default: case EVENT_SRC_SYSFS: alias->from_sysfs = true; - if (pmu->events_table) { + if (pmu->events_table || pmu->is_core) { /* Update an event from sysfs with json data. */ struct update_alias_data data = { .pmu = pmu, .alias = alias, + .legacy = false, }; - if (pmu_events_table__find_event(pmu->events_table, pmu, name, - update_alias, &data) == 0) - pmu->cpu_json_aliases++; + if ((pmu_events_table__find_event(pmu->events_table, pmu, name, + update_alias, &data) == 0)) { + /* + * Override sysfs encodings with json encodings + * specific to the cpuid. + */ + pmu->cpu_common_json_aliases++; + } + if (pmu->is_core) { + /* Add in legacy encodings. */ + data.legacy = true; + if (pmu_events_table__find_event( + perf_pmu__default_core_events_table(), + pmu, name, update_alias, &data) == 0) + pmu->cpu_common_json_aliases++; + } } pmu->sysfs_aliases++; break; @@ -610,7 +698,8 @@ static int perf_pmu__new_alias(struct perf_pmu *pmu, const char *name, break; } - list_add_tail(&alias->list, &pmu->aliases); + hashmap__set(pmu->aliases, alias->name, alias, /*old_key=*/ NULL, &old_alias); + perf_pmu_free_alias(old_alias); return 0; } @@ -637,17 +726,14 @@ static inline bool pmu_alias_info_file(const char *name) */ static int __pmu_aliases_parse(struct perf_pmu *pmu, int events_dir_fd) { - struct dirent *evt_ent; - DIR *event_dir; + struct io_dirent64 *evt_ent; + struct io_dir event_dir; - event_dir = fdopendir(events_dir_fd); - if (!event_dir) - return -EINVAL; + io_dir__init(&event_dir, events_dir_fd); - while ((evt_ent = readdir(event_dir))) { + while ((evt_ent = io_dir__readdir(&event_dir))) { char *name = evt_ent->d_name; int fd; - FILE *file; if (!strcmp(name, ".") || !strcmp(name, "..")) continue; @@ -663,20 +749,14 @@ static int __pmu_aliases_parse(struct perf_pmu *pmu, int events_dir_fd) pr_debug("Cannot open %s\n", name); continue; } - file = fdopen(fd, "r"); - if (!file) { - close(fd); - continue; - } if (perf_pmu__new_alias(pmu, name, /*desc=*/ NULL, - /*val=*/ NULL, file, /*pe=*/ NULL, + /*val=*/ NULL, fd, /*pe=*/ NULL, EVENT_SRC_SYSFS) < 0) pr_debug("Cannot set up %s\n", name); - fclose(file); + close(fd); } - closedir(event_dir); pmu->sysfs_aliases_loaded = true; return 0; } @@ -707,7 +787,7 @@ static int pmu_aliases_parse(struct perf_pmu *pmu) static int pmu_aliases_parse_eager(struct perf_pmu *pmu, int sysfs_fd) { - char path[FILENAME_MAX + 7]; + char path[NAME_MAX + 8]; int ret, events_dir_fd; scnprintf(path, sizeof(path), "%s/events", pmu->name); @@ -721,29 +801,29 @@ static int pmu_aliases_parse_eager(struct perf_pmu *pmu, int sysfs_fd) return ret; } -static int pmu_alias_terms(struct perf_pmu_alias *alias, int err_loc, struct list_head *terms) +static int pmu_alias_terms(struct perf_pmu_alias *alias, struct list_head *terms) { - struct parse_events_term *term, *cloned; - struct parse_events_terms clone_terms; - - parse_events_terms__init(&clone_terms); - list_for_each_entry(term, &alias->terms.terms, list) { - int ret = parse_events_term__clone(&cloned, term); + struct parse_events_terms alias_terms; + struct parse_events_term *term; + int ret; - if (ret) { - parse_events_terms__exit(&clone_terms); - return ret; - } + parse_events_terms__init(&alias_terms); + ret = parse_events_terms(&alias_terms, alias->terms); + if (ret) { + pr_err("Cannot parse '%s' terms '%s': %d\n", + alias->name, alias->terms, ret); + parse_events_terms__exit(&alias_terms); + return ret; + } + list_for_each_entry(term, &alias_terms.terms, list) { /* * Weak terms don't override command line options, * which we don't want for implicit terms in aliases. */ - cloned->weak = true; - cloned->err_term = cloned->err_val = err_loc; - list_add_tail(&cloned->list, &clone_terms.terms); + term->weak = true; } - list_splice_init(&clone_terms.terms, terms); - parse_events_terms__exit(&clone_terms); + list_splice_init(&alias_terms.terms, terms); + parse_events_terms__exit(&alias_terms); return 0; } @@ -751,32 +831,41 @@ static int pmu_alias_terms(struct perf_pmu_alias *alias, int err_loc, struct lis * Uncore PMUs have a "cpumask" file under sysfs. CPU PMUs (e.g. on arm/arm64) * may have a "cpus" file. */ -static struct perf_cpu_map *pmu_cpumask(int dirfd, const char *name, bool is_core) +static struct perf_cpu_map *pmu_cpumask(int dirfd, const char *pmu_name, bool is_core) { - struct perf_cpu_map *cpus; const char *templates[] = { "cpumask", "cpus", NULL }; const char **template; - char pmu_name[PATH_MAX]; - struct perf_pmu pmu = {.name = pmu_name}; - FILE *file; - strlcpy(pmu_name, name, sizeof(pmu_name)); for (template = templates; *template; template++) { - file = perf_pmu__open_file_at(&pmu, dirfd, *template); - if (!file) + struct io io; + char buf[128]; + char *cpumask = NULL; + size_t cpumask_len; + ssize_t ret; + struct perf_cpu_map *cpus; + + io.fd = perf_pmu__pathname_fd(dirfd, pmu_name, *template, O_RDONLY); + if (io.fd < 0) continue; - cpus = perf_cpu_map__read(file); - fclose(file); + + io__init(&io, io.fd, buf, sizeof(buf)); + ret = io__getline(&io, &cpumask, &cpumask_len); + close(io.fd); + if (ret < 0) + continue; + + cpus = perf_cpu_map__new(cpumask); + free(cpumask); if (cpus) return cpus; } /* Nothing found, for core PMUs assume this means all CPUs. */ - return is_core ? perf_cpu_map__get(cpu_map__online()) : NULL; + return is_core ? cpu_map__online() : NULL; } static bool pmu_is_uncore(int dirfd, const char *name) @@ -822,118 +911,144 @@ static int is_sysfs_pmu_core(const char *name) return file_available(path); } -char *perf_pmu__getcpuid(struct perf_pmu *pmu) -{ - char *cpuid; - static bool printed; - - cpuid = getenv("PERF_CPUID"); - if (cpuid) - cpuid = strdup(cpuid); - if (!cpuid) - cpuid = get_cpuid_str(pmu); - if (!cpuid) - return NULL; - - if (!printed) { - pr_debug("Using CPUID %s\n", cpuid); - printed = true; - } - return cpuid; -} - -__weak const struct pmu_metrics_table *pmu_metrics_table__find(void) +/** + * Return the length of the PMU name not including the suffix for uncore PMUs. + * + * We want to deduplicate many similar uncore PMUs by stripping their suffixes, + * but there are never going to be too many core PMUs and the suffixes might be + * interesting. "arm_cortex_a53" vs "arm_cortex_a57" or "cpum_cf" for example. + * + * @skip_duplicate_pmus: False in verbose mode so all uncore PMUs are visible + */ +static size_t pmu_deduped_name_len(const struct perf_pmu *pmu, const char *name, + bool skip_duplicate_pmus) { - return perf_pmu__find_metrics_table(NULL); + return skip_duplicate_pmus && !pmu->is_core + ? pmu_name_len_no_suffix(name) + : strlen(name); } /** - * perf_pmu__match_ignoring_suffix - Does the pmu_name match tok ignoring any - * trailing suffix? The Suffix must be in form - * tok_{digits}, or tok{digits}. + * perf_pmu__match_wildcard - Does the pmu_name start with tok and is then only + * followed by nothing or a suffix? tok may contain + * part of a suffix. * @pmu_name: The pmu_name with possible suffix. - * @tok: The possible match to pmu_name without suffix. + * @tok: The wildcard argument to match. */ -static bool perf_pmu__match_ignoring_suffix(const char *pmu_name, const char *tok) +static bool perf_pmu__match_wildcard(const char *pmu_name, const char *tok) { - const char *p; + const char *p, *suffix; + bool has_hex = false; + size_t tok_len = strlen(tok); - if (strncmp(pmu_name, tok, strlen(tok))) + /* Check start of pmu_name for equality. */ + if (strncmp(pmu_name, tok, tok_len)) return false; - p = pmu_name + strlen(tok); + suffix = p = pmu_name + tok_len; if (*p == 0) return true; - if (*p == '_') + if (*p == '_') { ++p; + ++suffix; + } /* Ensure we end in a number */ while (1) { - if (!isdigit(*p)) + if (!isxdigit(*p)) return false; + if (!has_hex) + has_hex = !isdigit(*p); if (*(++p) == 0) break; } + if (has_hex) + return (p - suffix) > 2; + return true; } /** - * pmu_uncore_alias_match - does name match the PMU name? - * @pmu_name: the json struct pmu_event name. This may lack a suffix (which + * perf_pmu__match_ignoring_suffix_uncore - Does the pmu_name match tok ignoring + * any trailing suffix on pmu_name and + * tok? The Suffix must be in form + * tok_{digits}, or tok{digits}. + * @pmu_name: The pmu_name with possible suffix. + * @tok: The possible match to pmu_name. + */ +static bool perf_pmu__match_ignoring_suffix_uncore(const char *pmu_name, const char *tok) +{ + size_t pmu_name_len, tok_len; + + /* For robustness, check for NULL. */ + if (pmu_name == NULL) + return tok == NULL; + + /* uncore_ prefixes are ignored. */ + if (!strncmp(pmu_name, "uncore_", 7)) + pmu_name += 7; + if (!strncmp(tok, "uncore_", 7)) + tok += 7; + + pmu_name_len = pmu_name_len_no_suffix(pmu_name); + tok_len = pmu_name_len_no_suffix(tok); + if (pmu_name_len != tok_len) + return false; + + return strncmp(pmu_name, tok, pmu_name_len) == 0; +} + + +/** + * perf_pmu__match_wildcard_uncore - does to_match match the PMU's name? + * @pmu_name: The pmu->name or pmu->alias to match against. + * @to_match: the json struct pmu_event name. This may lack a suffix (which * matches) or be of the form "socket,pmuname" which will match * "socketX_pmunameY". - * @name: a real full PMU name as from sysfs. */ -static bool pmu_uncore_alias_match(const char *pmu_name, const char *name) +static bool perf_pmu__match_wildcard_uncore(const char *pmu_name, const char *to_match) { - char *tmp = NULL, *tok, *str; - bool res; - - if (strchr(pmu_name, ',') == NULL) - return perf_pmu__match_ignoring_suffix(name, pmu_name); + char *mutable_to_match, *tok, *tmp; - str = strdup(pmu_name); - if (!str) + if (!pmu_name) return false; - /* - * uncore alias may be from different PMU with common prefix - */ - tok = strtok_r(str, ",", &tmp); - if (strncmp(pmu_name, tok, strlen(tok))) { - res = false; - goto out; - } + /* uncore_ prefixes are ignored. */ + if (!strncmp(pmu_name, "uncore_", 7)) + pmu_name += 7; + if (!strncmp(to_match, "uncore_", 7)) + to_match += 7; - /* - * Match more complex aliases where the alias name is a comma-delimited - * list of tokens, orderly contained in the matching PMU name. - * - * Example: For alias "socket,pmuname" and PMU "socketX_pmunameY", we - * match "socket" in "socketX_pmunameY" and then "pmuname" in - * "pmunameY". - */ - while (1) { - char *next_tok = strtok_r(NULL, ",", &tmp); + if (strchr(to_match, ',') == NULL) + return perf_pmu__match_wildcard(pmu_name, to_match); - name = strstr(name, tok); - if (!name || - (!next_tok && !perf_pmu__match_ignoring_suffix(name, tok))) { - res = false; - goto out; + /* Process comma separated list of PMU name components. */ + mutable_to_match = strdup(to_match); + if (!mutable_to_match) + return false; + + tok = strtok_r(mutable_to_match, ",", &tmp); + while (tok) { + size_t tok_len = strlen(tok); + + if (strncmp(pmu_name, tok, tok_len)) { + /* Mismatch between part of pmu_name and tok. */ + free(mutable_to_match); + return false; } - if (!next_tok) - break; - tok = next_tok; - name += strlen(tok); + /* Move pmu_name forward over tok and suffix. */ + pmu_name += tok_len; + while (*pmu_name != '\0' && isdigit(*pmu_name)) + pmu_name++; + if (*pmu_name == '_') + pmu_name++; + + tok = strtok_r(NULL, ",", &tmp); } - - res = true; -out: - free(str); - return res; + free(mutable_to_match); + return *pmu_name == '\0'; } bool pmu_uncore_identifier_match(const char *compat, const char *id) @@ -964,7 +1079,7 @@ static int pmu_add_cpu_aliases_map_callback(const struct pmu_event *pe, { struct perf_pmu *pmu = vdata; - perf_pmu__new_alias(pmu, pe->name, pe->desc, pe->event, /*val_fd=*/ NULL, + perf_pmu__new_alias(pmu, pe->name, pe->desc, pe->event, /*val_fd=*/ -1, pe, EVENT_SRC_CPU_JSON); return 0; } @@ -980,13 +1095,16 @@ void pmu_add_cpu_aliases_table(struct perf_pmu *pmu, const struct pmu_events_tab static void pmu_add_cpu_aliases(struct perf_pmu *pmu) { - if (!pmu->events_table) + if (!pmu->events_table && !pmu->is_core) return; if (pmu->cpu_aliases_added) return; pmu_add_cpu_aliases_table(pmu, pmu->events_table); + if (pmu->is_core) + pmu_add_cpu_aliases_table(pmu, perf_pmu__default_core_events_table()); + pmu->cpu_aliases_added = true; } @@ -996,20 +1114,27 @@ static int pmu_add_sys_aliases_iter_fn(const struct pmu_event *pe, { struct perf_pmu *pmu = vdata; - if (!pe->compat || !pe->pmu) + if (!pe->compat || !pe->pmu) { + /* No data to match. */ + return 0; + } + + if (!perf_pmu__match_wildcard_uncore(pmu->name, pe->pmu) && + !perf_pmu__match_wildcard_uncore(pmu->alias_name, pe->pmu)) { + /* PMU name/alias_name don't match. */ return 0; + } - if (pmu_uncore_alias_match(pe->pmu, pmu->name) && - pmu_uncore_identifier_match(pe->compat, pmu->id)) { + if (pmu_uncore_identifier_match(pe->compat, pmu->id)) { + /* Id matched. */ perf_pmu__new_alias(pmu, pe->name, pe->desc, pe->event, - /*val_fd=*/ NULL, + /*val_fd=*/ -1, pe, EVENT_SRC_SYS_JSON); } - return 0; } @@ -1059,43 +1184,107 @@ perf_pmu__arch_init(struct perf_pmu *pmu) pmu->mem_events = perf_mem_events; } +/* Variant of str_hash that does tolower on each character. */ +static size_t aliases__hash(long key, void *ctx __maybe_unused) +{ + const char *s = (const char *)key; + size_t h = 0; + + while (*s) { + h = h * 31 + tolower(*s); + s++; + } + return h; +} + +static bool aliases__equal(long key1, long key2, void *ctx __maybe_unused) +{ + return strcasecmp((const char *)key1, (const char *)key2) == 0; +} + +int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name) +{ + pmu->type = type; + INIT_LIST_HEAD(&pmu->format); + INIT_LIST_HEAD(&pmu->caps); + + pmu->name = strdup(name); + if (!pmu->name) + return -ENOMEM; + + pmu->aliases = hashmap__new(aliases__hash, aliases__equal, /*ctx=*/ NULL); + if (!pmu->aliases) + return -ENOMEM; + + return 0; +} + +static __u32 wellknown_pmu_type(const char *pmu_name) +{ + struct { + const char *pmu_name; + __u32 type; + } wellknown_pmus[] = { + { + "software", + PERF_TYPE_SOFTWARE + }, + { + "tracepoint", + PERF_TYPE_TRACEPOINT + }, + { + "breakpoint", + PERF_TYPE_BREAKPOINT + }, + }; + for (size_t i = 0; i < ARRAY_SIZE(wellknown_pmus); i++) { + if (!strcmp(wellknown_pmus[i].pmu_name, pmu_name)) + return wellknown_pmus[i].type; + } + return PERF_TYPE_MAX; +} + struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *name, bool eager_load) { struct perf_pmu *pmu; - __u32 type; pmu = zalloc(sizeof(*pmu)); if (!pmu) return NULL; - pmu->name = strdup(name); - if (!pmu->name) - goto err; + if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, name) != 0) { + perf_pmu__delete(pmu); + return NULL; + } /* * Read type early to fail fast if a lookup name isn't a PMU. Ensure * that type value is successfully assigned (return 1). */ - if (perf_pmu__scan_file_at(pmu, dirfd, "type", "%u", &type) != 1) - goto err; - - INIT_LIST_HEAD(&pmu->format); - INIT_LIST_HEAD(&pmu->aliases); - INIT_LIST_HEAD(&pmu->caps); + if (perf_pmu__scan_file_at(pmu, dirfd, "type", "%u", &pmu->type) != 1) { + /* Double check the PMU's name isn't wellknown. */ + pmu->type = wellknown_pmu_type(name); + if (pmu->type == PERF_TYPE_MAX) { + perf_pmu__delete(pmu); + return NULL; + } + } /* * The pmu data we store & need consists of the pmu * type value and format definitions. Load both right * now. */ - if (pmu_format(pmu, dirfd, name, eager_load)) - goto err; + if (pmu_format(pmu, dirfd, name, eager_load)) { + perf_pmu__delete(pmu); + return NULL; + } pmu->is_core = is_pmu_core(name); pmu->cpus = pmu_cpumask(dirfd, name, pmu->is_core); - pmu->type = type; pmu->is_uncore = pmu_is_uncore(dirfd, name); if (pmu->is_uncore) pmu->id = pmu_id(name); @@ -1117,10 +1306,6 @@ struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char pmu_aliases_parse_eager(pmu, dirfd); return pmu; -err: - zfree(&pmu->name); - free(pmu); - return NULL; } /* Creates the PMU when sysfs scanning fails. */ @@ -1142,12 +1327,17 @@ struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pm pmu->cpus = cpu_map__online(); INIT_LIST_HEAD(&pmu->format); - INIT_LIST_HEAD(&pmu->aliases); + pmu->aliases = hashmap__new(aliases__hash, aliases__equal, /*ctx=*/ NULL); INIT_LIST_HEAD(&pmu->caps); list_add_tail(&pmu->list, core_pmus); return pmu; } +bool perf_pmu__is_fake(const struct perf_pmu *pmu) +{ + return pmu->type == PERF_PMU_TYPE_FAKE; +} + void perf_pmu__warn_invalid_formats(struct perf_pmu *pmu) { struct perf_pmu_format *format; @@ -1158,7 +1348,7 @@ void perf_pmu__warn_invalid_formats(struct perf_pmu *pmu) pmu->formats_checked = true; /* fake pmu doesn't have format list */ - if (pmu == &perf_pmu__fake) + if (perf_pmu__is_fake(pmu)) return; list_for_each_entry(format, &pmu->format, list) { @@ -1174,8 +1364,12 @@ void perf_pmu__warn_invalid_formats(struct perf_pmu *pmu) bool evsel__is_aux_event(const struct evsel *evsel) { - struct perf_pmu *pmu = evsel__find_pmu(evsel); + struct perf_pmu *pmu; + if (evsel->needs_auxtrace_mmap) + return true; + + pmu = evsel__find_pmu(evsel); return pmu && pmu->auxtrace; } @@ -1337,7 +1531,8 @@ static int pmu_config_term(const struct perf_pmu *pmu, struct perf_event_attr *attr, struct parse_events_term *term, struct parse_events_terms *head_terms, - bool zero, struct parse_events_error *err) + bool zero, bool apply_hardcoded, + struct parse_events_error *err) { struct perf_pmu_format *format; __u64 *vp; @@ -1351,11 +1546,78 @@ static int pmu_config_term(const struct perf_pmu *pmu, return 0; /* - * Hardcoded terms should be already in, so nothing - * to be done for them. + * Hardcoded terms are generally handled in event parsing, which + * traditionally have had to handle not having a PMU. An alias may + * have hard coded config values, optionally apply them below. */ - if (parse_events__is_hardcoded_term(term)) + if (parse_events__is_hardcoded_term(term)) { + /* Config terms set all bits in the config. */ + DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS); + + if (!apply_hardcoded) + return 0; + + bitmap_fill(bits, PERF_PMU_FORMAT_BITS); + + switch (term->type_term) { + case PARSE_EVENTS__TERM_TYPE_CONFIG: + assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); + pmu_format_value(bits, term->val.num, &attr->config, zero); + break; + case PARSE_EVENTS__TERM_TYPE_CONFIG1: + assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); + pmu_format_value(bits, term->val.num, &attr->config1, zero); + break; + case PARSE_EVENTS__TERM_TYPE_CONFIG2: + assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); + pmu_format_value(bits, term->val.num, &attr->config2, zero); + break; + case PARSE_EVENTS__TERM_TYPE_CONFIG3: + assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); + pmu_format_value(bits, term->val.num, &attr->config3, zero); + break; + case PARSE_EVENTS__TERM_TYPE_CONFIG4: + assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); + pmu_format_value(bits, term->val.num, &attr->config4, zero); + break; + case PARSE_EVENTS__TERM_TYPE_LEGACY_HARDWARE_CONFIG: + assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); + assert(term->val.num < PERF_COUNT_HW_MAX); + assert(pmu->is_core); + attr->config = term->val.num; + if (perf_pmus__supports_extended_type()) + attr->config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT; + attr->type = PERF_TYPE_HARDWARE; + break; + case PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE_CONFIG: { +#ifndef NDEBUG + int cache_type = term->val.num & 0xFF; + int cache_op = (term->val.num >> 8) & 0xFF; + int cache_result = (term->val.num >> 16) & 0xFF; + + assert(cache_type < PERF_COUNT_HW_CACHE_MAX); + assert(cache_op < PERF_COUNT_HW_CACHE_OP_MAX); + assert(cache_result < PERF_COUNT_HW_CACHE_RESULT_MAX); +#endif + assert(term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); + assert((term->val.num & ~0xFFFFFF) == 0); + assert(pmu->is_core); + attr->config = term->val.num; + if (perf_pmus__supports_extended_type()) + attr->config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT; + attr->type = PERF_TYPE_HW_CACHE; + break; + } + case PARSE_EVENTS__TERM_TYPE_USER: /* Not hardcoded. */ + return -EINVAL; + case PARSE_EVENTS__TERM_TYPE_NAME ... PARSE_EVENTS__TERM_TYPE_RATIO_TO_PREV: + /* Skip non-config terms. */ + break; + default: + break; + } return 0; + } format = pmu_find_format(&pmu->format, term->config); if (!format) { @@ -1393,6 +1655,9 @@ static int pmu_config_term(const struct perf_pmu *pmu, case PERF_PMU_FORMAT_VALUE_CONFIG3: vp = &attr->config3; break; + case PERF_PMU_FORMAT_VALUE_CONFIG4: + vp = &attr->config4; + break; default: return -EINVAL; } @@ -1437,13 +1702,12 @@ static int pmu_config_term(const struct perf_pmu *pmu, if (err) { char *err_str; - parse_events_error__handle(err, term->err_val, - asprintf(&err_str, - "value too big for format (%s), maximum is %llu", - format->name, (unsigned long long)max_val) < 0 - ? strdup("value too big for format") - : err_str, - NULL); + if (asprintf(&err_str, + "value too big for format (%s), maximum is %llu", + format->name, (unsigned long long)max_val) < 0) { + err_str = strdup("value too big for format"); + } + parse_events_error__handle(err, term->err_val, err_str, /*help=*/NULL); return -EINVAL; } /* @@ -1459,12 +1723,18 @@ static int pmu_config_term(const struct perf_pmu *pmu, int perf_pmu__config_terms(const struct perf_pmu *pmu, struct perf_event_attr *attr, struct parse_events_terms *terms, - bool zero, struct parse_events_error *err) + bool zero, bool apply_hardcoded, + struct parse_events_error *err) { struct parse_events_term *term; + if (perf_pmu__is_hwmon(pmu)) + return hwmon_pmu__config_terms(pmu, attr, terms, err); + if (perf_pmu__is_drm(pmu)) + return drm_pmu__config_terms(pmu, attr, terms, err); + list_for_each_entry(term, &terms->terms, list) { - if (pmu_config_term(pmu, attr, term, terms, zero, err)) + if (pmu_config_term(pmu, attr, term, terms, zero, apply_hardcoded, err)) return -EINVAL; } @@ -1478,11 +1748,16 @@ int perf_pmu__config_terms(const struct perf_pmu *pmu, */ int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr, struct parse_events_terms *head_terms, + bool apply_hardcoded, struct parse_events_error *err) { bool zero = !!pmu->perf_event_attr_init_default; - return perf_pmu__config_terms(pmu, attr, head_terms, zero, err); + /* Fake PMU doesn't have proper terms so nothing to configure in attr. */ + if (perf_pmu__is_fake(pmu)) + return 0; + + return perf_pmu__config_terms(pmu, attr, head_terms, zero, apply_hardcoded, err); } static struct perf_pmu_alias *pmu_find_alias(struct perf_pmu *pmu, @@ -1514,10 +1789,14 @@ static struct perf_pmu_alias *pmu_find_alias(struct perf_pmu *pmu, return alias; /* Alias doesn't exist, try to get it from the json events. */ - if (pmu->events_table && - pmu_events_table__find_event(pmu->events_table, pmu, name, - pmu_add_cpu_aliases_map_callback, - pmu) == 0) { + if ((pmu_events_table__find_event(pmu->events_table, pmu, name, + pmu_add_cpu_aliases_map_callback, + pmu) == 0) || + (pmu->is_core && + pmu_events_table__find_event(perf_pmu__default_core_events_table(), + pmu, name, + pmu_add_cpu_aliases_map_callback, + pmu) == 0)) { alias = perf_pmu__find_alias(pmu, name, /*load=*/ false); } return alias; @@ -1567,13 +1846,31 @@ static int check_info_data(struct perf_pmu *pmu, return 0; } +static int perf_pmu__parse_terms_to_attr(struct perf_pmu *pmu, const char *terms_str, + struct perf_event_attr *attr) +{ + struct parse_events_terms terms; + int ret; + + parse_events_terms__init(&terms); + ret = parse_events_terms(&terms, terms_str); + if (ret) { + pr_debug("Failed to parse terms '%s': %d\n", terms_str, ret); + parse_events_terms__exit(&terms); + return ret; + } + ret = perf_pmu__config(pmu, attr, &terms, /*apply_hardcoded=*/true, /*err=*/NULL); + parse_events_terms__exit(&terms); + return ret; +} + /* * Find alias in the terms list and replace it with the terms * defined for the alias */ int perf_pmu__check_alias(struct perf_pmu *pmu, struct parse_events_terms *head_terms, struct perf_pmu_info *info, bool *rewrote_terms, - struct parse_events_error *err) + u64 *alternate_hw_config, struct parse_events_error *err) { struct parse_events_term *term, *h; struct perf_pmu_alias *alias; @@ -1589,30 +1886,65 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct parse_events_terms *head_ info->unit = NULL; info->scale = 0.0; info->snapshot = false; + info->retirement_latency_mean = 0.0; + info->retirement_latency_min = 0.0; + info->retirement_latency_max = 0.0; + + if (perf_pmu__is_hwmon(pmu)) { + ret = hwmon_pmu__check_alias(head_terms, info, err); + goto out; + } + if (perf_pmu__is_drm(pmu)) { + ret = drm_pmu__check_alias(pmu, head_terms, info, err); + goto out; + } + + /* Fake PMU doesn't rewrite terms. */ + if (perf_pmu__is_fake(pmu)) + goto out; list_for_each_entry_safe(term, h, &head_terms->terms, list) { alias = pmu_find_alias(pmu, term); if (!alias) continue; - ret = pmu_alias_terms(alias, term->err_term, &term->list); + ret = pmu_alias_terms(alias, &term->list); if (ret) { parse_events_error__handle(err, term->err_term, - strdup("Failure to duplicate terms"), + strdup("Failed to parse terms"), NULL); return ret; } + *rewrote_terms = true; ret = check_info_data(pmu, alias, info, err, term->err_term); if (ret) return ret; + if (alias->legacy_terms) { + struct perf_event_attr attr = {.config = 0,}; + + ret = perf_pmu__parse_terms_to_attr(pmu, alias->legacy_terms, &attr); + if (ret) { + parse_events_error__handle(err, term->err_term, + strdup("Error evaluating legacy terms"), + NULL); + return ret; + } + if (attr.type == PERF_TYPE_HARDWARE) + *alternate_hw_config = attr.config & PERF_HW_EVENT_MASK; + } + if (alias->per_pkg) info->per_pkg = true; + info->retirement_latency_mean = alias->retirement_latency_mean; + info->retirement_latency_min = alias->retirement_latency_min; + info->retirement_latency_max = alias->retirement_latency_max; + list_del_init(&term->list); parse_events_term__delete(term); } - +out: /* * if no unit or scale found in aliases, then * set defaults as for evsel @@ -1685,6 +2017,9 @@ int perf_pmu__for_each_format(struct perf_pmu *pmu, void *state, pmu_format_call "config1=0..0xffffffffffffffff", "config2=0..0xffffffffffffffff", "config3=0..0xffffffffffffffff", + "config4=0..0xffffffffffffffff", + "legacy-hardware-config=0..9,", + "legacy-cache-config=0..0xffffff,", "name=string", "period=number", "freq=number", @@ -1700,17 +2035,20 @@ int perf_pmu__for_each_format(struct perf_pmu *pmu, void *state, pmu_format_call "no-overwrite", "percore", "aux-output", + "aux-action=(pause|resume|start-paused)", "aux-sample-size=number", + "cpu=number", + "ratio-to-prev=string", }; struct perf_pmu_format *format; int ret; /* * max-events and driver-config are missing above as are the internal - * types user, metric-id, raw, legacy cache and hardware. Assert against - * the enum parse_events__term_type so they are kept in sync. + * types user, metric-id, and raw. Assert against the enum + * parse_events__term_type so they are kept in sync. */ - _Static_assert(ARRAY_SIZE(terms) == __PARSE_EVENTS__TERM_TYPE_NR - 6, + _Static_assert(ARRAY_SIZE(terms) == __PARSE_EVENTS__TERM_TYPE_NR - 4, "perf_pmu__for_each_format()'s terms must be kept in sync with enum parse_events__term_type"); list_for_each_entry(format, &pmu->format, list) { perf_pmu_format__load(pmu, format); @@ -1753,26 +2091,54 @@ bool perf_pmu__have_event(struct perf_pmu *pmu, const char *name) { if (!name) return false; + if (perf_pmu__is_tool(pmu) && tool_pmu__skip_event(name)) + return false; + if (perf_pmu__is_tracepoint(pmu)) + return tp_pmu__have_event(pmu, name); + if (perf_pmu__is_hwmon(pmu)) + return hwmon_pmu__have_event(pmu, name); + if (perf_pmu__is_drm(pmu)) + return drm_pmu__have_event(pmu, name); if (perf_pmu__find_alias(pmu, name, /*load=*/ true) != NULL) return true; - if (pmu->cpu_aliases_added || !pmu->events_table) + if (pmu->cpu_aliases_added || (!pmu->events_table && !pmu->is_core)) return false; - return pmu_events_table__find_event(pmu->events_table, pmu, name, NULL, NULL) == 0; + if (pmu_events_table__find_event(pmu->events_table, pmu, name, NULL, NULL) == 0) + return true; + return pmu->is_core && + pmu_events_table__find_event(perf_pmu__default_core_events_table(), + pmu, name, NULL, NULL) == 0; } size_t perf_pmu__num_events(struct perf_pmu *pmu) { size_t nr; + if (perf_pmu__is_tracepoint(pmu)) + return tp_pmu__num_events(pmu); + if (perf_pmu__is_hwmon(pmu)) + return hwmon_pmu__num_events(pmu); + if (perf_pmu__is_drm(pmu)) + return drm_pmu__num_events(pmu); + pmu_aliases_parse(pmu); - nr = pmu->sysfs_aliases + pmu->sys_json_aliases;; + nr = pmu->sysfs_aliases + pmu->sys_json_aliases; + + if (pmu->cpu_aliases_added) { + nr += pmu->cpu_json_aliases; + } else if (pmu->events_table || pmu->is_core) { + nr += pmu_events_table__num_events(pmu->events_table, pmu); + if (pmu->is_core) { + nr += pmu_events_table__num_events( + perf_pmu__default_core_events_table(), pmu); + } + nr -= pmu->cpu_common_json_aliases; + } else { + assert(pmu->cpu_json_aliases == 0 && pmu->cpu_common_json_aliases == 0); + } - if (pmu->cpu_aliases_added) - nr += pmu->cpu_json_aliases; - else if (pmu->events_table) - nr += pmu_events_table__num_events(pmu->events_table, pmu) - pmu->cpu_json_aliases; - else - assert(pmu->cpu_json_aliases == 0); + if (perf_pmu__is_tool(pmu)) + nr -= tool_pmu__num_skip_events(); return pmu->selectable ? nr + 1 : nr; } @@ -1787,19 +2153,37 @@ static int sub_non_neg(int a, int b) static char *format_alias(char *buf, int len, const struct perf_pmu *pmu, const struct perf_pmu_alias *alias, bool skip_duplicate_pmus) { + struct parse_events_terms terms; struct parse_events_term *term; - int pmu_name_len = skip_duplicate_pmus - ? pmu_name_len_no_suffix(pmu->name, /*num=*/NULL) - : (int)strlen(pmu->name); - int used = snprintf(buf, len, "%.*s/%s", pmu_name_len, pmu->name, alias->name); + int ret, used; + size_t pmu_name_len = pmu_deduped_name_len(pmu, pmu->name, + skip_duplicate_pmus); + + /* Paramemterized events have the parameters shown. */ + if (strstr(alias->terms, "=?")) { + /* No parameters. */ + snprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name); + return buf; + } - list_for_each_entry(term, &alias->terms.terms, list) { + parse_events_terms__init(&terms); + ret = parse_events_terms(&terms, alias->terms); + if (ret) { + pr_err("Failure to parse '%s' terms '%s': %d\n", + alias->name, alias->terms, ret); + parse_events_terms__exit(&terms); + snprintf(buf, len, "%.*s/%s/", (int)pmu_name_len, pmu->name, alias->name); + return buf; + } + used = snprintf(buf, len, "%.*s/%s", (int)pmu_name_len, pmu->name, alias->name); + + list_for_each_entry(term, &terms.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); } - + parse_events_terms__exit(&terms); if (sub_non_neg(len, used) > 0) { buf[used] = '/'; used++; @@ -1813,28 +2197,73 @@ static char *format_alias(char *buf, int len, const struct perf_pmu *pmu, return buf; } +static bool perf_pmu_alias__check_deprecated(struct perf_pmu *pmu, struct perf_pmu_alias *alias) +{ + struct perf_event_attr attr = {.config = 0,}; + const char *check_terms; + bool has_legacy_config; + + if (alias->legacy_deprecated_checked) + return alias->deprecated; + + alias->legacy_deprecated_checked = true; + if (alias->deprecated) + return true; + + check_terms = alias->terms; + has_legacy_config = + strstr(check_terms, "legacy-hardware-config=") != NULL || + strstr(check_terms, "legacy-cache-config=") != NULL; + if (!has_legacy_config && alias->legacy_terms) { + check_terms = alias->legacy_terms; + has_legacy_config = + strstr(check_terms, "legacy-hardware-config=") != NULL || + strstr(check_terms, "legacy-cache-config=") != NULL; + } + if (!has_legacy_config) + return false; + + if (perf_pmu__parse_terms_to_attr(pmu, check_terms, &attr) != 0) { + /* Parsing failed, set as deprecated. */ + alias->deprecated = true; + } else if (attr.type < PERF_TYPE_MAX) { + /* Flag unsupported legacy events as deprecated. */ + alias->deprecated = !is_event_supported(attr.type, attr.config); + } + return alias->deprecated; +} + int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus, void *state, pmu_event_callback cb) { char buf[1024]; - struct perf_pmu_alias *event; struct pmu_event_info info = { .pmu = pmu, + .event_type_desc = "Kernel PMU event", }; int ret = 0; - struct strbuf sb; + struct hashmap_entry *entry; + size_t bkt; + + if (perf_pmu__is_tracepoint(pmu)) + return tp_pmu__for_each_event(pmu, state, cb); + if (perf_pmu__is_hwmon(pmu)) + return hwmon_pmu__for_each_event(pmu, state, cb); + if (perf_pmu__is_drm(pmu)) + return drm_pmu__for_each_event(pmu, state, cb); - strbuf_init(&sb, /*hint=*/ 0); pmu_aliases_parse(pmu); pmu_add_cpu_aliases(pmu); - list_for_each_entry(event, &pmu->aliases, list) { - size_t buf_used; - int pmu_name_len; + hashmap__for_each_entry(pmu->aliases, entry, bkt) { + struct perf_pmu_alias *event = entry->pvalue; + size_t buf_used, pmu_name_len; + + if (perf_pmu__is_tool(pmu) && tool_pmu__skip_event(event->name)) + continue; info.pmu_name = event->pmu_name ?: pmu->name; - pmu_name_len = skip_duplicate_pmus - ? pmu_name_len_no_suffix(info.pmu_name, /*num=*/NULL) - : (int)strlen(info.pmu_name); + pmu_name_len = pmu_deduped_name_len(pmu, info.pmu_name, + skip_duplicate_pmus); info.alias = NULL; if (event->desc) { info.name = event->name; @@ -1857,16 +2286,14 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus, info.desc = event->desc; info.long_desc = event->long_desc; info.encoding_desc = buf + buf_used; - parse_events_terms__to_strbuf(&event->terms, &sb); buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used, - "%.*s/%s/", pmu_name_len, info.pmu_name, sb.buf) + 1; + "%.*s/%s/", (int)pmu_name_len, info.pmu_name, event->terms) + 1; + info.str = event->terms; info.topic = event->topic; - info.str = sb.buf; - info.deprecated = event->deprecated; + info.deprecated = perf_pmu_alias__check_deprecated(pmu, event); ret = cb(state, &info); if (ret) goto out; - strbuf_setlen(&sb, /*len=*/ 0); } if (pmu->selectable) { info.name = buf; @@ -1882,19 +2309,88 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus, ret = cb(state, &info); } out: - strbuf_release(&sb); return ret; } -bool pmu__name_match(const struct perf_pmu *pmu, const char *pmu_name) +static bool perf_pmu___name_match(const struct perf_pmu *pmu, const char *to_match, bool wildcard) { - return !strcmp(pmu->name, pmu_name) || - (pmu->is_uncore && pmu_uncore_alias_match(pmu_name, pmu->name)) || + const char *names[2] = { + pmu->name, + pmu->alias_name, + }; + if (pmu->is_core) { + for (size_t i = 0; i < ARRAY_SIZE(names); i++) { + const char *name = names[i]; + + if (!name) + continue; + + if (!strcmp(name, to_match)) { + /* Exact name match. */ + return true; + } + } + if (!strcmp(to_match, "default_core")) { + /* + * jevents and tests use default_core as a marker for any core + * PMU as the PMU name varies across architectures. + */ + return true; + } + return false; + } + if (!pmu->is_uncore) { /* - * jevents and tests use default_core as a marker for any core - * PMU as the PMU name varies across architectures. + * PMU isn't core or uncore, some kind of broken CPU mask + * situation. Only match exact name. */ - (pmu->is_core && !strcmp(pmu_name, "default_core")); + for (size_t i = 0; i < ARRAY_SIZE(names); i++) { + const char *name = names[i]; + + if (!name) + continue; + + if (!strcmp(name, to_match)) { + /* Exact name match. */ + return true; + } + } + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(names); i++) { + const char *name = names[i]; + + if (!name) + continue; + + if (wildcard && perf_pmu__match_wildcard_uncore(name, to_match)) + return true; + if (!wildcard && perf_pmu__match_ignoring_suffix_uncore(name, to_match)) + return true; + } + return false; +} + +/** + * perf_pmu__name_wildcard_match - Called by the jevents generated code to see + * if pmu matches the json to_match string. + * @pmu: The pmu whose name/alias to match. + * @to_match: The possible match to pmu_name. + */ +bool perf_pmu__name_wildcard_match(const struct perf_pmu *pmu, const char *to_match) +{ + return perf_pmu___name_match(pmu, to_match, /*wildcard=*/true); +} + +/** + * perf_pmu__name_no_suffix_match - Does pmu's name match to_match ignoring any + * trailing suffix on the pmu_name and/or tok? + * @pmu: The pmu whose name/alias to match. + * @to_match: The possible match to pmu_name. + */ +bool perf_pmu__name_no_suffix_match(const struct perf_pmu *pmu, const char *to_match) +{ + return perf_pmu___name_match(pmu, to_match, /*wildcard=*/false); } bool perf_pmu__is_software(const struct perf_pmu *pmu) @@ -1914,6 +2410,7 @@ bool perf_pmu__is_software(const struct perf_pmu *pmu) case PERF_TYPE_HW_CACHE: return false; case PERF_TYPE_RAW: return false; case PERF_TYPE_BREAKPOINT: return true; + case PERF_PMU_TYPE_TOOL: return true; default: break; } for (size_t i = 0; i < ARRAY_SIZE(known_sw_pmus); i++) { @@ -2025,6 +2522,17 @@ static void perf_pmu__del_caps(struct perf_pmu *pmu) } } +struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name) +{ + struct perf_pmu_caps *caps; + + list_for_each_entry(caps, &pmu->caps, list) { + if (!strcmp(caps->name, name)) + return caps; + } + return NULL; +} + /* * Reading/parsing the given pmu capabilities, which should be located at: * /sys/bus/event_source/devices/<dev>/caps as sysfs group attributes. @@ -2032,10 +2540,9 @@ static void perf_pmu__del_caps(struct perf_pmu *pmu) */ int perf_pmu__caps_parse(struct perf_pmu *pmu) { - struct stat st; char caps_path[PATH_MAX]; - DIR *caps_dir; - struct dirent *evt_ent; + struct io_dir caps_dir; + struct io_dirent64 *evt_ent; int caps_fd; if (pmu->caps_initialized) @@ -2046,24 +2553,21 @@ int perf_pmu__caps_parse(struct perf_pmu *pmu) if (!perf_pmu__pathname_scnprintf(caps_path, sizeof(caps_path), pmu->name, "caps")) return -1; - if (stat(caps_path, &st) < 0) { + caps_fd = open(caps_path, O_CLOEXEC | O_DIRECTORY | O_RDONLY); + if (caps_fd == -1) { pmu->caps_initialized = true; return 0; /* no error if caps does not exist */ } - caps_dir = opendir(caps_path); - if (!caps_dir) - return -EINVAL; - - caps_fd = dirfd(caps_dir); + io_dir__init(&caps_dir, caps_fd); - while ((evt_ent = readdir(caps_dir)) != NULL) { + while ((evt_ent = io_dir__readdir(&caps_dir)) != NULL) { char *name = evt_ent->d_name; char value[128]; FILE *file; int fd; - if (!strcmp(name, ".") || !strcmp(name, "..")) + if (io_dir__is_dir(&caps_dir, evt_ent)) continue; fd = openat(caps_fd, name, O_RDONLY); @@ -2085,7 +2589,7 @@ int perf_pmu__caps_parse(struct perf_pmu *pmu) fclose(file); } - closedir(caps_dir); + close(caps_fd); pmu->caps_initialized = true; return pmu->nr_caps; @@ -2140,34 +2644,31 @@ void perf_pmu__warn_invalid_config(struct perf_pmu *pmu, __u64 config, name ?: "N/A", buf, config_name, config); } -bool perf_pmu__match(const struct perf_pmu *pmu, const char *tok) +bool perf_pmu__wildcard_match(const struct perf_pmu *pmu, const char *wildcard_to_match) { - const char *name = pmu->name; - bool need_fnmatch = strchr(tok, '*') != NULL; - - if (!strncmp(tok, "uncore_", 7)) - tok += 7; - if (!strncmp(name, "uncore_", 7)) - name += 7; + const char *names[2] = { + pmu->name, + pmu->alias_name, + }; + bool need_fnmatch = strisglob(wildcard_to_match); - if (perf_pmu__match_ignoring_suffix(name, tok) || - (need_fnmatch && !fnmatch(tok, name, 0))) - return true; + if (!strncmp(wildcard_to_match, "uncore_", 7)) + wildcard_to_match += 7; - name = pmu->alias_name; - if (!name) - return false; + for (size_t i = 0; i < ARRAY_SIZE(names); i++) { + const char *pmu_name = names[i]; - if (!strncmp(name, "uncore_", 7)) - name += 7; + if (!pmu_name) + continue; - return perf_pmu__match_ignoring_suffix(name, tok) || - (need_fnmatch && !fnmatch(tok, name, 0)); -} + if (!strncmp(pmu_name, "uncore_", 7)) + pmu_name += 7; -double __weak perf_pmu__cpu_slots_per_cycle(void) -{ - return NAN; + if (perf_pmu__match_wildcard(pmu_name, wildcard_to_match) || + (need_fnmatch && !fnmatch(wildcard_to_match, pmu_name, 0))) + return true; + } + return false; } int perf_pmu__event_source_devices_scnprintf(char *pathname, size_t size) @@ -2222,6 +2723,14 @@ int perf_pmu__pathname_fd(int dirfd, const char *pmu_name, const char *filename, void perf_pmu__delete(struct perf_pmu *pmu) { + if (!pmu) + return; + + if (perf_pmu__is_hwmon(pmu)) + hwmon_pmu__exit(pmu); + else if (perf_pmu__is_drm(pmu)) + drm_pmu__exit(pmu); + perf_pmu__del_formats(&pmu->format); perf_pmu__del_aliases(pmu); perf_pmu__del_caps(pmu); @@ -2236,16 +2745,18 @@ void perf_pmu__delete(struct perf_pmu *pmu) const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config) { - struct perf_pmu_alias *event; + struct hashmap_entry *entry; + size_t bkt; if (!pmu) return NULL; pmu_aliases_parse(pmu); pmu_add_cpu_aliases(pmu); - list_for_each_entry(event, &pmu->aliases, list) { + hashmap__for_each_entry(pmu->aliases, entry, bkt) { + struct perf_pmu_alias *event = entry->pvalue; struct perf_event_attr attr = {.config = 0,}; - int ret = perf_pmu__config(pmu, &attr, &event->terms, NULL); + int ret = perf_pmu__parse_terms_to_attr(pmu, event->terms, &attr); if (ret == 0 && config == attr.config) return event->name; |
