aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools/perf/util/pmu.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/perf/util/pmu.c2472
1 files changed, 1651 insertions, 821 deletions
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index d41caeb35cf6..609828513f6c 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3,78 +3,242 @@
#include <linux/compiler.h>
#include <linux/string.h>
#include <linux/zalloc.h>
-#include <subcmd/pager.h>
+#include <linux/ctype.h>
#include <sys/types.h>
-#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
-#include <stdarg.h>
#include <dirent.h>
#include <api/fs/fs.h>
+#include <api/io.h>
+#include <api/io_dir.h>
#include <locale.h>
-#include <regex.h>
-#include <perf/cpumap.h>
+#include <fnmatch.h>
+#include <math.h>
#include "debug.h"
#include "evsel.h"
#include "pmu.h"
+#include "hwmon_pmu.h"
+#include "pmus.h"
+#include "tool_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"
#include "fncache.h"
+#include "util/evsel_config.h"
+#include <regex.h>
-struct perf_pmu perf_pmu__fake;
+#define UNIT_MAX_LEN 31 /* max length for event unit name */
-struct perf_pmu_format {
+enum event_source {
+ /* 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/bus/event_source/devices/<pmu>/identifier matched json
+ * file.
+ */
+ EVENT_SRC_SYS_JSON,
+};
+
+/**
+ * struct perf_pmu_alias - An event either read from sysfs or builtin in
+ * pmu-events.c, created by parsing the pmu-events json files.
+ */
+struct perf_pmu_alias {
+ /** @name: Name of the event like "mem-loads". */
char *name;
- int value;
- DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS);
+ /** @desc: Optional short description of the event. */
+ char *desc;
+ /** @long_desc: Optional long description. */
+ char *long_desc;
+ /**
+ * @topic: Optional topic such as cache or pipeline, particularly for
+ * json events.
+ */
+ char *topic;
+ /** @terms: Owned list of the original parsed parameters. */
+ struct parse_events_terms 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.
+ */
+ char *pmu_name;
+ /** @unit: Units for the event, such as bytes or cache lines. */
+ 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
+ * equivalent json value exist and have the value 1.
+ */
+ bool per_pkg;
+ /**
+ * @snapshot: Does the file
+ * <sysfs>/bus/event_source/devices/<pmu_name>/events/<name>.snapshot
+ * exist and have the value 1.
+ */
+ bool snapshot;
+ /**
+ * @deprecated: Is the event hidden and so not shown in perf list by
+ * default.
+ */
+ bool deprecated;
+ /** @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? */
+ bool info_loaded;
+};
+
+/**
+ * struct perf_pmu_format - Values from a format file read from
+ * <sysfs>/devices/cpu/format/ held in struct perf_pmu.
+ *
+ * For example, the contents of <sysfs>/devices/cpu/format/event may be
+ * "config:0-7" and will be represented here as name="event",
+ * value=PERF_PMU_FORMAT_VALUE_CONFIG and bits 0 to 7 will be set.
+ */
+struct perf_pmu_format {
+ /** @list: Element on list within struct perf_pmu. */
struct list_head list;
+ /** @bits: Which config bits are set by this format value. */
+ DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS);
+ /** @name: The modifier/file name. */
+ char *name;
+ /**
+ * @value : Which config value the format relates to. Supported values
+ * are from PERF_PMU_FORMAT_VALUE_CONFIG to
+ * PERF_PMU_FORMAT_VALUE_CONFIG_END.
+ */
+ u16 value;
+ /** @loaded: Has the contents been loaded/parsed. */
+ bool loaded;
};
-int perf_pmu_parse(struct list_head *list, char *name);
-extern FILE *perf_pmu_in;
+static int pmu_aliases_parse(struct perf_pmu *pmu);
+
+static struct perf_pmu_format *perf_pmu__new_format(struct list_head *list, char *name)
+{
+ struct perf_pmu_format *format;
+
+ format = zalloc(sizeof(*format));
+ if (!format)
+ return NULL;
-static LIST_HEAD(pmus);
+ format->name = strdup(name);
+ if (!format->name) {
+ free(format);
+ return NULL;
+ }
+ list_add_tail(&format->list, list);
+ return format;
+}
+
+/* Called at the end of parsing a format. */
+void perf_pmu_format__set_value(void *vformat, int config, unsigned long *bits)
+{
+ struct perf_pmu_format *format = vformat;
+
+ format->value = config;
+ memcpy(format->bits, bits, sizeof(format->bits));
+}
+
+static void __perf_pmu_format__load(struct perf_pmu_format *format, FILE *file)
+{
+ void *scanner;
+ int ret;
+
+ ret = perf_pmu_lex_init(&scanner);
+ if (ret)
+ return;
+
+ perf_pmu_set_in(file, scanner);
+ ret = perf_pmu_parse(format, scanner);
+ perf_pmu_lex_destroy(scanner);
+ format->loaded = true;
+}
+
+static void perf_pmu_format__load(const struct perf_pmu *pmu, struct perf_pmu_format *format)
+{
+ char path[PATH_MAX];
+ FILE *file = NULL;
+
+ if (format->loaded)
+ return;
+
+ if (!perf_pmu__pathname_scnprintf(path, sizeof(path), pmu->name, "format"))
+ return;
+
+ assert(strlen(path) + strlen(format->name) + 2 < sizeof(path));
+ strcat(path, "/");
+ strcat(path, format->name);
+
+ file = fopen(path, "r");
+ if (!file)
+ return;
+ __perf_pmu_format__load(format, file);
+ fclose(file);
+}
/*
* Parse & process all the sysfs attributes located under
* the directory specified in 'dir' parameter.
*/
-int perf_pmu__format_parse(char *dir, struct list_head *head)
+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 = opendir(dir);
- if (!format_dir)
- return -EINVAL;
+ io_dir__init(&format_dir, dirfd);
- while (!ret && (evt_ent = readdir(format_dir))) {
- char path[PATH_MAX];
+ while ((evt_ent = io_dir__readdir(&format_dir)) != NULL) {
+ struct perf_pmu_format *format;
char *name = evt_ent->d_name;
- FILE *file;
- if (!strcmp(name, ".") || !strcmp(name, ".."))
+ if (io_dir__is_dir(&format_dir, evt_ent))
continue;
- snprintf(path, PATH_MAX, "%s/%s", dir, name);
-
- ret = -EINVAL;
- file = fopen(path, "r");
- if (!file)
+ format = perf_pmu__new_format(&pmu->format, name);
+ if (!format) {
+ ret = -ENOMEM;
break;
+ }
- perf_pmu_in = file;
- ret = perf_pmu_parse(head, name);
- fclose(file);
+ if (eager_load) {
+ FILE *file;
+ int fd = openat(dirfd, name, O_RDONLY);
+
+ if (fd < 0) {
+ ret = -errno;
+ break;
+ }
+ file = fdopen(fd, "r");
+ if (!file) {
+ close(fd);
+ break;
+ }
+ __perf_pmu_format__load(format, file);
+ fclose(file);
+ }
}
- closedir(format_dir);
+ close(format_dir.dirfd);
return ret;
}
@@ -83,27 +247,22 @@ int perf_pmu__format_parse(char *dir, struct list_head *head)
* located at:
* /sys/bus/event_source/devices/<dev>/format as sysfs group attributes.
*/
-static int pmu_format(const char *name, struct list_head *format)
+static int pmu_format(struct perf_pmu *pmu, int dirfd, const char *name, bool eager_load)
{
- char path[PATH_MAX];
- const char *sysfs = sysfs__mountpoint();
-
- if (!sysfs)
- return -1;
-
- snprintf(path, PATH_MAX,
- "%s" EVENT_SOURCE_DEVICE_PATH "%s/format", sysfs, name);
+ int fd;
- if (!file_available(path))
+ fd = perf_pmu__pathname_fd(dirfd, name, "format", O_DIRECTORY);
+ if (fd < 0)
return 0;
- if (perf_pmu__format_parse(path, format))
+ /* it'll close the fd */
+ if (perf_pmu__format_parse(pmu, fd, eager_load))
return -1;
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;
@@ -140,15 +299,24 @@ out:
return ret;
}
-static int perf_pmu__parse_scale(struct perf_pmu_alias *alias, char *dir, char *name)
+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;
ssize_t sret;
+ size_t len;
char scale[128];
int fd, ret = -1;
char path[PATH_MAX];
- scnprintf(path, PATH_MAX, "%s/%s.scale", dir, name);
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ return 0;
+ scnprintf(path + len, sizeof(path) - len, "%s/events/%s.scale", pmu->name, alias->name);
fd = open(path, O_RDONLY);
if (fd == -1)
@@ -172,13 +340,18 @@ error:
return ret;
}
-static int perf_pmu__parse_unit(struct perf_pmu_alias *alias, char *dir, char *name)
+static int perf_pmu__parse_unit(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
{
char path[PATH_MAX];
+ size_t len;
ssize_t sret;
int fd;
- scnprintf(path, PATH_MAX, "%s/%s.unit", dir, name);
+
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ return 0;
+ scnprintf(path + len, sizeof(path) - len, "%s/events/%s.unit", pmu->name, alias->name);
fd = open(path, O_RDONLY);
if (fd == -1)
@@ -202,215 +375,294 @@ error:
return -1;
}
-static int
-perf_pmu__parse_per_pkg(struct perf_pmu_alias *alias, char *dir, char *name)
+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;
int fd;
- scnprintf(path, PATH_MAX, "%s/%s.per-pkg", dir, name);
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ 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_alias *alias,
- char *dir, char *name)
+static void perf_pmu__parse_per_pkg(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
{
- char path[PATH_MAX];
- int fd;
+ alias->per_pkg = perf_pmu__parse_event_source_bool(pmu->name, alias->name, "per-pkg");
+}
- scnprintf(path, PATH_MAX, "%s/%s.snapshot", dir, name);
+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");
+}
- fd = open(path, O_RDONLY);
- if (fd == -1)
- return -1;
+/* Delete an alias entry. */
+static void perf_pmu_free_alias(struct perf_pmu_alias *alias)
+{
+ if (!alias)
+ return;
- alias->snapshot = true;
- close(fd);
- return 0;
+ zfree(&alias->name);
+ zfree(&alias->desc);
+ zfree(&alias->long_desc);
+ zfree(&alias->topic);
+ zfree(&alias->pmu_name);
+ parse_events_terms__exit(&alias->terms);
+ free(alias);
}
-static void perf_pmu_assign_str(char *name, const char *field, char **old_str,
- char **new_str)
+static void perf_pmu__del_aliases(struct perf_pmu *pmu)
{
- if (!*old_str)
- goto set_new;
+ struct hashmap_entry *entry;
+ size_t bkt;
- if (*new_str) { /* Have new string, check with old */
- if (strcasecmp(*old_str, *new_str))
- pr_debug("alias %s differs in field '%s'\n",
- name, field);
- zfree(old_str);
- } else /* Nothing new --> keep old string */
+ if (!pmu->aliases)
return;
-set_new:
- *old_str = *new_str;
- *new_str = NULL;
+
+ hashmap__for_each_entry(pmu->aliases, entry, bkt)
+ perf_pmu_free_alias(entry->pvalue);
+
+ hashmap__free(pmu->aliases);
+ pmu->aliases = NULL;
}
-static void perf_pmu_update_alias(struct perf_pmu_alias *old,
- struct perf_pmu_alias *newalias)
+static struct perf_pmu_alias *perf_pmu__find_alias(struct perf_pmu *pmu,
+ const char *name,
+ bool load)
{
- perf_pmu_assign_str(old->name, "desc", &old->desc, &newalias->desc);
- perf_pmu_assign_str(old->name, "long_desc", &old->long_desc,
- &newalias->long_desc);
- perf_pmu_assign_str(old->name, "topic", &old->topic, &newalias->topic);
- perf_pmu_assign_str(old->name, "metric_expr", &old->metric_expr,
- &newalias->metric_expr);
- perf_pmu_assign_str(old->name, "metric_name", &old->metric_name,
- &newalias->metric_name);
- perf_pmu_assign_str(old->name, "value", &old->str, &newalias->str);
- old->scale = newalias->scale;
- old->per_pkg = newalias->per_pkg;
- old->snapshot = newalias->snapshot;
- memcpy(old->unit, newalias->unit, sizeof(old->unit));
+ struct perf_pmu_alias *alias;
+ bool has_sysfs_event;
+ char event_file_name[FILENAME_MAX + 8];
+
+ if (hashmap__find(pmu->aliases, name, &alias))
+ return alias;
+
+ if (!load || pmu->sysfs_aliases_loaded)
+ return NULL;
+
+ /*
+ * 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) {
+ 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);
+ }
+ if (has_sysfs_event) {
+ pmu_aliases_parse(pmu);
+ if (hashmap__find(pmu->aliases, name, &alias))
+ return alias;
+ }
+
+ return NULL;
}
-/* Delete an alias entry. */
-void perf_pmu_free_alias(struct perf_pmu_alias *newalias)
+static bool assign_str(const char *name, const char *field, char **old_str,
+ const char *new_str)
{
- zfree(&newalias->name);
- zfree(&newalias->desc);
- zfree(&newalias->long_desc);
- zfree(&newalias->topic);
- zfree(&newalias->str);
- zfree(&newalias->metric_expr);
- zfree(&newalias->metric_name);
- parse_events_terms__purge(&newalias->terms);
- free(newalias);
+ if (!*old_str && new_str) {
+ *old_str = strdup(new_str);
+ return true;
+ }
+
+ if (!new_str || !strcasecmp(*old_str, new_str))
+ return false; /* Nothing to update. */
+
+ pr_debug("alias %s differs in field '%s' ('%s' != '%s')\n",
+ name, field, *old_str, new_str);
+ zfree(old_str);
+ *old_str = strdup(new_str);
+ return true;
}
-/* Merge an alias, search in alias list. If this name is already
- * present merge both of them to combine all information.
- */
-static bool perf_pmu_merge_alias(struct perf_pmu_alias *newalias,
- struct list_head *alist)
+static void read_alias_info(struct perf_pmu *pmu, struct perf_pmu_alias *alias)
{
- struct perf_pmu_alias *a;
+ if (!alias->from_sysfs || alias->info_loaded)
+ return;
- list_for_each_entry(a, alist, list) {
- if (!strcasecmp(newalias->name, a->name)) {
- perf_pmu_update_alias(a, newalias);
- perf_pmu_free_alias(newalias);
- return true;
- }
+ /*
+ * load unit name and scale if available
+ */
+ perf_pmu__parse_unit(pmu, alias);
+ perf_pmu__parse_scale(pmu, alias);
+ perf_pmu__parse_per_pkg(pmu, alias);
+ perf_pmu__parse_snapshot(pmu, alias);
+}
+
+struct update_alias_data {
+ struct perf_pmu *pmu;
+ struct perf_pmu_alias *alias;
+};
+
+static int update_alias(const struct pmu_event *pe,
+ const struct pmu_events_table *table __maybe_unused,
+ void *vdata)
+{
+ struct update_alias_data *data = vdata;
+ int ret = 0;
+
+ read_alias_info(data->pmu, data->alias);
+ assign_str(pe->name, "desc", &data->alias->desc, pe->desc);
+ assign_str(pe->name, "long_desc", &data->alias->long_desc, pe->long_desc);
+ 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);
}
- return false;
+ if (!ret && pe->unit) {
+ char *unit;
+
+ ret = perf_pmu__convert_scale(pe->unit, &unit, &data->alias->scale);
+ 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 list_head *list, char *dir, char *name,
- char *desc, char *val,
- char *long_desc, char *topic,
- char *unit, char *perpkg,
- char *metric_expr,
- char *metric_name,
- char *deprecated)
+static int perf_pmu__new_alias(struct perf_pmu *pmu, const char *name,
+ const char *desc, const char *val, FILE *val_fd,
+ const struct pmu_event *pe, enum event_source src)
{
- struct parse_events_term *term;
- struct perf_pmu_alias *alias;
- int ret;
- int num;
- char newval[256];
+ 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;
- alias = malloc(sizeof(*alias));
+ if (perf_pmu__find_alias(pmu, name, /*load=*/ false)) {
+ /* Alias was already created/loaded. */
+ return 0;
+ }
+
+ if (pe) {
+ long_desc = pe->long_desc;
+ topic = pe->topic;
+ unit = pe->unit;
+ perpkg = pe->perpkg;
+ deprecated = pe->deprecated;
+ if (pe->pmu && strcmp(pe->pmu, "default_core"))
+ pmu_name = pe->pmu;
+ }
+
+ alias = zalloc(sizeof(*alias));
if (!alias)
return -ENOMEM;
- INIT_LIST_HEAD(&alias->terms);
+ parse_events_terms__init(&alias->terms);
alias->scale = 1.0;
alias->unit[0] = '\0';
- alias->per_pkg = false;
+ alias->per_pkg = perpkg;
alias->snapshot = false;
- alias->deprecated = false;
+ alias->deprecated = deprecated;
+ alias->retirement_latency_mean = 0.0;
+ alias->retirement_latency_min = 0.0;
+ alias->retirement_latency_max = 0.0;
+
+ 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;
- ret = parse_events_terms(&alias->terms, val);
+ 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;
}
- /* Scan event and remove leading zeroes, spaces, newlines, some
- * platforms have terms specified as
- * event=0x0091 (read from files ../<PMU>/events/<FILE>
- * and terms specified as event=0x91 (read from JSON files).
- *
- * Rebuild string to make alias->str member comparable.
- */
- memset(newval, 0, sizeof(newval));
- ret = 0;
- list_for_each_entry(term, &alias->terms, list) {
- if (ret)
- ret += scnprintf(newval + ret, sizeof(newval) - ret,
- ",");
- if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM)
- ret += scnprintf(newval + ret, sizeof(newval) - ret,
- "%s=%#x", term->config, term->val.num);
- else if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR)
- ret += scnprintf(newval + ret, sizeof(newval) - ret,
- "%s=%s", term->config, term->val.str);
- }
-
alias->name = strdup(name);
- if (dir) {
- /*
- * load unit name and scale if available
- */
- perf_pmu__parse_unit(alias, dir, name);
- perf_pmu__parse_scale(alias, dir, name);
- perf_pmu__parse_per_pkg(alias, dir, name);
- perf_pmu__parse_snapshot(alias, dir, name);
- }
-
- alias->metric_expr = metric_expr ? strdup(metric_expr) : NULL;
- alias->metric_name = metric_name ? strdup(metric_name): NULL;
alias->desc = desc ? strdup(desc) : NULL;
alias->long_desc = long_desc ? strdup(long_desc) :
desc ? strdup(desc) : NULL;
alias->topic = topic ? strdup(topic) : NULL;
+ alias->pmu_name = pmu_name ? strdup(pmu_name) : NULL;
if (unit) {
- if (perf_pmu__convert_scale(unit, &unit, &alias->scale) < 0)
+ if (perf_pmu__convert_scale(unit, (char **)&unit, &alias->scale) < 0) {
+ perf_pmu_free_alias(alias);
return -1;
+ }
snprintf(alias->unit, sizeof(alias->unit), "%s", unit);
}
- alias->per_pkg = perpkg && sscanf(perpkg, "%d", &num) == 1 && num == 1;
- alias->str = strdup(newval);
-
- if (deprecated)
- alias->deprecated = true;
-
- if (!perf_pmu_merge_alias(alias, list))
- list_add_tail(&alias->list, list);
+ switch (src) {
+ default:
+ case EVENT_SRC_SYSFS:
+ alias->from_sysfs = true;
+ if (pmu->events_table) {
+ /* Update an event from sysfs with json data. */
+ struct update_alias_data data = {
+ .pmu = pmu,
+ .alias = alias,
+ };
+ if (pmu_events_table__find_event(pmu->events_table, pmu, name,
+ update_alias, &data) == 0)
+ pmu->cpu_common_json_aliases++;
+ }
+ pmu->sysfs_aliases++;
+ break;
+ case EVENT_SRC_CPU_JSON:
+ pmu->cpu_json_aliases++;
+ break;
+ case EVENT_SRC_SYS_JSON:
+ pmu->sys_json_aliases++;
+ break;
+ }
+ hashmap__set(pmu->aliases, alias->name, alias, /*old_key=*/ NULL, &old_alias);
+ perf_pmu_free_alias(old_alias);
return 0;
}
-static int perf_pmu__new_alias(struct list_head *list, char *dir, char *name, FILE *file)
-{
- char buf[256];
- int ret;
-
- ret = fread(buf, 1, sizeof(buf), file);
- if (ret == 0)
- return -EINVAL;
-
- buf[ret] = 0;
-
- /* Remove trailing newline from sysfs file */
- strim(buf);
-
- return __perf_pmu__new_alias(list, dir, name, NULL, buf, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL);
-}
-
-static inline bool pmu_alias_info_file(char *name)
+static inline bool pmu_alias_info_file(const char *name)
{
size_t len;
@@ -428,21 +680,19 @@ static inline bool pmu_alias_info_file(char *name)
}
/*
- * Process all the sysfs attributes located under the directory
- * specified in 'dir' parameter.
+ * Reading the pmu event aliases definition, which should be located at:
+ * /sys/bus/event_source/devices/<dev>/events as sysfs group attributes.
*/
-static int pmu_aliases_parse(char *dir, struct list_head *head)
+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 = opendir(dir);
- if (!event_dir)
- return -EINVAL;
+ io_dir__init(&event_dir, events_dir_fd);
- while ((evt_ent = readdir(event_dir))) {
- char path[PATH_MAX];
+ while ((evt_ent = io_dir__readdir(&event_dir))) {
char *name = evt_ent->d_name;
+ int fd;
FILE *file;
if (!strcmp(name, ".") || !strcmp(name, ".."))
@@ -454,58 +704,79 @@ static int pmu_aliases_parse(char *dir, struct list_head *head)
if (pmu_alias_info_file(name))
continue;
- scnprintf(path, PATH_MAX, "%s/%s", dir, name);
-
- file = fopen(path, "r");
+ fd = openat(events_dir_fd, name, O_RDONLY);
+ if (fd == -1) {
+ pr_debug("Cannot open %s\n", name);
+ continue;
+ }
+ file = fdopen(fd, "r");
if (!file) {
- pr_debug("Cannot open %s\n", path);
+ close(fd);
continue;
}
- if (perf_pmu__new_alias(head, dir, name, file) < 0)
+ if (perf_pmu__new_alias(pmu, name, /*desc=*/ NULL,
+ /*val=*/ NULL, file, /*pe=*/ NULL,
+ EVENT_SRC_SYSFS) < 0)
pr_debug("Cannot set up %s\n", name);
fclose(file);
}
- closedir(event_dir);
+ pmu->sysfs_aliases_loaded = true;
return 0;
}
-/*
- * Reading the pmu event aliases definition, which should be located at:
- * /sys/bus/event_source/devices/<dev>/events as sysfs group attributes.
- */
-static int pmu_aliases(const char *name, struct list_head *head)
+static int pmu_aliases_parse(struct perf_pmu *pmu)
{
char path[PATH_MAX];
- const char *sysfs = sysfs__mountpoint();
+ size_t len;
+ int events_dir_fd, ret;
- if (!sysfs)
- return -1;
+ if (pmu->sysfs_aliases_loaded)
+ return 0;
- snprintf(path, PATH_MAX,
- "%s/bus/event_source/devices/%s/events", sysfs, name);
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ return 0;
+ scnprintf(path + len, sizeof(path) - len, "%s/events", pmu->name);
- if (!file_available(path))
+ events_dir_fd = open(path, O_DIRECTORY);
+ if (events_dir_fd == -1) {
+ pmu->sysfs_aliases_loaded = true;
return 0;
+ }
+ ret = __pmu_aliases_parse(pmu, events_dir_fd);
+ close(events_dir_fd);
+ return ret;
+}
- if (pmu_aliases_parse(path, head))
- return -1;
+static int pmu_aliases_parse_eager(struct perf_pmu *pmu, int sysfs_fd)
+{
+ char path[FILENAME_MAX + 7];
+ int ret, events_dir_fd;
- return 0;
+ scnprintf(path, sizeof(path), "%s/events", pmu->name);
+ events_dir_fd = openat(sysfs_fd, path, O_DIRECTORY, 0);
+ if (events_dir_fd == -1) {
+ pmu->sysfs_aliases_loaded = true;
+ return 0;
+ }
+ ret = __pmu_aliases_parse(pmu, events_dir_fd);
+ close(events_dir_fd);
+ return ret;
}
-static int pmu_alias_terms(struct perf_pmu_alias *alias,
- struct list_head *terms)
+static int pmu_alias_terms(struct perf_pmu_alias *alias, int err_loc, struct list_head *terms)
{
struct parse_events_term *term, *cloned;
- LIST_HEAD(list);
- int ret;
+ 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);
- list_for_each_entry(term, &alias->terms, list) {
- ret = parse_events_term__clone(&cloned, term);
if (ret) {
- parse_events_terms__purge(&list);
+ parse_events_terms__exit(&clone_terms);
return ret;
}
/*
@@ -513,429 +784,558 @@ static int pmu_alias_terms(struct perf_pmu_alias *alias,
* which we don't want for implicit terms in aliases.
*/
cloned->weak = true;
- list_add_tail(&cloned->list, &list);
+ cloned->err_term = cloned->err_val = err_loc;
+ list_add_tail(&cloned->list, &clone_terms.terms);
}
- list_splice(&list, terms);
+ list_splice_init(&clone_terms.terms, terms);
+ parse_events_terms__exit(&clone_terms);
return 0;
}
/*
- * Reading/parsing the default pmu type value, which should be
- * located at:
- * /sys/bus/event_source/devices/<dev>/type as sysfs attribute.
+ * Uncore PMUs have a "cpumask" file under sysfs. CPU PMUs (e.g. on arm/arm64)
+ * may have a "cpus" file.
*/
-static int pmu_type(const char *name, __u32 *type)
+static struct perf_cpu_map *pmu_cpumask(int dirfd, const char *pmu_name, bool is_core)
{
- char path[PATH_MAX];
- FILE *file;
- int ret = 0;
- const char *sysfs = sysfs__mountpoint();
+ const char *templates[] = {
+ "cpumask",
+ "cpus",
+ NULL
+ };
+ const char **template;
- if (!sysfs)
- return -1;
+ for (template = templates; *template; template++) {
+ 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;
- snprintf(path, PATH_MAX,
- "%s" EVENT_SOURCE_DEVICE_PATH "%s/type", sysfs, name);
+ io__init(&io, io.fd, buf, sizeof(buf));
+ ret = io__getline(&io, &cpumask, &cpumask_len);
+ close(io.fd);
+ if (ret < 0)
+ continue;
- if (access(path, R_OK) < 0)
- return -1;
+ cpus = perf_cpu_map__new(cpumask);
+ free(cpumask);
+ if (cpus)
+ return cpus;
+ }
- file = fopen(path, "r");
- if (!file)
- return -EINVAL;
+ /* Nothing found, for core PMUs assume this means all CPUs. */
+ return is_core ? cpu_map__online() : NULL;
+}
- if (1 != fscanf(file, "%u", type))
- ret = -1;
+static bool pmu_is_uncore(int dirfd, const char *name)
+{
+ int fd;
- fclose(file);
- return ret;
+ fd = perf_pmu__pathname_fd(dirfd, name, "cpumask", O_PATH);
+ if (fd < 0)
+ return false;
+
+ close(fd);
+ return true;
}
-/* Add all pmus in sysfs to pmu list: */
-static void pmu_read_sysfs(void)
+static char *pmu_id(const char *name)
{
- char path[PATH_MAX];
- DIR *dir;
- struct dirent *dent;
- const char *sysfs = sysfs__mountpoint();
-
- if (!sysfs)
- return;
+ char path[PATH_MAX], *str;
+ size_t len;
- snprintf(path, PATH_MAX,
- "%s" EVENT_SOURCE_DEVICE_PATH, sysfs);
+ perf_pmu__pathname_scnprintf(path, sizeof(path), name, "identifier");
- dir = opendir(path);
- if (!dir)
- return;
+ if (filename__read_str(path, &str, &len) < 0)
+ return NULL;
- while ((dent = readdir(dir))) {
- if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
- continue;
- /* add to static LIST_HEAD(pmus): */
- perf_pmu__find(dent->d_name);
- }
+ str[len - 1] = 0; /* remove line feed */
- closedir(dir);
+ return str;
}
-static struct perf_cpu_map *__pmu_cpumask(const char *path)
+/**
+ * is_sysfs_pmu_core() - PMU CORE devices have different name other than cpu in
+ * sysfs on some platforms like ARM or Intel hybrid. Looking for
+ * possible the cpus file in sysfs files to identify whether this is a
+ * core device.
+ * @name: The PMU name such as "cpu_atom".
+ */
+static int is_sysfs_pmu_core(const char *name)
{
- FILE *file;
- struct perf_cpu_map *cpus;
-
- file = fopen(path, "r");
- if (!file)
- return NULL;
+ char path[PATH_MAX];
- cpus = perf_cpu_map__read(file);
- fclose(file);
- return cpus;
+ if (!perf_pmu__pathname_scnprintf(path, sizeof(path), name, "cpus"))
+ return 0;
+ return file_available(path);
}
-/*
- * Uncore PMUs have a "cpumask" file under sysfs. CPU PMUs (e.g. on arm/arm64)
- * may have a "cpus" file.
+/**
+ * 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
*/
-#define CPUS_TEMPLATE_UNCORE "%s/bus/event_source/devices/%s/cpumask"
-#define CPUS_TEMPLATE_CPU "%s/bus/event_source/devices/%s/cpus"
+static size_t pmu_deduped_name_len(const struct perf_pmu *pmu, const char *name,
+ bool skip_duplicate_pmus)
+{
+ return skip_duplicate_pmus && !pmu->is_core
+ ? pmu_name_len_no_suffix(name)
+ : strlen(name);
+}
-static struct perf_cpu_map *pmu_cpumask(const char *name)
+/**
+ * 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 wildcard argument to match.
+ */
+static bool perf_pmu__match_wildcard(const char *pmu_name, const char *tok)
{
- char path[PATH_MAX];
- struct perf_cpu_map *cpus;
- const char *sysfs = sysfs__mountpoint();
- const char *templates[] = {
- CPUS_TEMPLATE_UNCORE,
- CPUS_TEMPLATE_CPU,
- NULL
- };
- const char **template;
+ const char *p, *suffix;
+ bool has_hex = false;
+ size_t tok_len = strlen(tok);
- if (!sysfs)
- return NULL;
+ /* Check start of pmu_name for equality. */
+ if (strncmp(pmu_name, tok, tok_len))
+ return false;
- for (template = templates; *template; template++) {
- snprintf(path, PATH_MAX, *template, sysfs, name);
- cpus = __pmu_cpumask(path);
- if (cpus)
- return cpus;
+ suffix = p = pmu_name + tok_len;
+ if (*p == 0)
+ return true;
+
+ if (*p == '_') {
+ ++p;
+ ++suffix;
}
- return NULL;
-}
+ /* Ensure we end in a number */
+ while (1) {
+ if (!isxdigit(*p))
+ return false;
+ if (!has_hex)
+ has_hex = !isdigit(*p);
+ if (*(++p) == 0)
+ break;
+ }
-static bool pmu_is_uncore(const char *name)
-{
- char path[PATH_MAX];
- const char *sysfs;
+ if (has_hex)
+ return (p - suffix) > 2;
- sysfs = sysfs__mountpoint();
- snprintf(path, PATH_MAX, CPUS_TEMPLATE_UNCORE, sysfs, name);
- return file_available(path);
+ return true;
}
-/*
- * PMU CORE devices have different name other than cpu in sysfs on some
- * platforms.
- * Looking for possible sysfs files to identify the arm core device.
+/**
+ * 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 int is_arm_pmu_core(const char *name)
+static bool perf_pmu__match_ignoring_suffix_uncore(const char *pmu_name, const char *tok)
{
- char path[PATH_MAX];
- const char *sysfs = sysfs__mountpoint();
+ size_t pmu_name_len, tok_len;
- if (!sysfs)
- return 0;
+ /* For robustness, check for NULL. */
+ if (pmu_name == NULL)
+ return tok == NULL;
- /* Look for cpu sysfs (specific to arm) */
- scnprintf(path, PATH_MAX, "%s/bus/event_source/devices/%s/cpus",
- sysfs, name);
- return file_available(path);
+ /* 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;
}
-static char *perf_pmu__getcpuid(struct perf_pmu *pmu)
+
+/**
+ * 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".
+ */
+static bool perf_pmu__match_wildcard_uncore(const char *pmu_name, const char *to_match)
{
- char *cpuid;
- static bool printed;
+ char *mutable_to_match, *tok, *tmp;
- cpuid = getenv("PERF_CPUID");
- if (cpuid)
- cpuid = strdup(cpuid);
- if (!cpuid)
- cpuid = get_cpuid_str(pmu);
- if (!cpuid)
- return NULL;
+ if (!pmu_name)
+ return false;
- if (!printed) {
- pr_debug("Using CPUID %s\n", cpuid);
- printed = true;
- }
- return cpuid;
-}
+ /* uncore_ prefixes are ignored. */
+ if (!strncmp(pmu_name, "uncore_", 7))
+ pmu_name += 7;
+ if (!strncmp(to_match, "uncore_", 7))
+ to_match += 7;
-struct pmu_events_map *perf_pmu__find_map(struct perf_pmu *pmu)
-{
- struct pmu_events_map *map;
- char *cpuid = perf_pmu__getcpuid(pmu);
- int i;
+ if (strchr(to_match, ',') == NULL)
+ return perf_pmu__match_wildcard(pmu_name, to_match);
- /* on some platforms which uses cpus map, cpuid can be NULL for
- * PMUs other than CORE PMUs.
- */
- if (!cpuid)
- return NULL;
+ /* Process comma separated list of PMU name components. */
+ mutable_to_match = strdup(to_match);
+ if (!mutable_to_match)
+ return false;
- i = 0;
- for (;;) {
- map = &pmu_events_map[i++];
- if (!map->table) {
- map = NULL;
- break;
- }
+ tok = strtok_r(mutable_to_match, ",", &tmp);
+ while (tok) {
+ size_t tok_len = strlen(tok);
- if (!strcmp_cpuid_str(map->cpuid, cpuid))
- break;
+ if (strncmp(pmu_name, tok, tok_len)) {
+ /* Mismatch between part of pmu_name and tok. */
+ free(mutable_to_match);
+ return false;
+ }
+ /* 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);
}
- free(cpuid);
- return map;
+ free(mutable_to_match);
+ return *pmu_name == '\0';
}
-bool pmu_uncore_alias_match(const char *pmu_name, const char *name)
+bool pmu_uncore_identifier_match(const char *compat, const char *id)
{
- char *tmp = NULL, *tok, *str;
- bool res;
+ regex_t re;
+ regmatch_t pmatch[1];
+ int match;
- str = strdup(pmu_name);
- if (!str)
+ if (regcomp(&re, compat, REG_EXTENDED) != 0) {
+ /* Warn unable to generate match particular string. */
+ pr_info("Invalid regular expression %s\n", compat);
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;
}
- /*
- * 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".
- */
- for (; tok; name += strlen(tok), tok = strtok_r(NULL, ",", &tmp)) {
- name = strstr(name, tok);
- if (!name) {
- res = false;
- goto out;
- }
+ match = !regexec(&re, id, 1, pmatch, 0);
+ if (match) {
+ /* Ensure a full match. */
+ match = pmatch[0].rm_so == 0 && (size_t)pmatch[0].rm_eo == strlen(id);
}
+ regfree(&re);
- res = true;
-out:
- free(str);
- return res;
+ return match;
+}
+
+static int pmu_add_cpu_aliases_map_callback(const struct pmu_event *pe,
+ const struct pmu_events_table *table __maybe_unused,
+ void *vdata)
+{
+ struct perf_pmu *pmu = vdata;
+
+ perf_pmu__new_alias(pmu, pe->name, pe->desc, pe->event, /*val_fd=*/ NULL,
+ pe, EVENT_SRC_CPU_JSON);
+ return 0;
}
/*
- * From the pmu_events_map, find the table of PMU events that corresponds
- * to the current running CPU. Then, add all PMU events from that table
- * as aliases.
+ * From the pmu_events_table, find the events that correspond to the given
+ * PMU and add them to the list 'head'.
*/
-void pmu_add_cpu_aliases_map(struct list_head *head, struct perf_pmu *pmu,
- struct pmu_events_map *map)
+void pmu_add_cpu_aliases_table(struct perf_pmu *pmu, const struct pmu_events_table *table)
{
- int i;
- const char *name = pmu->name;
- /*
- * Found a matching PMU events table. Create aliases
- */
- i = 0;
- while (1) {
- const char *cpu_name = is_arm_pmu_core(name) ? name : "cpu";
- struct pmu_event *pe = &map->table[i++];
- const char *pname = pe->pmu ? pe->pmu : cpu_name;
+ pmu_events_table__for_each_event(table, pmu, pmu_add_cpu_aliases_map_callback, pmu);
+}
- if (!pe->name) {
- if (pe->metric_group || pe->metric_name)
- continue;
- break;
- }
+static void pmu_add_cpu_aliases(struct perf_pmu *pmu)
+{
+ if (!pmu->events_table)
+ return;
- if (pmu_is_uncore(name) &&
- pmu_uncore_alias_match(pname, name))
- goto new_alias;
+ if (pmu->cpu_aliases_added)
+ return;
- if (strcmp(pname, name))
- continue;
+ pmu_add_cpu_aliases_table(pmu, pmu->events_table);
+ pmu->cpu_aliases_added = true;
+}
+
+static int pmu_add_sys_aliases_iter_fn(const struct pmu_event *pe,
+ const struct pmu_events_table *table __maybe_unused,
+ void *vdata)
+{
+ struct perf_pmu *pmu = vdata;
-new_alias:
- /* need type casts to override 'const' */
- __perf_pmu__new_alias(head, NULL, (char *)pe->name,
- (char *)pe->desc, (char *)pe->event,
- (char *)pe->long_desc, (char *)pe->topic,
- (char *)pe->unit, (char *)pe->perpkg,
- (char *)pe->metric_expr,
- (char *)pe->metric_name,
- (char *)pe->deprecated);
+ 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_identifier_match(pe->compat, pmu->id)) {
+ /* Id matched. */
+ perf_pmu__new_alias(pmu,
+ pe->name,
+ pe->desc,
+ pe->event,
+ /*val_fd=*/ NULL,
+ pe,
+ EVENT_SRC_SYS_JSON);
+ }
+ return 0;
}
-static void pmu_add_cpu_aliases(struct list_head *head, struct perf_pmu *pmu)
+void pmu_add_sys_aliases(struct perf_pmu *pmu)
{
- struct pmu_events_map *map;
-
- map = perf_pmu__find_map(pmu);
- if (!map)
+ if (!pmu->id)
return;
- pmu_add_cpu_aliases_map(head, pmu, map);
+ pmu_for_each_sys_event(pmu_add_sys_aliases_iter_fn, pmu);
}
-struct perf_event_attr * __weak
-perf_pmu__get_default_config(struct perf_pmu *pmu __maybe_unused)
+static char *pmu_find_alias_name(struct perf_pmu *pmu, int dirfd)
{
- return NULL;
+ FILE *file = perf_pmu__open_file_at(pmu, dirfd, "alias");
+ char *line = NULL;
+ size_t line_len = 0;
+ ssize_t ret;
+
+ if (!file)
+ return NULL;
+
+ ret = getline(&line, &line_len, file);
+ if (ret < 0) {
+ fclose(file);
+ return NULL;
+ }
+ /* Remove trailing newline. */
+ if (ret > 0 && line[ret - 1] == '\n')
+ line[--ret] = '\0';
+
+ fclose(file);
+ return line;
}
-static int pmu_max_precise(const char *name)
+static int pmu_max_precise(int dirfd, struct perf_pmu *pmu)
{
- char path[PATH_MAX];
int max_precise = -1;
- scnprintf(path, PATH_MAX,
- "bus/event_source/devices/%s/caps/max_precise",
- name);
-
- sysfs__read_int(path, &max_precise);
+ perf_pmu__scan_file_at(pmu, dirfd, "caps/max_precise", "%d", &max_precise);
return max_precise;
}
-static struct perf_pmu *pmu_lookup(const char *name)
+void __weak
+perf_pmu__arch_init(struct perf_pmu *pmu)
+{
+ if (pmu->is_core)
+ 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;
+}
+
+struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *name,
+ bool eager_load)
{
struct perf_pmu *pmu;
- LIST_HEAD(format);
- LIST_HEAD(aliases);
- __u32 type;
+
+ pmu = zalloc(sizeof(*pmu));
+ if (!pmu)
+ return NULL;
+
+ 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", &pmu->type) != 1) {
+ 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(name, &format))
+ 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->is_uncore = pmu_is_uncore(dirfd, name);
+ if (pmu->is_uncore)
+ pmu->id = pmu_id(name);
+ pmu->max_precise = pmu_max_precise(dirfd, pmu);
+ pmu->alias_name = pmu_find_alias_name(pmu, dirfd);
+ pmu->events_table = perf_pmu__find_events_table(pmu);
/*
- * Check the type first to avoid unnecessary work.
+ * Load the sys json events/aliases when loading the PMU as each event
+ * may have a different compat regular expression. We therefore can't
+ * know the number of sys json events/aliases without computing the
+ * regular expressions for them all.
*/
- if (pmu_type(name, &type))
- return NULL;
+ pmu_add_sys_aliases(pmu);
+ list_add_tail(&pmu->list, pmus);
- if (pmu_aliases(name, &aliases))
- return NULL;
+ perf_pmu__arch_init(pmu);
+
+ if (eager_load)
+ pmu_aliases_parse_eager(pmu, dirfd);
+
+ return pmu;
+}
+
+/* Creates the PMU when sysfs scanning fails. */
+struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pmus)
+{
+ struct perf_pmu *pmu = zalloc(sizeof(*pmu));
- pmu = zalloc(sizeof(*pmu));
if (!pmu)
return NULL;
- pmu->cpus = pmu_cpumask(name);
- pmu->name = strdup(name);
- pmu->type = type;
- pmu->is_uncore = pmu_is_uncore(name);
- pmu->max_precise = pmu_max_precise(name);
- pmu_add_cpu_aliases(&aliases, pmu);
+ pmu->name = strdup("cpu");
+ if (!pmu->name) {
+ free(pmu);
+ return NULL;
+ }
+
+ pmu->is_core = true;
+ pmu->type = PERF_TYPE_RAW;
+ 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_splice(&format, &pmu->format);
- list_splice(&aliases, &pmu->aliases);
- list_add_tail(&pmu->list, &pmus);
-
- pmu->default_config = perf_pmu__get_default_config(pmu);
-
+ list_add_tail(&pmu->list, core_pmus);
return pmu;
}
-static struct perf_pmu *pmu_find(const char *name)
+bool perf_pmu__is_fake(const struct perf_pmu *pmu)
{
- struct perf_pmu *pmu;
-
- list_for_each_entry(pmu, &pmus, list)
- if (!strcmp(pmu->name, name))
- return pmu;
-
- return NULL;
+ return pmu->type == PERF_PMU_TYPE_FAKE;
}
-struct perf_pmu *perf_pmu__find_by_type(unsigned int type)
+void perf_pmu__warn_invalid_formats(struct perf_pmu *pmu)
{
- struct perf_pmu *pmu;
+ struct perf_pmu_format *format;
- list_for_each_entry(pmu, &pmus, list)
- if (pmu->type == type)
- return pmu;
+ if (pmu->formats_checked)
+ return;
- return NULL;
-}
+ pmu->formats_checked = true;
-struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu)
-{
- /*
- * pmu iterator: If pmu is NULL, we start at the begin,
- * otherwise return the next pmu. Returns NULL on end.
- */
- if (!pmu) {
- pmu_read_sysfs();
- pmu = list_prepare_entry(pmu, &pmus, list);
+ /* fake pmu doesn't have format list */
+ if (perf_pmu__is_fake(pmu))
+ return;
+
+ list_for_each_entry(format, &pmu->format, list) {
+ perf_pmu_format__load(pmu, format);
+ if (format->value >= PERF_PMU_FORMAT_VALUE_CONFIG_END) {
+ pr_warning("WARNING: '%s' format '%s' requires 'perf_event_attr::config%d'"
+ "which is not supported by this version of perf!\n",
+ pmu->name, format->name, format->value);
+ return;
+ }
}
- list_for_each_entry_continue(pmu, &pmus, list)
- return pmu;
- return NULL;
}
-struct perf_pmu *evsel__find_pmu(struct evsel *evsel)
+bool evsel__is_aux_event(const struct evsel *evsel)
{
- struct perf_pmu *pmu = NULL;
+ struct perf_pmu *pmu;
- while ((pmu = perf_pmu__scan(pmu)) != NULL) {
- if (pmu->type == evsel->core.attr.type)
- break;
- }
+ if (evsel->needs_auxtrace_mmap)
+ return true;
- return pmu;
+ pmu = evsel__find_pmu(evsel);
+ return pmu && pmu->auxtrace;
}
-bool evsel__is_aux_event(struct evsel *evsel)
+/*
+ * Set @config_name to @val as long as the user hasn't already set or cleared it
+ * by passing a config term on the command line.
+ *
+ * @val is the value to put into the bits specified by @config_name rather than
+ * the bit pattern. It is shifted into position by this function, so to set
+ * something to true, pass 1 for val rather than a pre shifted value.
+ */
+#define field_prep(_mask, _val) (((_val) << (ffsll(_mask) - 1)) & (_mask))
+void evsel__set_config_if_unset(struct perf_pmu *pmu, struct evsel *evsel,
+ const char *config_name, u64 val)
{
- struct perf_pmu *pmu = evsel__find_pmu(evsel);
+ u64 user_bits = 0, bits;
+ struct evsel_config_term *term = evsel__get_config_term(evsel, CFG_CHG);
- return pmu && pmu->auxtrace;
-}
+ if (term)
+ user_bits = term->val.cfg_chg;
-struct perf_pmu *perf_pmu__find(const char *name)
-{
- struct perf_pmu *pmu;
+ bits = perf_pmu__format_bits(pmu, config_name);
- /*
- * Once PMU is loaded it stays in the list,
- * so we keep us from multiple reading/parsing
- * the pmu format definitions.
- */
- pmu = pmu_find(name);
- if (pmu)
- return pmu;
+ /* Do nothing if the user changed the value */
+ if (bits & user_bits)
+ return;
- return pmu_lookup(name);
+ /* Otherwise replace it */
+ evsel->core.attr.config &= ~bits;
+ evsel->core.attr.config |= field_prep(bits, val);
}
static struct perf_pmu_format *
-pmu_find_format(struct list_head *formats, const char *name)
+pmu_find_format(const struct list_head *formats, const char *name)
{
struct perf_pmu_format *format;
@@ -946,9 +1346,9 @@ pmu_find_format(struct list_head *formats, const char *name)
return NULL;
}
-__u64 perf_pmu__format_bits(struct list_head *formats, const char *name)
+__u64 perf_pmu__format_bits(struct perf_pmu *pmu, const char *name)
{
- struct perf_pmu_format *format = pmu_find_format(formats, name);
+ struct perf_pmu_format *format = pmu_find_format(&pmu->format, name);
__u64 bits = 0;
int fbit;
@@ -961,19 +1361,20 @@ __u64 perf_pmu__format_bits(struct list_head *formats, const char *name)
return bits;
}
-int perf_pmu__format_type(struct list_head *formats, const char *name)
+int perf_pmu__format_type(struct perf_pmu *pmu, const char *name)
{
- struct perf_pmu_format *format = pmu_find_format(formats, name);
+ struct perf_pmu_format *format = pmu_find_format(&pmu->format, name);
if (!format)
return -1;
+ perf_pmu_format__load(pmu, format);
return format->value;
}
/*
* Sets value based on the format definition (format parameter)
- * and unformated value (value parameter).
+ * and unformatted value (value parameter).
*/
static void pmu_format_value(unsigned long *format, __u64 value, __u64 *v,
bool zero)
@@ -1012,12 +1413,12 @@ static __u64 pmu_format_max_value(const unsigned long *format)
* in a config string) later on in the term list.
*/
static int pmu_resolve_param_term(struct parse_events_term *term,
- struct list_head *head_terms,
+ struct parse_events_terms *head_terms,
__u64 *value)
{
struct parse_events_term *t;
- list_for_each_entry(t, head_terms, list) {
+ list_for_each_entry(t, &head_terms->terms, list) {
if (t->type_val == PARSE_EVENTS__TERM_TYPE_NUM &&
t->config && !strcmp(t->config, term->config)) {
t->used = true;
@@ -1032,12 +1433,12 @@ static int pmu_resolve_param_term(struct parse_events_term *term,
return -1;
}
-static char *pmu_formats_string(struct list_head *formats)
+static char *pmu_formats_string(const struct list_head *formats)
{
struct perf_pmu_format *format;
char *str = NULL;
struct strbuf buf = STRBUF_INIT;
- unsigned i = 0;
+ unsigned int i = 0;
if (!formats)
return NULL;
@@ -1058,12 +1459,12 @@ error:
* Setup one of config[12] attr members based on the
* user input data - term parameter.
*/
-static int pmu_config_term(const char *pmu_name,
- struct list_head *formats,
+static int pmu_config_term(const struct perf_pmu *pmu,
struct perf_event_attr *attr,
struct parse_events_term *term,
- struct list_head *head_terms,
- bool zero, struct parse_events_error *err)
+ struct parse_events_terms *head_terms,
+ bool zero, bool apply_hardcoded,
+ struct parse_events_error *err)
{
struct perf_pmu_format *format;
__u64 *vp;
@@ -1077,25 +1478,60 @@ static int pmu_config_term(const char *pmu_name,
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_USER: /* Not hardcoded. */
+ return -EINVAL;
+ case PARSE_EVENTS__TERM_TYPE_NAME ... PARSE_EVENTS__TERM_TYPE_CPU:
+ /* Skip non-config terms. */
+ break;
+ default:
+ break;
+ }
return 0;
+ }
- format = pmu_find_format(formats, term->config);
+ format = pmu_find_format(&pmu->format, term->config);
if (!format) {
- char *pmu_term = pmu_formats_string(formats);
+ char *pmu_term = pmu_formats_string(&pmu->format);
char *unknown_term;
char *help_msg;
if (asprintf(&unknown_term,
"unknown term '%s' for pmu '%s'",
- term->config, pmu_name) < 0)
+ term->config, pmu->name) < 0)
unknown_term = NULL;
help_msg = parse_events_formats_error_string(pmu_term);
if (err) {
- parse_events__handle_error(err, term->err_term,
+ parse_events_error__handle(err, term->err_term,
unknown_term,
help_msg);
} else {
@@ -1105,7 +1541,7 @@ static int pmu_config_term(const char *pmu_name,
free(pmu_term);
return -EINVAL;
}
-
+ perf_pmu_format__load(pmu, format);
switch (format->value) {
case PERF_PMU_FORMAT_VALUE_CONFIG:
vp = &attr->config;
@@ -1116,6 +1552,9 @@ static int pmu_config_term(const char *pmu_name,
case PERF_PMU_FORMAT_VALUE_CONFIG2:
vp = &attr->config2;
break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG3:
+ vp = &attr->config3;
+ break;
default:
return -EINVAL;
}
@@ -1128,7 +1567,7 @@ static int pmu_config_term(const char *pmu_name,
if (term->no_value &&
bitmap_weight(format->bits, PERF_PMU_FORMAT_BITS) > 1) {
if (err) {
- parse_events__handle_error(err, term->err_val,
+ parse_events_error__handle(err, term->err_val,
strdup("no value assigned for term"),
NULL);
}
@@ -1143,7 +1582,7 @@ static int pmu_config_term(const char *pmu_name,
term->config, term->val.str);
}
if (err) {
- parse_events__handle_error(err, term->err_val,
+ parse_events_error__handle(err, term->err_val,
strdup("expected numeric value"),
NULL);
}
@@ -1160,13 +1599,12 @@ static int pmu_config_term(const char *pmu_name,
if (err) {
char *err_str;
- parse_events__handle_error(err, term->err_val,
- asprintf(&err_str,
- "value too big for format, maximum is %llu",
- (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;
}
/*
@@ -1179,16 +1617,19 @@ static int pmu_config_term(const char *pmu_name,
return 0;
}
-int perf_pmu__config_terms(const char *pmu_name, struct list_head *formats,
+int perf_pmu__config_terms(const struct perf_pmu *pmu,
struct perf_event_attr *attr,
- struct list_head *head_terms,
- bool zero, struct parse_events_error *err)
+ struct parse_events_terms *terms,
+ bool zero, bool apply_hardcoded,
+ struct parse_events_error *err)
{
struct parse_events_term *term;
- list_for_each_entry(term, head_terms, list) {
- if (pmu_config_term(pmu_name, formats, attr, term, head_terms,
- zero, err))
+ if (perf_pmu__is_hwmon(pmu))
+ return hwmon_pmu__config_terms(pmu, attr, terms, err);
+
+ list_for_each_entry(term, &terms->terms, list) {
+ if (pmu_config_term(pmu, attr, term, terms, zero, apply_hardcoded, err))
return -EINVAL;
}
@@ -1201,31 +1642,35 @@ int perf_pmu__config_terms(const char *pmu_name, struct list_head *formats,
* 2) pmu format definitions - specified by pmu parameter
*/
int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr,
- struct list_head *head_terms,
+ struct parse_events_terms *head_terms,
+ bool apply_hardcoded,
struct parse_events_error *err)
{
- bool zero = !!pmu->default_config;
+ bool zero = !!pmu->perf_event_attr_init_default;
+
+ /* Fake PMU doesn't have proper terms so nothing to configure in attr. */
+ if (perf_pmu__is_fake(pmu))
+ return 0;
- attr->type = pmu->type;
- return perf_pmu__config_terms(pmu->name, &pmu->format, attr,
- head_terms, zero, err);
+ 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,
struct parse_events_term *term)
{
struct perf_pmu_alias *alias;
- char *name;
+ const char *name;
if (parse_events__is_hardcoded_term(term))
return NULL;
if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM) {
- if (term->val.num != 1)
+ if (!term->no_value)
return NULL;
if (pmu_find_format(&pmu->format, term->config))
return NULL;
name = term->config;
+
} else if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR) {
if (strcasecmp(term->config, "event"))
return NULL;
@@ -1234,26 +1679,51 @@ static struct perf_pmu_alias *pmu_find_alias(struct perf_pmu *pmu,
return NULL;
}
- list_for_each_entry(alias, &pmu->aliases, list) {
- if (!strcasecmp(alias->name, name))
- return alias;
+ alias = perf_pmu__find_alias(pmu, name, /*load=*/ true);
+ if (alias || pmu->cpu_aliases_added)
+ 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) {
+ alias = perf_pmu__find_alias(pmu, name, /*load=*/ false);
}
- return NULL;
+ return alias;
}
-static int check_info_data(struct perf_pmu_alias *alias,
- struct perf_pmu_info *info)
+static int check_info_data(struct perf_pmu *pmu,
+ struct perf_pmu_alias *alias,
+ struct perf_pmu_info *info,
+ struct parse_events_error *err,
+ int column)
{
+ read_alias_info(pmu, alias);
/*
* Only one term in event definition can
* define unit, scale and snapshot, fail
* if there's more than one.
*/
- if ((info->unit && alias->unit[0]) ||
- (info->scale && alias->scale) ||
- (info->snapshot && alias->snapshot))
+ if (info->unit && alias->unit[0]) {
+ parse_events_error__handle(err, column,
+ strdup("Attempt to set event's unit twice"),
+ NULL);
+ return -EINVAL;
+ }
+ if (info->scale && alias->scale) {
+ parse_events_error__handle(err, column,
+ strdup("Attempt to set event's scale twice"),
+ NULL);
+ return -EINVAL;
+ }
+ if (info->snapshot && alias->snapshot) {
+ parse_events_error__handle(err, column,
+ strdup("Attempt to set event snapshot twice"),
+ NULL);
return -EINVAL;
+ }
if (alias->unit[0])
info->unit = alias->unit;
@@ -1271,13 +1741,15 @@ static int check_info_data(struct perf_pmu_alias *alias,
* 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 list_head *head_terms,
- struct perf_pmu_info *info)
+int perf_pmu__check_alias(struct perf_pmu *pmu, struct parse_events_terms *head_terms,
+ struct perf_pmu_info *info, bool *rewrote_terms,
+ u64 *alternate_hw_config, struct parse_events_error *err)
{
struct parse_events_term *term, *h;
struct perf_pmu_alias *alias;
int ret;
+ *rewrote_terms = false;
info->per_pkg = false;
/*
@@ -1287,32 +1759,52 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms,
info->unit = NULL;
info->scale = 0.0;
info->snapshot = false;
- info->metric_expr = NULL;
- info->metric_name = NULL;
+ 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;
+ }
- list_for_each_entry_safe(term, h, head_terms, list) {
+ /* 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->list);
- if (ret)
+ ret = pmu_alias_terms(alias, term->err_term, &term->list);
+ if (ret) {
+ parse_events_error__handle(err, term->err_term,
+ strdup("Failure to duplicate terms"),
+ NULL);
return ret;
+ }
- ret = check_info_data(alias, info);
+ *rewrote_terms = true;
+ ret = check_info_data(pmu, alias, info, err, term->err_term);
if (ret)
return ret;
if (alias->per_pkg)
info->per_pkg = true;
- info->metric_expr = alias->metric_expr;
- info->metric_name = alias->metric_name;
+
+ if (term->alternate_hw_config)
+ *alternate_hw_config = term->val.num;
+
+ 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 foundin aliases, then
+ * if no unit or scale found in aliases, then
* set defaults as for evsel
* unit cannot left to NULL
*/
@@ -1325,46 +1817,169 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms,
return 0;
}
-int perf_pmu__new_format(struct list_head *list, char *name,
- int config, unsigned long *bits)
-{
- struct perf_pmu_format *format;
+struct find_event_args {
+ const char *event;
+ void *state;
+ pmu_event_callback cb;
+};
- format = zalloc(sizeof(*format));
- if (!format)
- return -ENOMEM;
+static int find_event_callback(void *state, struct pmu_event_info *info)
+{
+ struct find_event_args *args = state;
- format->name = strdup(name);
- format->value = config;
- memcpy(format->bits, bits, sizeof(format->bits));
+ if (!strcmp(args->event, info->name))
+ return args->cb(args->state, info);
- list_add_tail(&format->list, list);
return 0;
}
-void perf_pmu__set_format(unsigned long *bits, long from, long to)
+int perf_pmu__find_event(struct perf_pmu *pmu, const char *event, void *state, pmu_event_callback cb)
{
- long b;
-
- if (!to)
- to = from;
+ struct find_event_args args = {
+ .event = event,
+ .state = state,
+ .cb = cb,
+ };
- memset(bits, 0, BITS_TO_BYTES(PERF_PMU_FORMAT_BITS));
- for (b = from; b <= to; b++)
- set_bit(b, bits);
+ /* Sub-optimal, but function is only used by tests. */
+ return perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/ false,
+ &args, find_event_callback);
}
-void perf_pmu__del_formats(struct list_head *formats)
+static void perf_pmu__del_formats(struct list_head *formats)
{
struct perf_pmu_format *fmt, *tmp;
list_for_each_entry_safe(fmt, tmp, formats, list) {
list_del(&fmt->list);
- free(fmt->name);
+ zfree(&fmt->name);
free(fmt);
}
}
+bool perf_pmu__has_format(const struct perf_pmu *pmu, const char *name)
+{
+ struct perf_pmu_format *format;
+
+ list_for_each_entry(format, &pmu->format, list) {
+ if (!strcmp(format->name, name))
+ return true;
+ }
+ return false;
+}
+
+int perf_pmu__for_each_format(struct perf_pmu *pmu, void *state, pmu_format_callback cb)
+{
+ static const char *const terms[] = {
+ "config=0..0xffffffffffffffff",
+ "config1=0..0xffffffffffffffff",
+ "config2=0..0xffffffffffffffff",
+ "config3=0..0xffffffffffffffff",
+ "name=string",
+ "period=number",
+ "freq=number",
+ "branch_type=(u|k|hv|any|...)",
+ "time",
+ "call-graph=(fp|dwarf|lbr)",
+ "stack-size=number",
+ "max-stack=number",
+ "nr=number",
+ "inherit",
+ "no-inherit",
+ "overwrite",
+ "no-overwrite",
+ "percore",
+ "aux-output",
+ "aux-action=(pause|resume|start-paused)",
+ "aux-sample-size=number",
+ "cpu=number",
+ };
+ 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.
+ */
+ _Static_assert(ARRAY_SIZE(terms) == __PARSE_EVENTS__TERM_TYPE_NR - 6,
+ "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);
+ ret = cb(state, format->name, (int)format->value, format->bits);
+ if (ret)
+ return ret;
+ }
+ if (!pmu->is_core)
+ return 0;
+
+ for (size_t i = 0; i < ARRAY_SIZE(terms); i++) {
+ int config = PERF_PMU_FORMAT_VALUE_CONFIG;
+
+ if (i < PERF_PMU_FORMAT_VALUE_CONFIG_END)
+ config = i;
+
+ ret = cb(state, terms[i], config, /*bits=*/NULL);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+bool is_pmu_core(const char *name)
+{
+ return !strcmp(name, "cpu") || !strcmp(name, "cpum_cf") || is_sysfs_pmu_core(name);
+}
+
+bool perf_pmu__supports_legacy_cache(const struct perf_pmu *pmu)
+{
+ return pmu->is_core;
+}
+
+bool perf_pmu__auto_merge_stats(const struct perf_pmu *pmu)
+{
+ return !pmu->is_core || perf_pmus__num_core_pmus() == 1;
+}
+
+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_hwmon(pmu))
+ return hwmon_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)
+ return false;
+ return pmu_events_table__find_event(pmu->events_table, pmu, name, NULL, NULL) == 0;
+}
+
+size_t perf_pmu__num_events(struct perf_pmu *pmu)
+{
+ size_t nr;
+
+ if (perf_pmu__is_hwmon(pmu))
+ return hwmon_pmu__num_events(pmu);
+
+ pmu_aliases_parse(pmu);
+ nr = pmu->sysfs_aliases + pmu->sys_json_aliases;
+
+ 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_common_json_aliases;
+ else
+ assert(pmu->cpu_json_aliases == 0 && pmu->cpu_common_json_aliases == 0);
+
+ if (perf_pmu__is_tool(pmu))
+ nr -= tool_pmu__num_skip_events();
+
+ return pmu->selectable ? nr + 1 : nr;
+}
+
static int sub_non_neg(int a, int b)
{
if (b > a)
@@ -1372,13 +1987,15 @@ static int sub_non_neg(int a, int b)
return a - b;
}
-static char *format_alias(char *buf, int len, struct perf_pmu *pmu,
- struct perf_pmu_alias *alias)
+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_term *term;
- int used = snprintf(buf, len, "%s/%s", pmu->name, alias->name);
+ size_t pmu_name_len = pmu_deduped_name_len(pmu, pmu->name,
+ skip_duplicate_pmus);
+ int used = snprintf(buf, len, "%.*s/%s", (int)pmu_name_len, pmu->name, alias->name);
- list_for_each_entry(term, &alias->terms, list) {
+ list_for_each_entry(term, &alias->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,
@@ -1398,228 +2015,217 @@ static char *format_alias(char *buf, int len, struct perf_pmu *pmu,
return buf;
}
-static char *format_alias_or(char *buf, int len, struct perf_pmu *pmu,
- struct perf_pmu_alias *alias)
+int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
+ void *state, pmu_event_callback cb)
{
- snprintf(buf, len, "%s OR %s/%s/", alias->name, pmu->name, alias->name);
- return buf;
-}
+ char buf[1024];
+ 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;
-struct sevent {
- char *name;
- char *desc;
- char *topic;
- char *str;
- char *pmu;
- char *metric_expr;
- char *metric_name;
- int is_cpu;
-};
+ if (perf_pmu__is_hwmon(pmu))
+ return hwmon_pmu__for_each_event(pmu, state, cb);
-static int cmp_sevent(const void *a, const void *b)
-{
- const struct sevent *as = a;
- const struct sevent *bs = b;
+ strbuf_init(&sb, /*hint=*/ 0);
+ pmu_aliases_parse(pmu);
+ pmu_add_cpu_aliases(pmu);
+ hashmap__for_each_entry(pmu->aliases, entry, bkt) {
+ struct perf_pmu_alias *event = entry->pvalue;
+ size_t buf_used, pmu_name_len;
- /* Put extra events last */
- if (!!as->desc != !!bs->desc)
- return !!as->desc - !!bs->desc;
- if (as->topic && bs->topic) {
- int n = strcmp(as->topic, bs->topic);
+ if (perf_pmu__is_tool(pmu) && tool_pmu__skip_event(event->name))
+ continue;
- if (n)
- return n;
+ info.pmu_name = event->pmu_name ?: 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;
+ buf_used = 0;
+ } else {
+ info.name = format_alias(buf, sizeof(buf), pmu, event,
+ skip_duplicate_pmus);
+ if (pmu->is_core) {
+ info.alias = info.name;
+ info.name = event->name;
+ }
+ buf_used = strlen(buf) + 1;
+ }
+ info.scale_unit = NULL;
+ if (strlen(event->unit) || event->scale != 1.0) {
+ info.scale_unit = buf + buf_used;
+ buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used,
+ "%G%s", event->scale, event->unit) + 1;
+ }
+ 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/", (int)pmu_name_len, info.pmu_name, sb.buf) + 1;
+ info.topic = event->topic;
+ info.str = sb.buf;
+ info.deprecated = event->deprecated;
+ ret = cb(state, &info);
+ if (ret)
+ goto out;
+ strbuf_setlen(&sb, /*len=*/ 0);
}
-
- /* Order CPU core events to be first */
- if (as->is_cpu != bs->is_cpu)
- return bs->is_cpu - as->is_cpu;
-
- return strcmp(as->name, bs->name);
+ if (pmu->selectable) {
+ info.name = buf;
+ snprintf(buf, sizeof(buf), "%s//", pmu->name);
+ info.alias = NULL;
+ info.scale_unit = NULL;
+ info.desc = NULL;
+ info.long_desc = NULL;
+ info.encoding_desc = NULL;
+ info.topic = NULL;
+ info.pmu_name = pmu->name;
+ info.deprecated = false;
+ ret = cb(state, &info);
+ }
+out:
+ strbuf_release(&sb);
+ return ret;
}
-static void wordwrap(char *s, int start, int max, int corr)
+static bool perf_pmu___name_match(const struct perf_pmu *pmu, const char *to_match, bool wildcard)
{
- int column = start;
- int n;
+ 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];
- while (*s) {
- int wlen = strcspn(s, " \t");
+ if (!name)
+ continue;
- if (column + wlen >= max && column > start) {
- printf("\n%*s", start, "");
- column = start + corr;
+ if (!strcmp(name, to_match)) {
+ /* Exact name match. */
+ return true;
+ }
}
- n = printf("%s%.*s", column > start ? " " : "", wlen, s);
- if (n <= 0)
- break;
- s += wlen;
- column += n;
- s = skip_spaces(s);
+ 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;
}
-}
-
-bool is_pmu_core(const char *name)
-{
- return !strcmp(name, "cpu") || is_arm_pmu_core(name);
-}
-
-void print_pmu_events(const char *event_glob, bool name_only, bool quiet_flag,
- bool long_desc, bool details_flag, bool deprecated)
-{
- struct perf_pmu *pmu;
- struct perf_pmu_alias *alias;
- char buf[1024];
- int printed = 0;
- int len, j;
- struct sevent *aliases;
- int numdesc = 0;
- int columns = pager_get_columns();
- char *topic = NULL;
-
- pmu = NULL;
- len = 0;
- while ((pmu = perf_pmu__scan(pmu)) != NULL) {
- list_for_each_entry(alias, &pmu->aliases, list)
- len++;
- if (pmu->selectable)
- len++;
- }
- aliases = zalloc(sizeof(struct sevent) * len);
- if (!aliases)
- goto out_enomem;
- pmu = NULL;
- j = 0;
- while ((pmu = perf_pmu__scan(pmu)) != NULL) {
- list_for_each_entry(alias, &pmu->aliases, list) {
- char *name = alias->desc ? alias->name :
- format_alias(buf, sizeof(buf), pmu, alias);
- bool is_cpu = is_pmu_core(pmu->name);
-
- if (alias->deprecated && !deprecated)
- continue;
+ if (!pmu->is_uncore) {
+ /*
+ * PMU isn't core or uncore, some kind of broken CPU mask
+ * situation. Only match exact name.
+ */
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
+ const char *name = names[i];
- if (event_glob != NULL &&
- !(strglobmatch_nocase(name, event_glob) ||
- (!is_cpu && strglobmatch_nocase(alias->name,
- event_glob)) ||
- (alias->topic &&
- strglobmatch_nocase(alias->topic, event_glob))))
+ if (!name)
continue;
- if (is_cpu && !name_only && !alias->desc)
- name = format_alias_or(buf, sizeof(buf), pmu, alias);
-
- aliases[j].name = name;
- if (is_cpu && !name_only && !alias->desc)
- aliases[j].name = format_alias_or(buf,
- sizeof(buf),
- pmu, alias);
- aliases[j].name = strdup(aliases[j].name);
- if (!aliases[j].name)
- goto out_enomem;
-
- aliases[j].desc = long_desc ? alias->long_desc :
- alias->desc;
- aliases[j].topic = alias->topic;
- aliases[j].str = alias->str;
- aliases[j].pmu = pmu->name;
- aliases[j].metric_expr = alias->metric_expr;
- aliases[j].metric_name = alias->metric_name;
- aliases[j].is_cpu = is_cpu;
- j++;
- }
- if (pmu->selectable &&
- (event_glob == NULL || strglobmatch(pmu->name, event_glob))) {
- char *s;
- if (asprintf(&s, "%s//", pmu->name) < 0)
- goto out_enomem;
- aliases[j].name = s;
- j++;
+ if (!strcmp(name, to_match)) {
+ /* Exact name match. */
+ return true;
+ }
}
+ return false;
}
- len = j;
- qsort(aliases, len, sizeof(struct sevent), cmp_sevent);
- for (j = 0; j < len; j++) {
- /* Skip duplicates */
- if (j > 0 && !strcmp(aliases[j].name, aliases[j - 1].name))
- continue;
- if (name_only) {
- printf("%s ", aliases[j].name);
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
+ const char *name = names[i];
+
+ if (!name)
continue;
- }
- if (aliases[j].desc && !quiet_flag) {
- if (numdesc++ == 0)
- printf("\n");
- if (aliases[j].topic && (!topic ||
- strcmp(topic, aliases[j].topic))) {
- printf("%s%s:\n", topic ? "\n" : "",
- aliases[j].topic);
- topic = aliases[j].topic;
- }
- printf(" %-50s\n", aliases[j].name);
- printf("%*s", 8, "[");
- wordwrap(aliases[j].desc, 8, columns, 0);
- printf("]\n");
- if (details_flag) {
- printf("%*s%s/%s/ ", 8, "", aliases[j].pmu, aliases[j].str);
- if (aliases[j].metric_name)
- printf(" MetricName: %s", aliases[j].metric_name);
- if (aliases[j].metric_expr)
- printf(" MetricExpr: %s", aliases[j].metric_expr);
- putchar('\n');
- }
- } else
- printf(" %-50s [Kernel PMU event]\n", aliases[j].name);
- printed++;
+
+ 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;
}
- if (printed && pager_in_use())
- printf("\n");
-out_free:
- for (j = 0; j < len; j++)
- zfree(&aliases[j].name);
- zfree(&aliases);
- return;
+ return false;
+}
-out_enomem:
- printf("FATAL: not enough memory to print PMU events\n");
- if (aliases)
- goto out_free;
+/**
+ * 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);
}
-bool pmu_have_event(const char *pname, const char *name)
+/**
+ * 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)
{
- struct perf_pmu *pmu;
- struct perf_pmu_alias *alias;
+ return perf_pmu___name_match(pmu, to_match, /*wildcard=*/false);
+}
- pmu = NULL;
- while ((pmu = perf_pmu__scan(pmu)) != NULL) {
- if (strcmp(pname, pmu->name))
- continue;
- list_for_each_entry(alias, &pmu->aliases, list)
- if (!strcmp(alias->name, name))
- return true;
+bool perf_pmu__is_software(const struct perf_pmu *pmu)
+{
+ const char *known_sw_pmus[] = {
+ "kprobe",
+ "msr",
+ "uprobe",
+ };
+
+ if (pmu->is_core || pmu->is_uncore || pmu->auxtrace)
+ return false;
+ switch (pmu->type) {
+ case PERF_TYPE_HARDWARE: return false;
+ case PERF_TYPE_SOFTWARE: return true;
+ case PERF_TYPE_TRACEPOINT: return true;
+ 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++) {
+ if (!strcmp(pmu->name, known_sw_pmus[i]))
+ return true;
}
return false;
}
-static FILE *perf_pmu__open_file(struct perf_pmu *pmu, const char *name)
+FILE *perf_pmu__open_file(const struct perf_pmu *pmu, const char *name)
{
char path[PATH_MAX];
- const char *sysfs;
- sysfs = sysfs__mountpoint();
- if (!sysfs)
+ if (!perf_pmu__pathname_scnprintf(path, sizeof(path), pmu->name, name) ||
+ !file_available(path))
return NULL;
- snprintf(path, PATH_MAX,
- "%s" EVENT_SOURCE_DEVICE_PATH "%s/%s", sysfs, pmu->name, name);
- if (!file_available(path))
- return NULL;
return fopen(path, "r");
}
-int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt,
+FILE *perf_pmu__open_file_at(const struct perf_pmu *pmu, int dirfd, const char *name)
+{
+ int fd;
+
+ fd = perf_pmu__pathname_fd(dirfd, pmu->name, name, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ return fdopen(fd, "r");
+}
+
+int perf_pmu__scan_file(const struct perf_pmu *pmu, const char *name, const char *fmt,
...)
{
va_list args;
@@ -1636,6 +2242,33 @@ int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt,
return ret;
}
+int perf_pmu__scan_file_at(const struct perf_pmu *pmu, int dirfd, const char *name,
+ const char *fmt, ...)
+{
+ va_list args;
+ FILE *file;
+ int ret = EOF;
+
+ va_start(args, fmt);
+ file = perf_pmu__open_file_at(pmu, dirfd, name);
+ if (file) {
+ ret = vfscanf(file, fmt, args);
+ fclose(file);
+ }
+ va_end(args);
+ return ret;
+}
+
+bool perf_pmu__file_exists(const struct perf_pmu *pmu, const char *name)
+{
+ char path[PATH_MAX];
+
+ if (!perf_pmu__pathname_scnprintf(path, sizeof(path), pmu->name, name))
+ return false;
+
+ return file_available(path);
+}
+
static int perf_pmu__new_caps(struct list_head *list, char *name, char *value)
{
struct perf_pmu_caps *caps = zalloc(sizeof(*caps));
@@ -1653,13 +2286,36 @@ static int perf_pmu__new_caps(struct list_head *list, char *name, char *value)
return 0;
free_name:
- zfree(caps->name);
+ zfree(&caps->name);
free_caps:
free(caps);
return -ENOMEM;
}
+static void perf_pmu__del_caps(struct perf_pmu *pmu)
+{
+ struct perf_pmu_caps *caps, *tmp;
+
+ list_for_each_entry_safe(caps, tmp, &pmu->caps, list) {
+ list_del(&caps->list);
+ zfree(&caps->name);
+ zfree(&caps->value);
+ free(caps);
+ }
+}
+
+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.
@@ -1667,40 +2323,44 @@ free_caps:
*/
int perf_pmu__caps_parse(struct perf_pmu *pmu)
{
- struct stat st;
char caps_path[PATH_MAX];
- const char *sysfs = sysfs__mountpoint();
- DIR *caps_dir;
- struct dirent *evt_ent;
- int nr_caps = 0;
+ struct io_dir caps_dir;
+ struct io_dirent64 *evt_ent;
+ int caps_fd;
- if (!sysfs)
- return -1;
+ if (pmu->caps_initialized)
+ return pmu->nr_caps;
- snprintf(caps_path, PATH_MAX,
- "%s" EVENT_SOURCE_DEVICE_PATH "%s/caps", sysfs, pmu->name);
+ pmu->nr_caps = 0;
+
+ 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;
+ io_dir__init(&caps_dir, caps_fd);
- while ((evt_ent = readdir(caps_dir)) != NULL) {
- char path[PATH_MAX + NAME_MAX + 1];
+ 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;
- snprintf(path, sizeof(path), "%s/%s", caps_path, name);
-
- file = fopen(path, "r");
- if (!file)
+ fd = openat(caps_fd, name, O_RDONLY);
+ if (fd == -1)
+ continue;
+ file = fdopen(fd, "r");
+ if (!file) {
+ close(fd);
continue;
+ }
if (!fgets(value, sizeof(value), file) ||
(perf_pmu__new_caps(&pmu->caps, name, value) < 0)) {
@@ -1708,11 +2368,181 @@ int perf_pmu__caps_parse(struct perf_pmu *pmu)
continue;
}
- nr_caps++;
+ pmu->nr_caps++;
fclose(file);
}
- closedir(caps_dir);
+ close(caps_fd);
+
+ pmu->caps_initialized = true;
+ return pmu->nr_caps;
+}
+
+static void perf_pmu__compute_config_masks(struct perf_pmu *pmu)
+{
+ struct perf_pmu_format *format;
+
+ if (pmu->config_masks_computed)
+ return;
+
+ list_for_each_entry(format, &pmu->format, list) {
+ unsigned int i;
+ __u64 *mask;
+
+ if (format->value >= PERF_PMU_FORMAT_VALUE_CONFIG_END)
+ continue;
+
+ pmu->config_masks_present = true;
+ mask = &pmu->config_masks[format->value];
+
+ for_each_set_bit(i, format->bits, PERF_PMU_FORMAT_BITS)
+ *mask |= 1ULL << i;
+ }
+ pmu->config_masks_computed = true;
+}
+
+void perf_pmu__warn_invalid_config(struct perf_pmu *pmu, __u64 config,
+ const char *name, int config_num,
+ const char *config_name)
+{
+ __u64 bits;
+ char buf[100];
+
+ perf_pmu__compute_config_masks(pmu);
+
+ /*
+ * Kernel doesn't export any valid format bits.
+ */
+ if (!pmu->config_masks_present)
+ return;
+
+ bits = config & ~pmu->config_masks[config_num];
+ if (bits == 0)
+ return;
+
+ bitmap_scnprintf((unsigned long *)&bits, sizeof(bits) * 8, buf, sizeof(buf));
+
+ pr_warning("WARNING: event '%s' not valid (bits %s of %s "
+ "'%llx' not supported by kernel)!\n",
+ name ?: "N/A", buf, config_name, config);
+}
+
+bool perf_pmu__wildcard_match(const struct perf_pmu *pmu, const char *wildcard_to_match)
+{
+ const char *names[2] = {
+ pmu->name,
+ pmu->alias_name,
+ };
+ bool need_fnmatch = strisglob(wildcard_to_match);
+
+ if (!strncmp(wildcard_to_match, "uncore_", 7))
+ wildcard_to_match += 7;
+
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
+ const char *pmu_name = names[i];
+
+ if (!pmu_name)
+ continue;
+
+ if (!strncmp(pmu_name, "uncore_", 7))
+ pmu_name += 7;
+
+ 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)
+{
+ const char *sysfs = sysfs__mountpoint();
- return nr_caps;
+ if (!sysfs)
+ return 0;
+ return scnprintf(pathname, size, "%s/bus/event_source/devices/", sysfs);
+}
+
+int perf_pmu__event_source_devices_fd(void)
+{
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ scnprintf(path, sizeof(path), "%s/bus/event_source/devices/", sysfs);
+ return open(path, O_DIRECTORY);
+}
+
+/*
+ * Fill 'buf' with the path to a file or folder in 'pmu_name' in
+ * sysfs. For example if pmu_name = "cs_etm" and 'filename' = "format"
+ * then pathname will be filled with
+ * "/sys/bus/event_source/devices/cs_etm/format"
+ *
+ * Return 0 if the sysfs mountpoint couldn't be found, if no characters were
+ * written or if the buffer size is exceeded.
+ */
+int perf_pmu__pathname_scnprintf(char *buf, size_t size,
+ const char *pmu_name, const char *filename)
+{
+ size_t len;
+
+ len = perf_pmu__event_source_devices_scnprintf(buf, size);
+ if (!len || (len + strlen(pmu_name) + strlen(filename) + 1) >= size)
+ return 0;
+
+ return scnprintf(buf + len, size - len, "%s/%s", pmu_name, filename);
+}
+
+int perf_pmu__pathname_fd(int dirfd, const char *pmu_name, const char *filename, int flags)
+{
+ char path[PATH_MAX];
+
+ scnprintf(path, sizeof(path), "%s/%s", pmu_name, filename);
+ return openat(dirfd, path, flags);
+}
+
+void perf_pmu__delete(struct perf_pmu *pmu)
+{
+ if (!pmu)
+ return;
+
+ if (perf_pmu__is_hwmon(pmu))
+ hwmon_pmu__exit(pmu);
+
+ perf_pmu__del_formats(&pmu->format);
+ perf_pmu__del_aliases(pmu);
+ perf_pmu__del_caps(pmu);
+
+ perf_cpu_map__put(pmu->cpus);
+
+ zfree(&pmu->name);
+ zfree(&pmu->alias_name);
+ zfree(&pmu->id);
+ free(pmu);
+}
+
+const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config)
+{
+ struct hashmap_entry *entry;
+ size_t bkt;
+
+ if (!pmu)
+ return NULL;
+
+ pmu_aliases_parse(pmu);
+ pmu_add_cpu_aliases(pmu);
+ 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, /*apply_hardcoded=*/true,
+ /*err=*/NULL);
+
+ if (ret == 0 && config == attr.config)
+ return event->name;
+ }
+ return NULL;
}