From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org,
Ian Rogers <irogers@google.com>
Subject: [PATCH 04/11] perf pmu-layer: Add openat-based directory layer with literal CPUID/symlink lookup
Date: Fri, 13 Nov 2026 13:10:55 -0800 [thread overview]
Message-ID: <20261113211105.3118281-5-irogers@google.com> (raw)
In-Reply-To: <20261113211105.3118281-1-irogers@google.com>
Add the filesystem-backed PMU layer backends (user_dir_layer_ops and
sysfs_layer_ops) in util/pmu-layer.c using openat() and io_dir.
A user or system overlay directory <root> supports both portable
multi-CPU trees (keyed by CPUID or uncore identifier) and host-direct
PMU overrides without requiring a runtime mapfile.csv or regex engine:
<root>/
|-- cpus/
| |-- GenuineIntel-6-4E -> ../models/x86/skylake
| |-- GenuineIntel-6-5E -> ../models/x86/skylake
| |-- GenuineIntel-6-8F -> ../models/x86/sapphirerapids
| |-- GenuineIntel-6-8F-8/ # stepping-specific override
| | `-- cpu/events/inst_retired.any.desc
| `-- 0x00000000410fd490 -> ../models/arm64/arm/neoverse-n2
|-- sys/
| `-- 43401 -> ../models/arm64/arm/cmn
|-- models/
| |-- x86/skylake/
| | |-- cpu/{events,metrics}/...
| | `-- uncore_imc/{events,metrics}/...
| `-- arm64/arm/neoverse-n2/
| `-- armv8_pmuv3/{events,metrics}/...
|-- default_core/ # cross-CPU or host-local core
| `-- metrics/my_ipc{,.desc}
|-- <pmu>/{events,metrics}/... # host-direct PMU overrides
`-- metricgroups/<group>.desc
Within each <root>, dir_for_each_scope() probes three scopes in order:
1. CPUID scope (<root>/cpus/<cpuid>/):
Queries the PMU's CPUID via get_cpuid_allow_env_override() (using
perf_cpu_map__min(pmu->cpus) so heterogeneous core PMUs such as
cpu_core and cpu_atom resolve against their own CPU's CPUID, and
honoring PERF_CPUID). build_cpuid_candidates() probes the exact
CPUID first, followed by deterministic base CPUIDs stripped of
stepping or revision fields:
- '-' or ',' delimited CPUIDs (x86, s390, RISC-V): strips trailing
fields down to two components, e.g.:
x86: GenuineIntel-6-8F-8 -> GenuineIntel-6-8F
s390: IBM,8561,703 -> IBM,8561
RISC-V: 0x489-0x8000000000000007-0x1 -> 0x489-0x8000000000000007
- ARM64 64-bit hex MIDR (0x + 16 hex digits): masks Variant[23:20]
and Revision[3:0] to zero, e.g.:
0x00000000411fd493 -> 0x00000000410fd490
- PowerPC 32-bit hex PVR (0x + 8 hex digits): masks Revision[15:0]
to zero, e.g.:
0x00800200 -> 0x00800000
Each <root> stops at the first matching cpus/<candidate> directory
so a stepping directory (such as cpus/GenuineIntel-6-8F-8/) does
not merge coarser model events from cpus/GenuineIntel-6-8F/ in the
same <root>, while unset sidecars and unoverridden events still fall
through to subsequent directories in pmu-events.dir, inbuilt JSON,
and sysfs. Because openat() traverses POSIX symlinks in the kernel
VFS, multiple CPUIDs sharing a microarchitecture can symlink to a
single canonical models/<arch>/<model>/ directory.
2. System/uncore identifier scope (<root>/sys/<id>/):
Probes pmu->id read from /sys/bus/event_source/devices/<pmu>/identifier
(supporting literal directories or POSIX symlinks).
3. Host-direct scope (<root>/):
Probes PMU subdirectories directly under <root>/ plus
<root>/metricgroups/<group>.desc.
Within each matched scope, dir_for_each_pmu_subdir() checks the exact
PMU name (e.g. cpu, cpu_core, uncore_imc_0), pmu->alias_name,
default_core for core PMUs, and suffix-stripped uncore prefixes (e.g.
uncore_imc matching uncore_imc_0).
Also parse extended event sidecars (.unit, .scale, .scale-unit,
.per-pkg, .snapshot, .desc, .long-desc, .topic, .compat, .deprecated,
.legacy-terms, and .retire-lat-{mean,min,max}), metric sidecars
(<pmu>/metrics/*), and metricgroup descriptions with %2F escaping for
'/' in names, and register PMU_LAYER_SYSFS between
PMU_LAYER_BUILTIN_CPU and PMU_LAYER_BUILTIN_COMMON so single-event
sysfs lookups resolve lazily via openat() without readdir().
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu-layer.c | 1351 ++++++++++++++++++++++++++++++++++-
tools/perf/util/pmu-layer.h | 6 +
2 files changed, 1350 insertions(+), 7 deletions(-)
diff --git a/tools/perf/util/pmu-layer.c b/tools/perf/util/pmu-layer.c
index e4c50cc6d1ea..e54d7e8f487f 100644
--- a/tools/perf/util/pmu-layer.c
+++ b/tools/perf/util/pmu-layer.c
@@ -1,23 +1,39 @@
// SPDX-License-Identifier: GPL-2.0
+#include <ctype.h>
+#include <dirent.h>
#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <api/fs/fs.h>
+#include <api/io.h>
+#include <api/io_dir.h>
#include <linux/compiler.h>
#include <linux/err.h>
+#include <linux/string.h>
#include <linux/zalloc.h>
+#include "cpumap.h"
#include "debug.h"
#include "hashmap.h"
+#include "header.h"
#include "pmu.h"
+#include "pmus.h"
#include "pmu-layer.h"
#include "pmu-events/pmu-events.h"
static struct pmu_layer *pmu_layers;
static size_t nr_pmu_layers;
static bool pmu_layers_initialized;
+static int active_sysfs_override_fd = -1;
static struct perf_pmu_alias *pmu_layer_alloc_alias(const char *name)
{
@@ -265,12 +281,40 @@ struct builtin_find_event_ctx {
bool is_common;
};
+static bool pmu_layer_alias_is_empty(const struct perf_pmu_alias *alias)
+{
+ return !alias->terms && !alias->legacy_terms &&
+ !alias->desc && !alias->long_desc && !alias->topic &&
+ !alias->pmu_name && !alias->compat && !alias->has_scale &&
+ !alias->has_unit && !alias->has_per_pkg &&
+ !alias->has_snapshot && !alias->has_deprecated &&
+ !alias->has_retire_lat_mean && !alias->has_retire_lat_min &&
+ !alias->has_retire_lat_max;
+}
+
+static void pmu_layer_canon_alias_name(struct perf_pmu_alias *alias,
+ const char *canon)
+{
+ char *dup_name;
+
+ if (!canon || !canon[0] || !pmu_layer_alias_is_empty(alias) ||
+ !strcmp(alias->name, canon))
+ return;
+
+ dup_name = strdup(canon);
+ if (dup_name) {
+ free(alias->name);
+ alias->name = dup_name;
+ }
+}
+
static int builtin_find_event_cb(const struct pmu_event *pe,
const struct pmu_events_table *table __maybe_unused,
void *vdata)
{
struct builtin_find_event_ctx *ctx = vdata;
+ pmu_layer_canon_alias_name(ctx->alias, pe->name);
return pmu_layer_merge_pe(ctx->alias, pe, ctx->is_common);
}
@@ -288,6 +332,7 @@ static int builtin_find_sys_event_cb(const struct pmu_event *pe,
return 0;
if (!pmu_uncore_identifier_match(pe->compat, ctx->pmu->id))
return 0;
+ pmu_layer_canon_alias_name(ctx->alias, pe->name);
return pmu_layer_merge_pe(ctx->alias, pe, /*is_common=*/false);
}
@@ -678,6 +723,1264 @@ static const struct pmu_layer_ops builtin_common_layer_ops = {
.load_metricgroup = builtin_common_load_metricgroup,
};
+int pmu_layer__encode_filename(const char *name, char *buf, size_t size)
+{
+ size_t out = 0;
+
+ if (!name || size == 0)
+ return -EINVAL;
+
+ for (size_t i = 0; name[i] != '\0'; i++) {
+ if (name[i] == '/') {
+ if (out + 3 >= size)
+ return -ENAMETOOLONG;
+ buf[out++] = '%';
+ buf[out++] = '2';
+ buf[out++] = 'F';
+ } else {
+ if (out + 1 >= size)
+ return -ENAMETOOLONG;
+ buf[out++] = name[i];
+ }
+ }
+ buf[out] = '\0';
+ return 0;
+}
+
+int pmu_layer__decode_filename(const char *encoded, size_t len,
+ char *buf, size_t size)
+{
+ size_t out = 0;
+
+ if (!encoded || size == 0)
+ return -EINVAL;
+
+ for (size_t i = 0; i < len; i++) {
+ if (i + 2 < len && encoded[i] == '%' && encoded[i + 1] == '2' &&
+ (encoded[i + 2] == 'F' || encoded[i + 2] == 'f')) {
+ if (out + 1 >= size)
+ return -ENAMETOOLONG;
+ buf[out++] = '/';
+ i += 2;
+ } else {
+ if (out + 1 >= size)
+ return -ENAMETOOLONG;
+ buf[out++] = encoded[i];
+ }
+ }
+ buf[out] = '\0';
+ return 0;
+}
+
+static int dir_read_file_at(int dirfd, const char *filename, char **out_str)
+{
+ struct stat st;
+ char *buf = NULL;
+ size_t cap = 256, len = 0;
+ int fd, ret = 0;
+
+ fd = openat(dirfd, filename, O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ if (fstat(fd, &st) < 0) {
+ ret = -errno;
+ close(fd);
+ return ret;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ close(fd);
+ return -EISDIR;
+ }
+
+ buf = malloc(cap);
+ if (!buf) {
+ close(fd);
+ return -ENOMEM;
+ }
+
+ for (;;) {
+ ssize_t n;
+
+ if (len + 1 >= cap) {
+ char *next;
+
+ cap *= 2;
+ next = realloc(buf, cap);
+ if (!next) {
+ free(buf);
+ close(fd);
+ return -ENOMEM;
+ }
+ buf = next;
+ }
+ n = read(fd, buf + len, cap - len - 1);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ ret = -errno;
+ free(buf);
+ close(fd);
+ return ret;
+ }
+ if (n == 0)
+ break;
+ len += (size_t)n;
+ }
+ close(fd);
+
+ if (len > 0 && buf[len - 1] == '\n') {
+ len--;
+ if (len > 0 && buf[len - 1] == '\r')
+ len--;
+ }
+ buf[len] = '\0';
+ *out_str = buf;
+ return 0;
+}
+
+static int parse_bool_str(char *str, bool *out_val)
+{
+ size_t len = strlen(str);
+
+ while (len > 0 && isspace((unsigned char)str[len - 1]))
+ str[--len] = '\0';
+
+ if (!strcmp(str, "1") || !strcasecmp(str, "true") || !strcasecmp(str, "yes")) {
+ *out_val = true;
+ return 0;
+ }
+ if (!strcmp(str, "0") || !strcasecmp(str, "false") || !strcasecmp(str, "no")) {
+ *out_val = false;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int dir_apply_event_file(int events_fd, const char *filename,
+ enum pmu_event_file_kind kind,
+ const char *pmu_dir_name,
+ bool is_sysfs,
+ struct perf_pmu_alias *alias,
+ bool *matched)
+{
+ char *val = NULL;
+ bool bval;
+ int r = -ENOENT;
+
+ switch (kind) {
+ case PMU_EVENT_FILE_BASE:
+ if (!alias->terms) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ alias->terms = val;
+ if (is_sysfs) {
+ alias->from_sysfs = true;
+ alias->info_loaded = true;
+ }
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_LEGACY_TERMS:
+ if (!alias->legacy_terms) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0)
+ alias->legacy_terms = val;
+ }
+ break;
+ case PMU_EVENT_FILE_UNIT:
+ if (!alias->has_unit) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ snprintf(alias->unit, sizeof(alias->unit), "%s", val);
+ alias->has_unit = true;
+ free(val);
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_SCALE:
+ if (!alias->has_scale) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ if (perf_pmu__convert_scale(val, NULL, &alias->scale) == 0)
+ alias->has_scale = true;
+ free(val);
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_SCALE_UNIT:
+ if (!alias->has_scale || !alias->has_unit) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ char *unit_str = NULL;
+ double sval = 1.0;
+
+ if (perf_pmu__convert_scale(val, &unit_str, &sval) == 0) {
+ if (!alias->has_scale) {
+ alias->scale = sval;
+ alias->has_scale = true;
+ }
+ if (!alias->has_unit && unit_str) {
+ snprintf(alias->unit, sizeof(alias->unit),
+ "%s", unit_str);
+ alias->has_unit = true;
+ }
+ }
+ free(val);
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_PER_PKG:
+ if (!alias->has_per_pkg) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ if (parse_bool_str(val, &bval) == 0) {
+ alias->per_pkg = bval;
+ alias->has_per_pkg = true;
+ }
+ free(val);
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_SNAPSHOT:
+ if (!alias->has_snapshot) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ if (parse_bool_str(val, &bval) == 0) {
+ alias->snapshot = bval;
+ alias->has_snapshot = true;
+ }
+ free(val);
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_DEPRECATED:
+ if (!alias->has_deprecated) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ if (parse_bool_str(val, &bval) == 0) {
+ alias->deprecated = bval;
+ alias->has_deprecated = true;
+ }
+ free(val);
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_DESC:
+ if (!alias->desc) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0)
+ alias->desc = val;
+ }
+ break;
+ case PMU_EVENT_FILE_LONG_DESC:
+ if (!alias->long_desc) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0)
+ alias->long_desc = val;
+ }
+ break;
+ case PMU_EVENT_FILE_TOPIC:
+ if (!alias->topic) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0)
+ alias->topic = val;
+ }
+ break;
+ case PMU_EVENT_FILE_COMPAT:
+ if (!alias->compat) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0)
+ alias->compat = val;
+ }
+ break;
+ case PMU_EVENT_FILE_RETIRE_LAT_MEAN:
+ if (!alias->has_retire_lat_mean) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ if (perf_pmu__convert_scale(val, NULL,
+ &alias->retirement_latency_mean) == 0)
+ alias->has_retire_lat_mean = true;
+ free(val);
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_RETIRE_LAT_MIN:
+ if (!alias->has_retire_lat_min) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ if (perf_pmu__convert_scale(val, NULL,
+ &alias->retirement_latency_min) == 0)
+ alias->has_retire_lat_min = true;
+ free(val);
+ }
+ }
+ break;
+ case PMU_EVENT_FILE_RETIRE_LAT_MAX:
+ if (!alias->has_retire_lat_max) {
+ r = dir_read_file_at(events_fd, filename, &val);
+ if (r == 0) {
+ if (perf_pmu__convert_scale(val, NULL,
+ &alias->retirement_latency_max) == 0)
+ alias->has_retire_lat_max = true;
+ free(val);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (r == 0) {
+ if (matched)
+ *matched = true;
+ if (!is_sysfs && !alias->pmu_name && pmu_dir_name &&
+ strcmp(pmu_dir_name, "default_core") != 0) {
+ alias->pmu_name = strdup(pmu_dir_name);
+ if (!alias->pmu_name)
+ return -ENOMEM;
+ }
+ }
+ return r == -ENOMEM ? -ENOMEM : 0;
+}
+
+static int parse_aggr_mode_str(const char *str, enum aggr_mode_class *out)
+{
+ if (!strcasecmp(str, "PerChip") || !strcmp(str, "1")) {
+ *out = PerChip;
+ return 0;
+ }
+ if (!strcasecmp(str, "PerCore") || !strcmp(str, "2")) {
+ *out = PerCore;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int parse_constraint_str(const char *str, enum metric_event_groups *out)
+{
+ if (!strcasecmp(str, "GROUP_EVENTS") || !strcmp(str, "0")) {
+ *out = MetricGroupEvents;
+ return 0;
+ }
+ if (!strcasecmp(str, "NO_GROUP_EVENTS") || !strcmp(str, "1")) {
+ *out = MetricNoGroupEvents;
+ return 0;
+ }
+ if (!strcasecmp(str, "NO_GROUP_EVENTS_NMI") ||
+ !strcasecmp(str, "NO_NMI_WATCHDOG") || !strcmp(str, "2")) {
+ *out = MetricNoGroupEventsNmi;
+ return 0;
+ }
+ if (!strcasecmp(str, "NO_GROUP_EVENTS_SMT") || !strcmp(str, "3")) {
+ *out = MetricNoGroupEventsSmt;
+ return 0;
+ }
+ if (!strcasecmp(str, "NO_THRESHOLD_AND_NMI") || !strcmp(str, "4")) {
+ *out = MetricNoGroupEventsThresholdAndNmi;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int dir_apply_metric_file(int metrics_fd, const char *filename,
+ enum pmu_metric_file_kind kind,
+ const char *pmu_dir_name,
+ struct perf_pmu_metric *metric,
+ bool *matched)
+{
+ char *val = NULL;
+ bool bval;
+ int r = -ENOENT;
+
+ switch (kind) {
+ case PMU_METRIC_FILE_BASE:
+ if (!metric->metric_expr) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->metric_expr = val;
+ } else if (faccessat(metrics_fd, filename, F_OK, 0) == 0) {
+ r = 0;
+ }
+ break;
+ case PMU_METRIC_FILE_SCALE_UNIT:
+ if (!metric->unit) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->unit = val;
+ }
+ break;
+ case PMU_METRIC_FILE_DESC:
+ if (!metric->desc) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->desc = val;
+ }
+ break;
+ case PMU_METRIC_FILE_LONG_DESC:
+ if (!metric->long_desc) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->long_desc = val;
+ }
+ break;
+ case PMU_METRIC_FILE_GROUP:
+ if (!metric->metric_group) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->metric_group = val;
+ }
+ break;
+ case PMU_METRIC_FILE_NO_GROUP:
+ if (!metric->metricgroup_no_group) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->metricgroup_no_group = val;
+ }
+ break;
+ case PMU_METRIC_FILE_DEFAULT_GROUP:
+ if (!metric->default_metricgroup_name) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->default_metricgroup_name = val;
+ }
+ break;
+ case PMU_METRIC_FILE_THRESHOLD:
+ if (!metric->metric_threshold) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->metric_threshold = val;
+ }
+ break;
+ case PMU_METRIC_FILE_COMPAT:
+ if (!metric->compat) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0)
+ metric->compat = val;
+ }
+ break;
+ case PMU_METRIC_FILE_AGGR_MODE:
+ if (!metric->has_aggr_mode) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0) {
+ if (parse_aggr_mode_str(val, &metric->aggr_mode) == 0)
+ metric->has_aggr_mode = true;
+ free(val);
+ }
+ }
+ break;
+ case PMU_METRIC_FILE_CONSTRAINT:
+ if (!metric->has_event_grouping) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0) {
+ if (parse_constraint_str(val, &metric->event_grouping) == 0)
+ metric->has_event_grouping = true;
+ free(val);
+ }
+ }
+ break;
+ case PMU_METRIC_FILE_SHOW_EVENTS:
+ if (!metric->has_default_show_events) {
+ r = dir_read_file_at(metrics_fd, filename, &val);
+ if (r == 0) {
+ if (parse_bool_str(val, &bval) == 0) {
+ metric->default_show_events = bval;
+ metric->has_default_show_events = true;
+ }
+ free(val);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (r == 0) {
+ if (matched)
+ *matched = true;
+ if (!metric->pmu && pmu_dir_name) {
+ metric->pmu = strdup(pmu_dir_name);
+ if (!metric->pmu)
+ return -ENOMEM;
+ }
+ }
+ return r == -ENOMEM ? -ENOMEM : 0;
+}
+
+static size_t build_cpuid_candidates(const char *cpuid,
+ char candidates[][128],
+ size_t max_candidates)
+{
+ size_t n = 0, len;
+
+ if (!cpuid || !cpuid[0] || max_candidates == 0)
+ return 0;
+
+ len = strlen(cpuid);
+ if (len >= sizeof(candidates[0]))
+ return 0;
+
+ snprintf(candidates[n++], sizeof(candidates[0]), "%s", cpuid);
+
+ if (strchr(cpuid, '-') || strchr(cpuid, ',')) {
+ char sep = strchr(cpuid, '-') ? '-' : ',';
+ char work[128];
+
+ snprintf(work, sizeof(work), "%s", cpuid);
+ while (n < max_candidates) {
+ char *last = strrchr(work, sep);
+
+ if (!last || last == work || !strchr(work, sep) ||
+ strchr(work, sep) == last)
+ break;
+ *last = '\0';
+ snprintf(candidates[n++], sizeof(candidates[0]), "%s", work);
+ }
+ } else if (!strncmp(cpuid, "0x", 2) || !strncmp(cpuid, "0X", 2)) {
+ char *end = NULL;
+ unsigned long long val = strtoull(cpuid, &end, 16);
+
+ if (end && *end == '\0' && n < max_candidates) {
+ if (len == 18) {
+ /* ARM64 MIDR: zero Variant[23:20] and Revision[3:0]. */
+ unsigned long long masked = val & ~0x00f0000fULL;
+
+ if (masked != val)
+ snprintf(candidates[n++], sizeof(candidates[0]),
+ "0x%016llx", masked);
+ } else if (len == 10) {
+ /* PowerPC PVR: zero Revision[15:0]. */
+ unsigned long long masked = val & ~0xffffULL;
+
+ if (masked != val)
+ snprintf(candidates[n++], sizeof(candidates[0]),
+ "0x%08llx", masked);
+ }
+ }
+ }
+ return n;
+}
+
+typedef int (*dir_scope_cb)(int scope_fd, struct perf_pmu *pmu, void *ctx);
+
+static int dir_for_each_scope(const struct pmu_layer *layer,
+ struct perf_pmu *pmu,
+ dir_scope_cb cb, void *ctx)
+{
+ int root_fd, cpus_fd, ret = 0;
+
+ if (!layer->root_path || !layer->root_path[0])
+ return 0;
+
+ root_fd = open(layer->root_path, O_RDONLY | O_DIRECTORY);
+ if (root_fd < 0)
+ return 0;
+
+ /* 1. Exact and deterministic base CPUID scopes under <root>/cpus/. */
+ cpus_fd = openat(root_fd, "cpus", O_RDONLY | O_DIRECTORY);
+ if (cpus_fd >= 0) {
+ struct perf_cpu cpu = pmu ? perf_cpu_map__min(pmu->cpus)
+ : (struct perf_cpu){ -1 };
+ char *cpuid = get_cpuid_allow_env_override(cpu);
+
+ if (!cpuid && pmu)
+ cpuid = get_cpuid_allow_env_override((struct perf_cpu){ -1 });
+ if (cpuid) {
+ char cands[6][128];
+ size_t nr_cands = build_cpuid_candidates(cpuid, cands,
+ ARRAY_SIZE(cands));
+
+ for (size_t i = 0; i < nr_cands; i++) {
+ int scope_fd = openat(cpus_fd, cands[i],
+ O_RDONLY | O_DIRECTORY);
+
+ if (scope_fd < 0)
+ continue;
+ ret = cb(scope_fd, pmu, ctx);
+ close(scope_fd);
+ break;
+ }
+ free(cpuid);
+ }
+ close(cpus_fd);
+ if (ret) {
+ close(root_fd);
+ return ret;
+ }
+ }
+
+ /* 2. Uncore/system PMU identifier scope under <root>/sys/<pmu->id>/. */
+ if (pmu && pmu->id && pmu->id[0]) {
+ int sys_fd = openat(root_fd, "sys", O_RDONLY | O_DIRECTORY);
+
+ if (sys_fd >= 0) {
+ int scope_fd = openat(sys_fd, pmu->id, O_RDONLY | O_DIRECTORY);
+
+ if (scope_fd >= 0) {
+ ret = cb(scope_fd, pmu, ctx);
+ close(scope_fd);
+ }
+ close(sys_fd);
+ if (ret) {
+ close(root_fd);
+ return ret;
+ }
+ }
+ }
+
+ /* 3. Host-direct layout directly under <root>/. */
+ ret = cb(root_fd, pmu, ctx);
+ close(root_fd);
+ return ret;
+}
+
+typedef int (*dir_pmu_cb)(int pmu_fd, const char *pmu_dir_name,
+ struct perf_pmu *pmu, void *ctx);
+
+static bool seen_pmu_dir(const char *seen[], size_t nr_seen, const char *name)
+{
+ for (size_t i = 0; i < nr_seen; i++) {
+ if (!strcmp(seen[i], name))
+ return true;
+ }
+ return false;
+}
+
+static int dir_for_each_pmu_subdir(int scope_fd, struct perf_pmu *pmu,
+ dir_pmu_cb cb, void *ctx)
+{
+ const char *cands[4];
+ char no_suffix[MAX_PMU_NAME_LEN];
+ size_t nr_cands = 0;
+ int ret = 0;
+
+ if (pmu->name && pmu->name[0])
+ cands[nr_cands++] = pmu->name;
+
+ if (pmu->alias_name && pmu->alias_name[0] &&
+ !seen_pmu_dir(cands, nr_cands, pmu->alias_name))
+ cands[nr_cands++] = pmu->alias_name;
+
+ if (pmu->is_core && !seen_pmu_dir(cands, nr_cands, "default_core"))
+ cands[nr_cands++] = "default_core";
+
+ if (pmu->is_uncore && pmu->name) {
+ size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+ if (base_len > 0 && base_len < sizeof(no_suffix) &&
+ base_len != strlen(pmu->name)) {
+ memcpy(no_suffix, pmu->name, base_len);
+ no_suffix[base_len] = '\0';
+ if (!seen_pmu_dir(cands, nr_cands, no_suffix))
+ cands[nr_cands++] = no_suffix;
+ }
+ }
+
+ for (size_t i = 0; i < nr_cands; i++) {
+ int pmu_fd = openat(scope_fd, cands[i], O_RDONLY | O_DIRECTORY);
+
+ if (pmu_fd < 0)
+ continue;
+ ret = cb(pmu_fd, cands[i], pmu, ctx);
+ close(pmu_fd);
+ if (ret)
+ return ret;
+ }
+
+ if (pmu->is_uncore) {
+ int dup_fd = dup(scope_fd);
+
+ if (dup_fd >= 0) {
+ struct io_dir dir;
+ struct io_dirent64 *dent;
+
+ io_dir__init(&dir, dup_fd);
+ while ((dent = io_dir__readdir(&dir)) != NULL) {
+ int pmu_fd;
+
+ if (dent->d_name[0] == '.' ||
+ seen_pmu_dir(cands, nr_cands, dent->d_name) ||
+ !strcmp(dent->d_name, "cpus") ||
+ !strcmp(dent->d_name, "sys") ||
+ !strcmp(dent->d_name, "models") ||
+ !strcmp(dent->d_name, "metricgroups"))
+ continue;
+ if (!perf_pmu__name_wildcard_match(pmu, dent->d_name))
+ continue;
+ pmu_fd = openat(scope_fd, dent->d_name,
+ O_RDONLY | O_DIRECTORY);
+ if (pmu_fd < 0)
+ continue;
+ ret = cb(pmu_fd, dent->d_name, pmu, ctx);
+ close(pmu_fd);
+ if (ret)
+ break;
+ }
+ close(dup_fd);
+ }
+ }
+
+ return ret;
+}
+
+static int dir_scan_events_fd(int events_fd, const char *pmu_dir_name,
+ bool is_sysfs, struct perf_pmu *pmu)
+{
+ int dup_fd = dup(events_fd);
+ struct io_dir dir;
+ struct io_dirent64 *dent;
+ int ret = 0;
+
+ if (dup_fd < 0)
+ return -errno;
+
+ io_dir__init(&dir, dup_fd);
+ while ((dent = io_dir__readdir(&dir)) != NULL) {
+ size_t base_len = 0;
+ enum pmu_event_file_kind kind;
+ char decoded[NAME_MAX];
+ struct perf_pmu_alias *alias;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ kind = pmu_event_file_kind(dent->d_name, &base_len);
+ if (base_len == 0 ||
+ pmu_layer__decode_filename(dent->d_name, base_len,
+ decoded, sizeof(decoded)) != 0)
+ continue;
+
+ alias = pmu_layer_get_or_create_alias(pmu, decoded);
+ if (!alias) {
+ ret = -ENOMEM;
+ break;
+ }
+ if (alias->resolved && active_sysfs_override_fd < 0)
+ continue;
+
+ ret = dir_apply_event_file(events_fd, dent->d_name, kind,
+ pmu_dir_name, is_sysfs, alias, NULL);
+ if (ret)
+ break;
+ }
+ close(dup_fd);
+ return ret;
+}
+
+static int dir_scan_metrics_fd(int metrics_fd, const char *pmu_dir_name,
+ struct perf_pmu *pmu)
+{
+ int dup_fd = dup(metrics_fd);
+ struct io_dir dir;
+ struct io_dirent64 *dent;
+ int ret = 0;
+
+ if (dup_fd < 0)
+ return -errno;
+
+ io_dir__init(&dir, dup_fd);
+ while ((dent = io_dir__readdir(&dir)) != NULL) {
+ size_t base_len = 0;
+ enum pmu_metric_file_kind kind;
+ char decoded[NAME_MAX];
+ struct perf_pmu_metric *metric;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ kind = pmu_metric_file_kind(dent->d_name, &base_len);
+ if (base_len == 0 ||
+ pmu_layer__decode_filename(dent->d_name, base_len,
+ decoded, sizeof(decoded)) != 0)
+ continue;
+
+ metric = pmu_layer_get_or_create_metric(pmu, decoded);
+ if (!metric) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ ret = dir_apply_metric_file(metrics_fd, dent->d_name, kind,
+ pmu_dir_name, metric, NULL);
+ if (ret)
+ break;
+ }
+ close(dup_fd);
+ return ret;
+}
+
+struct dir_find_event_ctx {
+ const char *name;
+ struct perf_pmu_alias *alias;
+};
+
+static int dir_probe_user_event_base(int events_fd, const char *enc_base,
+ const char *canon_name,
+ const char *pmu_dir_name,
+ struct perf_pmu_alias *alias)
+{
+ static const struct {
+ const char *suffix;
+ enum pmu_event_file_kind kind;
+ } probes[] = {
+ { "", PMU_EVENT_FILE_BASE },
+ { ".legacy-terms", PMU_EVENT_FILE_LEGACY_TERMS },
+ { ".unit", PMU_EVENT_FILE_UNIT },
+ { ".scale", PMU_EVENT_FILE_SCALE },
+ { ".scale-unit", PMU_EVENT_FILE_SCALE_UNIT },
+ { ".per-pkg", PMU_EVENT_FILE_PER_PKG },
+ { ".snapshot", PMU_EVENT_FILE_SNAPSHOT },
+ { ".desc", PMU_EVENT_FILE_DESC },
+ { ".long-desc", PMU_EVENT_FILE_LONG_DESC },
+ { ".topic", PMU_EVENT_FILE_TOPIC },
+ { ".compat", PMU_EVENT_FILE_COMPAT },
+ { ".deprecated", PMU_EVENT_FILE_DEPRECATED },
+ { ".retire-lat-mean", PMU_EVENT_FILE_RETIRE_LAT_MEAN },
+ { ".retire-lat-min", PMU_EVENT_FILE_RETIRE_LAT_MIN },
+ { ".retire-lat-max", PMU_EVENT_FILE_RETIRE_LAT_MAX },
+ };
+ char filename[NAME_MAX + 32];
+ bool was_empty = pmu_layer_alias_is_empty(alias);
+ bool matched_any = false;
+
+ for (size_t i = 0; i < ARRAY_SIZE(probes); i++) {
+ bool matched = false;
+ int ret;
+
+ snprintf(filename, sizeof(filename), "%s%s", enc_base, probes[i].suffix);
+ ret = dir_apply_event_file(events_fd, filename, probes[i].kind,
+ pmu_dir_name, /*is_sysfs=*/false,
+ alias, &matched);
+ if (ret)
+ return ret;
+ if (matched)
+ matched_any = true;
+ }
+
+ if (was_empty && matched_any && canon_name && strcmp(alias->name, canon_name) != 0) {
+ char *dup_name = strdup(canon_name);
+
+ if (!dup_name)
+ return -ENOMEM;
+ free(alias->name);
+ alias->name = dup_name;
+ }
+ return 0;
+}
+
+static int user_dir_find_event_pmu_cb(int pmu_fd, const char *pmu_dir_name,
+ struct perf_pmu *pmu __maybe_unused,
+ void *vctx)
+{
+ struct dir_find_event_ctx *ctx = vctx;
+ char lower[NAME_MAX], upper[NAME_MAX];
+ char enc_exact[NAME_MAX], enc_lower[NAME_MAX], enc_upper[NAME_MAX];
+ const char *enc_cands[3], *raw_cands[3];
+ size_t nr_cands = 0, len = strlen(ctx->name);
+ const char *pmu_label;
+ int events_fd;
+
+ if (len == 0 || len >= NAME_MAX || ctx->name[0] == '.' ||
+ pmu_event_file_kind(ctx->name, NULL) != PMU_EVENT_FILE_BASE)
+ return 0;
+
+ events_fd = openat(pmu_fd, "events", O_RDONLY | O_DIRECTORY);
+ if (events_fd < 0)
+ return 0;
+
+ for (size_t i = 0; i < len; i++) {
+ lower[i] = tolower((unsigned char)ctx->name[i]);
+ upper[i] = toupper((unsigned char)ctx->name[i]);
+ }
+ lower[len] = '\0';
+ upper[len] = '\0';
+
+ if (pmu_layer__encode_filename(ctx->name, enc_exact, sizeof(enc_exact)) == 0) {
+ enc_cands[nr_cands] = enc_exact;
+ raw_cands[nr_cands++] = ctx->name;
+ }
+ if (strcmp(lower, ctx->name) != 0 &&
+ pmu_layer__encode_filename(lower, enc_lower, sizeof(enc_lower)) == 0) {
+ enc_cands[nr_cands] = enc_lower;
+ raw_cands[nr_cands++] = lower;
+ }
+ if (strcmp(upper, ctx->name) != 0 && strcmp(upper, lower) != 0 &&
+ pmu_layer__encode_filename(upper, enc_upper, sizeof(enc_upper)) == 0) {
+ enc_cands[nr_cands] = enc_upper;
+ raw_cands[nr_cands++] = upper;
+ }
+
+ pmu_label = !strcmp(pmu_dir_name, "default_core") ? NULL : pmu_dir_name;
+
+ for (size_t i = 0; i < nr_cands; i++) {
+ int ret = dir_probe_user_event_base(events_fd, enc_cands[i], raw_cands[i],
+ pmu_label, ctx->alias);
+ if (ret) {
+ close(events_fd);
+ return ret;
+ }
+ }
+
+ close(events_fd);
+ return 0;
+}
+
+static int user_dir_find_event_scope_cb(int scope_fd, struct perf_pmu *pmu, void *vctx)
+{
+ return dir_for_each_pmu_subdir(scope_fd, pmu, user_dir_find_event_pmu_cb, vctx);
+}
+
+static int user_dir_find_event(const struct pmu_layer *layer,
+ struct perf_pmu *pmu, const char *name,
+ struct perf_pmu_alias *alias)
+{
+ struct dir_find_event_ctx ctx = {
+ .name = name,
+ .alias = alias,
+ };
+ int ret = dir_for_each_scope(layer, pmu, user_dir_find_event_scope_cb, &ctx);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int user_dir_for_each_event_pmu_cb(int pmu_fd, const char *pmu_dir_name,
+ struct perf_pmu *pmu, void *vctx __maybe_unused)
+{
+ const char *pmu_label;
+ int events_fd, ret;
+
+ events_fd = openat(pmu_fd, "events", O_RDONLY | O_DIRECTORY);
+ if (events_fd < 0)
+ return 0;
+
+ pmu_label = !strcmp(pmu_dir_name, "default_core") ? NULL : pmu_dir_name;
+ ret = dir_scan_events_fd(events_fd, pmu_label, /*is_sysfs=*/false, pmu);
+ close(events_fd);
+ return ret;
+}
+
+static int user_dir_for_each_event_scope_cb(int scope_fd, struct perf_pmu *pmu, void *vctx)
+{
+ return dir_for_each_pmu_subdir(scope_fd, pmu, user_dir_for_each_event_pmu_cb, vctx);
+}
+
+static int user_dir_for_each_event(const struct pmu_layer *layer,
+ struct perf_pmu *pmu)
+{
+ return dir_for_each_scope(layer, pmu, user_dir_for_each_event_scope_cb, NULL);
+}
+
+struct dir_find_metric_ctx {
+ const char *name;
+ struct perf_pmu_metric *metric;
+};
+
+static int user_dir_find_metric_pmu_cb(int pmu_fd, const char *pmu_dir_name,
+ struct perf_pmu *pmu __maybe_unused,
+ void *vctx)
+{
+ static const struct {
+ const char *suffix;
+ enum pmu_metric_file_kind kind;
+ } probes[] = {
+ { "", PMU_METRIC_FILE_BASE },
+ { ".scale-unit", PMU_METRIC_FILE_SCALE_UNIT },
+ { ".desc", PMU_METRIC_FILE_DESC },
+ { ".long-desc", PMU_METRIC_FILE_LONG_DESC },
+ { ".group", PMU_METRIC_FILE_GROUP },
+ { ".no-group", PMU_METRIC_FILE_NO_GROUP },
+ { ".default-group", PMU_METRIC_FILE_DEFAULT_GROUP },
+ { ".threshold", PMU_METRIC_FILE_THRESHOLD },
+ { ".compat", PMU_METRIC_FILE_COMPAT },
+ { ".aggr-mode", PMU_METRIC_FILE_AGGR_MODE },
+ { ".constraint", PMU_METRIC_FILE_CONSTRAINT },
+ { ".show-events", PMU_METRIC_FILE_SHOW_EVENTS },
+ };
+ struct dir_find_metric_ctx *ctx = vctx;
+ char enc_name[NAME_MAX];
+ char filename[NAME_MAX + 32];
+ int metrics_fd;
+
+ if (!ctx->name[0] || ctx->name[0] == '.' ||
+ pmu_metric_file_kind(ctx->name, NULL) != PMU_METRIC_FILE_BASE ||
+ pmu_layer__encode_filename(ctx->name, enc_name, sizeof(enc_name)) != 0)
+ return 0;
+
+ metrics_fd = openat(pmu_fd, "metrics", O_RDONLY | O_DIRECTORY);
+ if (metrics_fd < 0)
+ return 0;
+
+ for (size_t i = 0; i < ARRAY_SIZE(probes); i++) {
+ int ret;
+
+ snprintf(filename, sizeof(filename), "%s%s", enc_name, probes[i].suffix);
+ ret = dir_apply_metric_file(metrics_fd, filename, probes[i].kind,
+ pmu_dir_name, ctx->metric, NULL);
+ if (ret) {
+ close(metrics_fd);
+ return ret;
+ }
+ }
+
+ close(metrics_fd);
+ return 0;
+}
+
+static int user_dir_find_metric_scope_cb(int scope_fd, struct perf_pmu *pmu, void *vctx)
+{
+ return dir_for_each_pmu_subdir(scope_fd, pmu, user_dir_find_metric_pmu_cb, vctx);
+}
+
+static int user_dir_find_metric(const struct pmu_layer *layer,
+ struct perf_pmu *pmu, const char *name,
+ struct perf_pmu_metric *metric)
+{
+ struct dir_find_metric_ctx ctx = {
+ .name = name,
+ .metric = metric,
+ };
+ int ret = dir_for_each_scope(layer, pmu, user_dir_find_metric_scope_cb, &ctx);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int user_dir_for_each_metric_pmu_cb(int pmu_fd, const char *pmu_dir_name,
+ struct perf_pmu *pmu,
+ void *vctx __maybe_unused)
+{
+ int metrics_fd, ret;
+
+ metrics_fd = openat(pmu_fd, "metrics", O_RDONLY | O_DIRECTORY);
+ if (metrics_fd < 0)
+ return 0;
+
+ ret = dir_scan_metrics_fd(metrics_fd, pmu_dir_name, pmu);
+ close(metrics_fd);
+ return ret;
+}
+
+static int user_dir_for_each_metric_scope_cb(int scope_fd, struct perf_pmu *pmu, void *vctx)
+{
+ return dir_for_each_pmu_subdir(scope_fd, pmu, user_dir_for_each_metric_pmu_cb, vctx);
+}
+
+static int user_dir_for_each_metric(const struct pmu_layer *layer,
+ struct perf_pmu *pmu)
+{
+ return dir_for_each_scope(layer, pmu, user_dir_for_each_metric_scope_cb, NULL);
+}
+
+struct dir_metricgroup_ctx {
+ const char *group;
+ struct perf_pmu_metricgroup *mg;
+};
+
+static int dir_probe_metricgroups_fd(int mg_fd, const char *enc_group,
+ struct perf_pmu_metricgroup *mg)
+{
+ char filename[NAME_MAX + 16];
+ char *val = NULL;
+
+ snprintf(filename, sizeof(filename), "%s.desc", enc_group);
+ if (dir_read_file_at(mg_fd, filename, &val) == 0) {
+ mg->desc = val;
+ return 1;
+ }
+ return 0;
+}
+
+static int user_dir_metricgroup_pmu_cb(int pmu_fd, const char *pmu_dir_name __maybe_unused,
+ struct perf_pmu *pmu __maybe_unused, void *vctx)
+{
+ struct dir_metricgroup_ctx *ctx = vctx;
+ char enc_group[NAME_MAX];
+ int mg_fd, ret = 0;
+
+ if (pmu_layer__encode_filename(ctx->group, enc_group, sizeof(enc_group)) != 0)
+ return 0;
+
+ mg_fd = openat(pmu_fd, "metricgroups", O_RDONLY | O_DIRECTORY);
+ if (mg_fd < 0)
+ return 0;
+
+ ret = dir_probe_metricgroups_fd(mg_fd, enc_group, ctx->mg);
+ close(mg_fd);
+ return ret;
+}
+
+static int user_dir_metricgroup_scope_cb(int scope_fd, struct perf_pmu *pmu, void *vctx)
+{
+ struct dir_metricgroup_ctx *ctx = vctx;
+ char enc_group[NAME_MAX];
+ int mg_fd, ret = 0;
+
+ if (pmu_layer__encode_filename(ctx->group, enc_group, sizeof(enc_group)) != 0)
+ return 0;
+
+ mg_fd = openat(scope_fd, "metricgroups", O_RDONLY | O_DIRECTORY);
+ if (mg_fd >= 0) {
+ ret = dir_probe_metricgroups_fd(mg_fd, enc_group, ctx->mg);
+ close(mg_fd);
+ if (ret)
+ return ret;
+ }
+ if (pmu)
+ return dir_for_each_pmu_subdir(scope_fd, pmu,
+ user_dir_metricgroup_pmu_cb, vctx);
+ return 0;
+}
+
+static int user_dir_load_metricgroup(const struct pmu_layer *layer,
+ struct perf_pmu *pmu,
+ const char *group,
+ struct perf_pmu_metricgroup *mg)
+{
+ struct dir_metricgroup_ctx ctx = {
+ .group = group,
+ .mg = mg,
+ };
+ int ret;
+
+ if (mg->desc)
+ return 0;
+
+ ret = dir_for_each_scope(layer, pmu, user_dir_metricgroup_scope_cb, &ctx);
+ return ret < 0 ? ret : 0;
+}
+
+static const struct pmu_layer_ops __maybe_unused user_dir_layer_ops = {
+ .find_event = user_dir_find_event,
+ .for_each_event = user_dir_for_each_event,
+ .find_metric = user_dir_find_metric,
+ .for_each_metric = user_dir_for_each_metric,
+ .load_metricgroup = user_dir_load_metricgroup,
+};
+
+static int sysfs_open_events_dir(struct perf_pmu *pmu)
+{
+ char path[PATH_MAX];
+ size_t len;
+
+ if (active_sysfs_override_fd >= 0) {
+ scnprintf(path, sizeof(path), "%s/events", pmu->name);
+ return openat(active_sysfs_override_fd, path, O_RDONLY | O_DIRECTORY);
+ }
+
+ if (pmu->sysfs_aliases_loaded)
+ return -1;
+
+ len = perf_pmu__event_source_devices_scnprintf(path, sizeof(path));
+ if (!len)
+ return -1;
+ scnprintf(path + len, sizeof(path) - len, "%s/events", pmu->name);
+ return open(path, O_RDONLY | O_DIRECTORY);
+}
+
+static int sysfs_layer_find_event(const struct pmu_layer *layer __maybe_unused,
+ struct perf_pmu *pmu, const char *name,
+ struct perf_pmu_alias *alias)
+{
+ char lower[NAME_MAX], upper[NAME_MAX];
+ const char *cands[3];
+ size_t nr_cands = 0, len = strlen(name);
+ int events_fd;
+
+ if (pmu->eager_sysfs_loaded)
+ return 0;
+
+ if (len == 0 || len >= NAME_MAX || name[0] == '.' || strchr(name, '/') ||
+ pmu_event_file_kind(name, NULL) != PMU_EVENT_FILE_BASE)
+ return 0;
+
+ events_fd = sysfs_open_events_dir(pmu);
+ if (events_fd < 0) {
+ if (errno == ENOENT)
+ pmu->sysfs_aliases_loaded = true;
+ return 0;
+ }
+
+ cands[nr_cands++] = name;
+ for (size_t i = 0; i < len; i++) {
+ lower[i] = tolower((unsigned char)name[i]);
+ upper[i] = toupper((unsigned char)name[i]);
+ }
+ lower[len] = '\0';
+ upper[len] = '\0';
+
+ if (strcmp(lower, name) != 0)
+ cands[nr_cands++] = lower;
+ if (strcmp(upper, name) != 0 && strcmp(upper, lower) != 0)
+ cands[nr_cands++] = upper;
+
+ for (size_t i = 0; i < nr_cands; i++) {
+ struct stat st;
+ char filename[NAME_MAX + 16];
+ int fd = openat(events_fd, cands[i], O_RDONLY);
+
+ if (fd < 0)
+ continue;
+ if (fstat(fd, &st) == 0 && !S_ISDIR(st.st_mode)) {
+ static const struct {
+ const char *suffix;
+ enum pmu_event_file_kind kind;
+ } sysfs_probes[] = {
+ { "", PMU_EVENT_FILE_BASE },
+ { ".unit", PMU_EVENT_FILE_UNIT },
+ { ".scale", PMU_EVENT_FILE_SCALE },
+ { ".per-pkg", PMU_EVENT_FILE_PER_PKG },
+ { ".snapshot", PMU_EVENT_FILE_SNAPSHOT },
+ };
+ close(fd);
+ pmu_layer_canon_alias_name(alias, cands[i]);
+ for (size_t j = 0; j < ARRAY_SIZE(sysfs_probes); j++) {
+ int ret;
+
+ snprintf(filename, sizeof(filename), "%s%s",
+ cands[i], sysfs_probes[j].suffix);
+ ret = dir_apply_event_file(events_fd, filename,
+ sysfs_probes[j].kind, NULL,
+ /*is_sysfs=*/true,
+ alias, NULL);
+ if (ret) {
+ close(events_fd);
+ return ret;
+ }
+ }
+ break;
+ }
+ close(fd);
+ }
+
+ close(events_fd);
+ return 0;
+}
+
+static int sysfs_layer_for_each_event(const struct pmu_layer *layer __maybe_unused,
+ struct perf_pmu *pmu)
+{
+ int events_fd, ret;
+
+ if (pmu->eager_sysfs_loaded && active_sysfs_override_fd < 0)
+ return 0;
+
+ events_fd = sysfs_open_events_dir(pmu);
+ if (events_fd < 0) {
+ pmu->sysfs_aliases_loaded = true;
+ return 0;
+ }
+
+ ret = dir_scan_events_fd(events_fd, NULL, /*is_sysfs=*/true, pmu);
+ close(events_fd);
+ if (!ret)
+ pmu->sysfs_aliases_loaded = true;
+ return ret;
+}
+
+static const struct pmu_layer_ops sysfs_layer_ops = {
+ .find_event = sysfs_layer_find_event,
+ .for_each_event = sysfs_layer_for_each_event,
+};
+
static int pmu_layers_append(enum pmu_layer_kind kind,
const struct pmu_layer_ops *ops,
const char *root_path)
@@ -725,6 +2028,10 @@ static int pmu_layers_ensure_init(void)
if (ret)
goto err;
+ ret = pmu_layers_append(PMU_LAYER_SYSFS, &sysfs_layer_ops, NULL);
+ if (ret)
+ goto err;
+
ret = pmu_layers_append(PMU_LAYER_BUILTIN_COMMON, &builtin_common_layer_ops, NULL);
if (ret)
goto err;
@@ -786,7 +2093,8 @@ struct perf_pmu_alias *pmu_layer__find_event(struct perf_pmu *pmu,
if (hashmap__find(pmu->aliases, name, &alias))
return alias->terms ? alias : NULL;
- if (pmu->aliases_loaded)
+ if (pmu->aliases_loaded ||
+ (pmu->cpu_aliases_added && pmu->sysfs_aliases_loaded))
return NULL;
if (pmu_layers_ensure_init())
@@ -828,17 +2136,21 @@ struct perf_pmu_alias *pmu_layer__find_event(struct perf_pmu *pmu,
return alias;
}
-int pmu_layer__load_events(struct perf_pmu *pmu)
+int pmu_layer__load_events_at(struct perf_pmu *pmu, int sysfs_fd)
{
int ret;
- if (!pmu || pmu->aliases_loaded)
+ if (!pmu || pmu->aliases_loaded ||
+ (pmu->cpu_aliases_added && pmu->sysfs_aliases_loaded))
return 0;
ret = pmu_layers_ensure_init();
if (ret)
return ret;
+ if (sysfs_fd >= 0)
+ active_sysfs_override_fd = sysfs_fd;
+
/*
* Any single-event entries cached before full enumeration remain in
* pmu->aliases; top-down iteration fills any missing events cleanly.
@@ -849,17 +2161,28 @@ int pmu_layer__load_events(struct perf_pmu *pmu)
if (!layer->ops->for_each_event)
continue;
ret = layer->ops->for_each_event(layer, pmu);
- if (ret) {
- pmu_layer_finalize_aliases(pmu, /*promote_legacy=*/false);
- return ret;
- }
+ if (ret)
+ break;
+ }
+
+ active_sysfs_override_fd = -1;
+ if (ret) {
+ pmu_layer_finalize_aliases(pmu, /*promote_legacy=*/false);
+ return ret;
}
pmu_layer_finalize_aliases(pmu, /*promote_legacy=*/true);
+ if (sysfs_fd >= 0)
+ pmu->eager_sysfs_loaded = true;
pmu->aliases_loaded = true;
return 0;
}
+int pmu_layer__load_events(struct perf_pmu *pmu)
+{
+ return pmu_layer__load_events_at(pmu, -1);
+}
+
int pmu_layer__merge_event_table(struct perf_pmu *pmu,
const struct pmu_events_table *table)
{
@@ -876,6 +2199,20 @@ int pmu_layer__merge_event_table(struct perf_pmu *pmu,
builtin_for_each_event_cb, &ctx);
}
+int pmu_layer__merge_sys_events(struct perf_pmu *pmu)
+{
+ struct builtin_iter_event_ctx ctx = {
+ .pmu = pmu,
+ .is_common = false,
+ .override_existing = true,
+ };
+
+ if (!pmu || !pmu->id)
+ return 0;
+
+ return pmu_for_each_sys_event(builtin_for_each_sys_event_cb, &ctx);
+}
+
struct perf_pmu_metric *pmu_layer__find_metric(struct perf_pmu *pmu,
const char *name)
{
diff --git a/tools/perf/util/pmu-layer.h b/tools/perf/util/pmu-layer.h
index 330af9415448..5d8a33481410 100644
--- a/tools/perf/util/pmu-layer.h
+++ b/tools/perf/util/pmu-layer.h
@@ -154,11 +154,17 @@ void perf_pmu_free_metricgroup(struct perf_pmu_metricgroup *mg);
void perf_pmu_metric__to_pmu_metric(const struct perf_pmu_metric *src,
struct pmu_metric *dst);
+int pmu_layer__encode_filename(const char *name, char *buf, size_t size);
+int pmu_layer__decode_filename(const char *encoded, size_t len,
+ char *buf, size_t size);
+
struct perf_pmu_alias *pmu_layer__find_event(struct perf_pmu *pmu,
const char *name);
int pmu_layer__load_events(struct perf_pmu *pmu);
+int pmu_layer__load_events_at(struct perf_pmu *pmu, int sysfs_fd);
int pmu_layer__merge_event_table(struct perf_pmu *pmu,
const struct pmu_events_table *table);
+int pmu_layer__merge_sys_events(struct perf_pmu *pmu);
struct perf_pmu_metric *pmu_layer__find_metric(struct perf_pmu *pmu,
const char *name);
--
2.56.1.703.gf848ed807c-goog
next prev parent reply other threads:[~2026-11-13 21:11 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 21:10 [PATCH 00/11] perf pmu: Layered sysfs-style PMU event and metric directories Ian Rogers
2026-11-13 21:10 ` [PATCH 01/11] perf pmu: Generalize pmu_alias_info_file() and lazy single-event sysfs loading Ian Rogers
2026-11-13 21:10 ` [PATCH 02/11] perf pmu: Add metric and metricgroup storage on struct perf_pmu Ian Rogers
2026-11-13 21:10 ` [PATCH 03/11] perf pmu-layer: Introduce stateless struct pmu_layer abstraction and builtin adapter Ian Rogers
2026-11-13 21:10 ` Ian Rogers [this message]
2026-11-13 21:10 ` [PATCH 05/11] perf config: Add pmu-events.dir configuration option Ian Rogers
2026-11-13 21:10 ` [PATCH 06/11] perf pmu: Switch alias resolution and enumeration to layered PMU stack Ian Rogers
2026-11-13 21:10 ` [PATCH 07/11] perf metricgroup: Route metric and metricgroup lookups through struct perf_pmu Ian Rogers
2026-11-13 21:10 ` [PATCH 08/11] perf list, jevents: Add --export-dir and --output-dir sysfs-style exporters Ian Rogers
2026-11-13 21:11 ` [PATCH 09/11] perf jevents: Add compiled-in model and sys table iterators Ian Rogers
2026-11-13 21:11 ` [PATCH 10/11] perf list: Add --export-cpuid to export compiled-in CPU models Ian Rogers
2026-11-13 21:11 ` [PATCH 11/11] perf tests: Add round-trip export, CPUID symlink, and pmu-events.dir overlay tests Ian Rogers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261113211105.3118281-5-irogers@google.com \
--to=irogers@google.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®