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 11/11] perf tests: Add round-trip export, CPUID symlink, and pmu-events.dir overlay tests
Date: Fri, 13 Nov 2026 13:11:02 -0800 [thread overview]
Message-ID: <20261113211105.3118281-12-irogers@google.com> (raw)
In-Reply-To: <20261113211105.3118281-1-irogers@google.com>
Add C unit and CLI shell tests exercising the layered PMU directory VFS
stack and sysfs-style export tools through public pmu, pmus, and
parse-events APIs (keeping struct perf_pmu_alias and struct
perf_pmu_metric encapsulated):
- In tools/perf/tests/pmu.c (Layered PMU directory overlays):
Test colon-separated pmu-events.dir overlays via perfconfig, base
(cpus/GenuineIntel-6-99 -> ../models/x86/mock_arch) and
stepping-specific (cpus/GenuineIntel-6-99-4 ->
../models/x86/mock_arch_step4) CPUID POSIX symlinks via PERF_CPUID
without mapfile.csv, single-file attribute overrides preserving
lower-layer sysfs event terms, multi-directory priority overrides
across events, metrics, and metricgroups, extended event and metric
sidecars, %2F filename encoding, and heterogeneous multi-instance PMU
sub-metric resolution.
- In tools/perf/tests/shell/pmu_events_dir.sh:
Test lossless perf list --export-dir JSON round-tripping via
PERF_CONFIG, custom user overlay events and metrics executed with perf
stat -e / -M, file removal restoring lower-layer defaults,
perf list --export-cpuid (architecture, model, and stepping CPUID
export, metric filtering, and error handling), and jevents.py
--output-dir literal CPUID and uncore symlink generation.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/pmu.c | 959 +++++++++++++++++++++++
tools/perf/tests/shell/pmu_events_dir.sh | 236 ++++++
2 files changed, 1195 insertions(+)
create mode 100755 tools/perf/tests/shell/pmu_events_dir.sh
diff --git a/tools/perf/tests/pmu.c b/tools/perf/tests/pmu.c
index f208932590df..fc2a92dbb2b3 100644
--- a/tools/perf/tests/pmu.c
+++ b/tools/perf/tests/pmu.c
@@ -1,9 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
+#include "builtin.h"
+#include "config.h"
#include "evlist.h"
#include "evsel.h"
+#include "metricgroup.h"
#include "parse-events.h"
#include "pmu.h"
#include "pmus.h"
+#include "pmu-events/pmu-events.h"
#include "tests.h"
#include "debug.h"
#include "fncache.h"
@@ -12,8 +16,10 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -628,6 +634,958 @@ static int test__pmu_match(struct test_suite *test __maybe_unused, int subtest _
return TEST_OK;
}
+static int overlay_mkdir_p(const char *path)
+{
+ char tmp[PATH_MAX];
+ size_t len = strlen(path);
+
+ if (len == 0 || len >= sizeof(tmp))
+ return -ENAMETOOLONG;
+
+ memcpy(tmp, path, len + 1);
+ for (char *p = tmp + 1; *p; p++) {
+ if (*p != '/')
+ continue;
+ *p = '\0';
+ if (mkdir(tmp, 0755) < 0 && errno != EEXIST)
+ return -errno;
+ *p = '/';
+ }
+ if (mkdir(tmp, 0755) < 0 && errno != EEXIST)
+ return -errno;
+ return 0;
+}
+
+static int overlay_write_file(const char *root, const char *relpath,
+ const char *content)
+{
+ char full[PATH_MAX];
+ char *slash;
+ size_t len = strlen(content);
+ int fd;
+
+ if (snprintf(full, sizeof(full), "%s/%s", root, relpath) >= (int)sizeof(full))
+ return -ENAMETOOLONG;
+
+ slash = strrchr(full, '/');
+ if (slash) {
+ *slash = '\0';
+ if (overlay_mkdir_p(full) < 0)
+ return -errno;
+ *slash = '/';
+ }
+
+ fd = open(full, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0)
+ return -errno;
+
+ if (len > 0 && write(fd, content, len) != (ssize_t)len) {
+ int err = errno ? -errno : -EIO;
+
+ close(fd);
+ return err;
+ }
+ close(fd);
+ return 0;
+}
+
+static int overlay_symlink(const char *root, const char *relpath,
+ const char *target)
+{
+ char full[PATH_MAX];
+ char *slash;
+
+ if (snprintf(full, sizeof(full), "%s/%s", root, relpath) >= (int)sizeof(full))
+ return -ENAMETOOLONG;
+
+ slash = strrchr(full, '/');
+ if (slash) {
+ *slash = '\0';
+ if (overlay_mkdir_p(full) < 0)
+ return -errno;
+ *slash = '/';
+ }
+ if (symlink(target, full) < 0)
+ return -errno;
+ return 0;
+}
+
+static void overlay_rm_rf_at(int parent_fd, const char *name)
+{
+ struct stat st;
+
+ if (fstatat(parent_fd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+ return;
+
+ if (S_ISDIR(st.st_mode)) {
+ int fd = openat(parent_fd, name,
+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+
+ if (fd >= 0) {
+ DIR *dir = fdopendir(fd);
+
+ if (dir) {
+ struct dirent *dent;
+
+ while ((dent = readdir(dir)) != NULL) {
+ if (!strcmp(dent->d_name, ".") ||
+ !strcmp(dent->d_name, ".."))
+ continue;
+ overlay_rm_rf_at(fd, dent->d_name);
+ }
+ closedir(dir);
+ } else {
+ close(fd);
+ }
+ }
+ unlinkat(parent_fd, name, AT_REMOVEDIR);
+ return;
+ }
+
+ unlinkat(parent_fd, name, 0);
+}
+
+static bool overlay_expect_file(const char *root, const char *relpath,
+ const char *expected)
+{
+ char full[PATH_MAX];
+ char buf[256];
+ ssize_t n;
+ int fd;
+
+ if (snprintf(full, sizeof(full), "%s/%s", root, relpath) >= (int)sizeof(full))
+ return false;
+
+ fd = open(full, O_RDONLY);
+ if (fd < 0)
+ return false;
+
+ n = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (n < 0)
+ return false;
+
+ buf[n] = '\0';
+ return strcmp(buf, expected) == 0;
+}
+
+struct overlay_event_snapshot {
+ const char *target;
+ bool found;
+ char str[128];
+ char desc[128];
+ char topic[64];
+ char pmu_name[64];
+};
+
+static int snapshot_event_cb(void *state, struct pmu_event_info *info)
+{
+ struct overlay_event_snapshot *snap = state;
+ bool match = info->name && !strcmp(info->name, snap->target);
+
+ if (!match && info->name) {
+ const char *slash = strchr(info->name, '/');
+ const char *end = slash ? strrchr(slash + 1, '/') : NULL;
+ size_t tlen = strlen(snap->target);
+
+ if (slash && end && (size_t)(end - (slash + 1)) == tlen &&
+ !strncmp(slash + 1, snap->target, tlen))
+ match = true;
+ }
+ if (!match)
+ return 0;
+
+ snap->found = true;
+ snprintf(snap->str, sizeof(snap->str), "%s", info->str ?: "");
+ snprintf(snap->desc, sizeof(snap->desc), "%s", info->desc ?: "");
+ snprintf(snap->topic, sizeof(snap->topic), "%s", info->topic ?: "");
+ snprintf(snap->pmu_name, sizeof(snap->pmu_name), "%s", info->pmu_name ?: "");
+ return 0;
+}
+
+static bool overlay_find_event(struct perf_pmu *pmu, const char *event,
+ struct overlay_event_snapshot *snap)
+{
+ memset(snap, 0, sizeof(*snap));
+ snap->target = event;
+ if (perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false,
+ snap, snapshot_event_cb))
+ return false;
+ return snap->found;
+}
+
+static int count_metrics_cb(const struct pmu_metric *pm __maybe_unused,
+ const struct pmu_metrics_table *table __maybe_unused,
+ void *data)
+{
+ int *cnt = data;
+
+ (*cnt)++;
+ return 0;
+}
+
+static int run_pmu_layer_overlay_checks(const char *overlay_root,
+ char *config_path, size_t config_path_sz,
+ char *sysfs_dir, size_t sysfs_dir_sz,
+ struct perf_pmu **pmu_out)
+{
+ char config_body[PATH_MAX * 2];
+ struct perf_pmu *pmu;
+ struct overlay_event_snapshot ev_snap;
+ struct pmu_metric metric;
+ struct evlist *evlist;
+ struct evsel *first_ev;
+ struct parse_events_error err;
+ const char *mg_desc;
+ double parsed_scale = 0.0, parsed_retire_lat = 0.0;
+ char parsed_unit[32] = "";
+ int parse_ret, metric_cnt = 0;
+ u64 parsed_config;
+
+ /*
+ * Populate dir2 (lower-priority overlay) using a canonical model tree
+ * linked via a base-CPUID POSIX symlink (no mapfile.csv or regexes).
+ */
+ TEST_ASSERT_VAL("write base-cpuid-event",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/events/base-cpuid-event",
+ "krava01=1\n") == 0);
+ TEST_ASSERT_VAL("write base-cpuid-event.desc",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/events/base-cpuid-event.desc",
+ "From base CPUID model\n") == 0);
+ TEST_ASSERT_VAL("write base-cpuid-event.topic",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/events/base-cpuid-event.topic",
+ "custom\n") == 0);
+ TEST_ASSERT_VAL("write base-cpuid-event.scale-unit",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/events/base-cpuid-event.scale-unit",
+ "64Bytes\n") == 0);
+ TEST_ASSERT_VAL("write base-cpuid-event.retire-lat-mean",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/events/base-cpuid-event.retire-lat-mean",
+ "12.5\n") == 0);
+ TEST_ASSERT_VAL("write dir2 overridden-event",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/events/overridden-event",
+ "krava01=2\n") == 0);
+ TEST_ASSERT_VAL("write slash%2Fevent",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/events/slash%2Fevent",
+ "krava01=3\n") == 0);
+ TEST_ASSERT_VAL("write slash%2Fevent.desc",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/events/slash%2Fevent.desc",
+ "Encoded slash event\n") == 0);
+
+ TEST_ASSERT_VAL("write base_metric",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/metrics/base_metric",
+ "test-event * 2\n") == 0);
+ TEST_ASSERT_VAL("write base_metric.scale-unit",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/metrics/base_metric.scale-unit",
+ "100%\n") == 0);
+ TEST_ASSERT_VAL("write base_metric.desc",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/metrics/base_metric.desc",
+ "Base metric desc\n") == 0);
+ TEST_ASSERT_VAL("write base_metric.group",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/metrics/base_metric.group",
+ "TestOverlayGroup\n") == 0);
+ TEST_ASSERT_VAL("write base_metric.aggr-mode",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/metrics/base_metric.aggr-mode",
+ "PerCore\n") == 0);
+ TEST_ASSERT_VAL("write base_metric.constraint",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/metrics/base_metric.constraint",
+ "NO_GROUP_EVENTS\n") == 0);
+ TEST_ASSERT_VAL("write dir2 overridden_metric",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/perf-pmu-test/metrics/overridden_metric",
+ "test-event + 1\n") == 0);
+
+ TEST_ASSERT_VAL("write dir2 TestOverlayGroup.desc",
+ overlay_write_file(overlay_root,
+ "dir2/models/testmodel/metricgroups/TestOverlayGroup.desc",
+ "Dir2 group description\n") == 0);
+
+ TEST_ASSERT_VAL("symlink base CPUID",
+ overlay_symlink(overlay_root,
+ "dir2/cpus/GenuineTest-6-AA",
+ "../models/testmodel") == 0);
+
+ /*
+ * Populate dir1 (higher-priority overlay) with base and single-attribute
+ * overrides.
+ */
+ TEST_ASSERT_VAL("write dir1 test-event.desc override",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/events/test-event.desc",
+ "Overridden sysfs description\n") == 0);
+ TEST_ASSERT_VAL("write dir1 test-event.legacy-terms fallback",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/events/test-event.legacy-terms",
+ "krava01=9\n") == 0);
+ TEST_ASSERT_VAL("write dir1 legacy-only-event.legacy-terms",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/events/legacy-only-event.legacy-terms",
+ "krava01=7\n") == 0);
+ TEST_ASSERT_VAL("write dir1 malformed per-pkg boolean sidecar",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/events/base-cpuid-event.per-pkg",
+ "garbage\n") == 0);
+ TEST_ASSERT_VAL("write dir1 default_core bpu-refs.legacy-terms override",
+ overlay_write_file(overlay_root,
+ "dir1/default_core/events/bpu-refs.legacy-terms",
+ "legacy-cache-config=1\n") == 0);
+ TEST_ASSERT_VAL("write dir1 .hidden dotfile",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/events/.hidden",
+ "krava01=6\n") == 0);
+ TEST_ASSERT_VAL("write dir1 overridden-event over dir2",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/events/overridden-event",
+ "krava01=5\n") == 0);
+ TEST_ASSERT_VAL("write dir1 base_metric.threshold override",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/metrics/base_metric.threshold",
+ "base_metric > 10\n") == 0);
+ TEST_ASSERT_VAL("write dir1 malformed show-events boolean sidecar",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/metrics/base_metric.show-events",
+ "garbage\n") == 0);
+ TEST_ASSERT_VAL("write dir1 overridden_metric over dir2",
+ overlay_write_file(overlay_root,
+ "dir1/perf-pmu-test/metrics/overridden_metric",
+ "test-event * 4\n") == 0);
+ TEST_ASSERT_VAL("write dir1 TestOverlayGroup.desc override",
+ overlay_write_file(overlay_root,
+ "dir1/metricgroups/TestOverlayGroup.desc",
+ "Dir1 winning group description\n") == 0);
+ TEST_ASSERT_VAL("write dir1 tool TopdownL1.desc override",
+ overlay_write_file(overlay_root,
+ "dir1/tool/metricgroups/TopdownL1.desc",
+ "Overridden TopdownL1 on tool PMU\n") == 0);
+ TEST_ASSERT_VAL("write dir1 soc_v1 het_sub",
+ overlay_write_file(overlay_root,
+ "dir1/sys/soc_v1/het_pmu/metrics/het_sub",
+ "duration_time\n") == 0);
+ TEST_ASSERT_VAL("write dir1 soc_v1 het_parent",
+ overlay_write_file(overlay_root,
+ "dir1/sys/soc_v1/het_pmu/metrics/het_parent",
+ "het_sub + 1\n") == 0);
+ TEST_ASSERT_VAL("write dir1 soc_v2 het_sub",
+ overlay_write_file(overlay_root,
+ "dir1/sys/soc_v2/het_pmu/metrics/het_sub",
+ "user_time\n") == 0);
+ TEST_ASSERT_VAL("write dir1 soc_v2 het_parent",
+ overlay_write_file(overlay_root,
+ "dir1/sys/soc_v2/het_pmu/metrics/het_parent",
+ "het_sub + 1\n") == 0);
+ TEST_ASSERT_VAL("write dir1 soc_v2 het_soc2_only",
+ overlay_write_file(overlay_root,
+ "dir1/sys/soc_v2/het_pmu/metrics/het_soc2_only",
+ "user_time + 2\n") == 0);
+ TEST_ASSERT_VAL("write dir1 soc_v1 het_filt_m",
+ overlay_write_file(overlay_root,
+ "dir1/sys/soc_v1/het_pmu/metrics/het_filt_m",
+ "het_pmu@het_ev_0@ + 1\n") == 0);
+ TEST_ASSERT_VAL("write dir1 soc_v2 het_filt_m",
+ overlay_write_file(overlay_root,
+ "dir1/sys/soc_v2/het_pmu/metrics/het_filt_m",
+ "het_pmu@het_ev_1@ + 1\n") == 0);
+ TEST_ASSERT_VAL("write dir1 het_pmu_0 het_coexist_m",
+ overlay_write_file(overlay_root,
+ "dir1/het_pmu_0/metrics/het_coexist_m",
+ "duration_time + 10\n") == 0);
+ TEST_ASSERT_VAL("write dir2 het_pmu het_coexist_m",
+ overlay_write_file(overlay_root,
+ "dir2/het_pmu/metrics/het_coexist_m",
+ "duration_time + 20\n") == 0);
+ TEST_ASSERT_VAL("write dir1 het_pmu_0 het_coexist_ev",
+ overlay_write_file(overlay_root,
+ "dir1/het_pmu_0/events/het_coexist_ev",
+ "config=0x31\n") == 0);
+ TEST_ASSERT_VAL("write dir2 het_pmu het_coexist_ev",
+ overlay_write_file(overlay_root,
+ "dir2/het_pmu/events/het_coexist_ev",
+ "config=0x32\n") == 0);
+ TEST_ASSERT_VAL("write het_pmu_0 type",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_pmu_0/type", "9997\n") == 0);
+ TEST_ASSERT_VAL("write het_pmu_0 cpumask",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_pmu_0/cpumask", "0\n") == 0);
+ TEST_ASSERT_VAL("write het_pmu_0 format config",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_pmu_0/format/config", "config:0-63\n") == 0);
+ TEST_ASSERT_VAL("write het_pmu_0 het_ev_0",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_pmu_0/events/het_ev_0", "config=0x1\n") == 0);
+ TEST_ASSERT_VAL("write het_pmu_1 type",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_pmu_1/type", "9998\n") == 0);
+ TEST_ASSERT_VAL("write het_pmu_1 cpumask",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_pmu_1/cpumask", "0\n") == 0);
+ TEST_ASSERT_VAL("write het_pmu_1 format config",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_pmu_1/format/config", "config:0-63\n") == 0);
+ TEST_ASSERT_VAL("write het_pmu_1 het_ev_1",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_pmu_1/events/het_ev_1", "config=0x2\n") == 0);
+ TEST_ASSERT_VAL("write dir1 aa_het_core_0 het_coexist_m",
+ overlay_write_file(overlay_root,
+ "dir1/aa_het_core_0/metrics/het_coexist_m",
+ "duration_time + 30\n") == 0);
+ TEST_ASSERT_VAL("write dir2 default_core het_coexist_m",
+ overlay_write_file(overlay_root,
+ "dir2/default_core/metrics/het_coexist_m",
+ "duration_time + 40\n") == 0);
+ TEST_ASSERT_VAL("write aa_het_core_0 type",
+ overlay_write_file(overlay_root,
+ "het_sysfs/aa_het_core_0/type", "9994\n") == 0);
+ TEST_ASSERT_VAL("write aa_het_core_0 cpus",
+ overlay_write_file(overlay_root,
+ "het_sysfs/aa_het_core_0/cpus", "0\n") == 0);
+ TEST_ASSERT_VAL("write dir1 default_core het_core_m.desc",
+ overlay_write_file(overlay_root,
+ "dir1/default_core/metrics/het_core_m.desc",
+ "Heterogeneous core metric\n") == 0);
+ TEST_ASSERT_VAL("write dir2 het_core_0 het_core_m",
+ overlay_write_file(overlay_root,
+ "dir2/het_core_0/metrics/het_core_m",
+ "het_core_ev + 1\n") == 0);
+ TEST_ASSERT_VAL("write dir2 het_core_1 het_core_m",
+ overlay_write_file(overlay_root,
+ "dir2/het_core_1/metrics/het_core_m",
+ "het_core_ev + 2\n") == 0);
+ TEST_ASSERT_VAL("write dir1 default_core het_core0_only_m.desc",
+ overlay_write_file(overlay_root,
+ "dir1/default_core/metrics/het_core0_only_m.desc",
+ "Single core PMU metric\n") == 0);
+ TEST_ASSERT_VAL("write dir2 het_core_0 het_core0_only_m",
+ overlay_write_file(overlay_root,
+ "dir2/het_core_0/metrics/het_core0_only_m",
+ "het_core_ev + 3\n") == 0);
+ TEST_ASSERT_VAL("write het_core_0 type",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_core_0/type", "9995\n") == 0);
+ TEST_ASSERT_VAL("write het_core_0 cpus",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_core_0/cpus", "0\n") == 0);
+ TEST_ASSERT_VAL("write het_core_0 het_core_ev",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_core_0/events/het_core_ev", "config=0x11\n") == 0);
+ TEST_ASSERT_VAL("write het_core_1 type",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_core_1/type", "9996\n") == 0);
+ TEST_ASSERT_VAL("write het_core_1 cpus",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_core_1/cpus", "0\n") == 0);
+ TEST_ASSERT_VAL("write het_core_1 het_core_ev",
+ overlay_write_file(overlay_root,
+ "het_sysfs/het_core_1/events/het_core_ev", "config=0x22\n") == 0);
+
+ TEST_ASSERT_VAL("format perfconfig",
+ snprintf(config_body, sizeof(config_body),
+ "[pmu-events]\n\tdir = %s/dir1:%s/dir2\n",
+ overlay_root, overlay_root) < (int)sizeof(config_body));
+ TEST_ASSERT_VAL("write perfconfig",
+ overlay_write_file(overlay_root, "perfconfig", config_body) == 0);
+ TEST_ASSERT_VAL("format config_path",
+ snprintf(config_path, config_path_sz, "%s/perfconfig",
+ overlay_root) < (int)config_path_sz);
+
+ config_exclusive_filename = config_path;
+ TEST_ASSERT_VAL("setenv PERF_CONFIG",
+ setenv("PERF_CONFIG", config_path, 1) == 0);
+ TEST_ASSERT_VAL("setenv PERF_CPUID",
+ setenv("PERF_CPUID", "GenuineTest-6-AA-1", 1) == 0);
+ perf_config__exit();
+ perf_pmus__destroy();
+
+ pmu = test_pmu_get(sysfs_dir, sysfs_dir_sz);
+ *pmu_out = pmu;
+ TEST_ASSERT_VAL("test_pmu_get", pmu != NULL);
+
+ /*
+ * 1-3. Exercise lazy single-event lookup (before perf_pmu__for_each_event()
+ * enumerates all events), then verify full enumeration metadata.
+ */
+ TEST_ASSERT_VAL("reject lowercase event sidecar lookup",
+ !perf_pmu__have_event(pmu, "test-event.desc"));
+ TEST_ASSERT_VAL("reject uppercase event sidecar lookup",
+ !perf_pmu__have_event(pmu, "TEST-EVENT.DESC"));
+ TEST_ASSERT_VAL("reject dotfile event lookup",
+ !perf_pmu__have_event(pmu, ".hidden"));
+ TEST_ASSERT_VAL("have test-event lazily",
+ perf_pmu__have_event(pmu, "test-event"));
+
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new", evlist != NULL);
+ parse_events_error__init(&err);
+ parse_ret = parse_events(evlist, "perf-pmu-test/test-event/", &err);
+ parse_events_error__exit(&err);
+ parsed_config = parse_ret == 0 ? evlist__first(evlist)->core.attr.config : 0;
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse test-event", parse_ret, 0);
+ TEST_ASSERT_VAL("test-event sysfs config preserved",
+ parsed_config == 0xc00000000002a823);
+
+ /* 2. Base CPUID symlink + extended sidecars + %2F decoding. */
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new base-cpuid-event", evlist != NULL);
+ parse_events_error__init(&err);
+ parse_ret = parse_events(evlist, "perf-pmu-test/base-cpuid-event/", &err);
+ parse_events_error__exit(&err);
+ if (parse_ret == 0) {
+ first_ev = evlist__first(evlist);
+ parsed_config = first_ev->core.attr.config;
+ parsed_scale = first_ev->scale;
+ snprintf(parsed_unit, sizeof(parsed_unit), "%s", first_ev->unit ?: "");
+ parsed_retire_lat = first_ev->retirement_latency.mean;
+ }
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse base-cpuid-event via base CPUID symlink", parse_ret, 0);
+ TEST_ASSERT_VAL("base-cpuid-event config", parsed_config == 1);
+ TEST_ASSERT_VAL("base-cpuid-event scale", parsed_scale == 64.0);
+ TEST_ASSERT_VAL("base-cpuid-event unit", !strcmp(parsed_unit, "Bytes"));
+ TEST_ASSERT_VAL("base-cpuid-event retire_lat_mean", parsed_retire_lat == 12.5);
+
+ TEST_ASSERT_VAL("have %2F-encoded slash/event lazily",
+ perf_pmu__have_event(pmu, "slash/event"));
+
+ /* 3. Multi-directory priority overrides, legacy promotion, and full scan. */
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new overridden-event", evlist != NULL);
+ parse_events_error__init(&err);
+ parse_ret = parse_events(evlist, "perf-pmu-test/overridden-event/", &err);
+ parse_events_error__exit(&err);
+ parsed_config = parse_ret == 0 ? evlist__first(evlist)->core.attr.config : 0;
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse overridden-event", parse_ret, 0);
+ TEST_ASSERT_VAL("dir1 event beats dir2 event",
+ parsed_config == ((1ULL << 62) | 1ULL));
+
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new legacy-only-event", evlist != NULL);
+ parse_events_error__init(&err);
+ parse_ret = parse_events(evlist, "perf-pmu-test/legacy-only-event/", &err);
+ parse_events_error__exit(&err);
+ parsed_config = parse_ret == 0 ? evlist__first(evlist)->core.attr.config : 0;
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse legacy-only-event", parse_ret, 0);
+ TEST_ASSERT_VAL("legacy-terms promoted when no base event exists",
+ parsed_config == ((1ULL << 62) | 3ULL));
+
+ TEST_ASSERT_VAL("full event scan succeeds despite malformed boolean sidecar",
+ perf_pmu__num_events(pmu) >= 4);
+
+ TEST_ASSERT_VAL("find test-event info",
+ overlay_find_event(pmu, "test-event", &ev_snap) &&
+ !strcmp(ev_snap.desc, "Overridden sysfs description"));
+ TEST_ASSERT_VAL("find base-cpuid-event info",
+ overlay_find_event(pmu, "base-cpuid-event", &ev_snap) &&
+ !strcmp(ev_snap.str, "krava01=1") &&
+ !strcmp(ev_snap.desc, "From base CPUID model") &&
+ !strcmp(ev_snap.topic, "custom"));
+ TEST_ASSERT_VAL("find slash/event info",
+ overlay_find_event(pmu, "slash/event", &ev_snap) &&
+ !strcmp(ev_snap.str, "krava01=3") &&
+ !strcmp(ev_snap.desc, "Encoded slash event"));
+ TEST_ASSERT_VAL("find overridden-event info",
+ overlay_find_event(pmu, "overridden-event", &ev_snap) &&
+ !strcmp(ev_snap.str, "krava01=5"));
+ TEST_ASSERT_VAL("legacy-only-event str preserved after load_events",
+ overlay_find_event(pmu, "legacy-only-event", &ev_snap) &&
+ !strcmp(ev_snap.str, "krava01=7"));
+
+ if (perf_pmus__scan_core(NULL) != NULL) {
+ struct perf_pmu *core_pmu = perf_pmus__scan_core(NULL);
+ u32 bpu_type = 0;
+
+ TEST_ASSERT_VAL("have core bpu-refs lazily",
+ perf_pmu__have_event(core_pmu, "bpu-refs"));
+ (void)perf_pmu__num_events(core_pmu);
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new bpu-refs", evlist != NULL);
+ parse_events_error__init(&err);
+ parse_ret = parse_events(evlist, "bpu-refs", &err);
+ parse_events_error__exit(&err);
+ if (parse_ret == 0) {
+ first_ev = evlist__first(evlist);
+ bpu_type = first_ev->core.attr.type;
+ parsed_config = first_ev->core.attr.config & 0xffffffULL;
+ }
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse overridden bpu-refs after load_events",
+ parse_ret, 0);
+ TEST_ASSERT_VAL("user .legacy-terms beats builtin common table",
+ bpu_type == PERF_TYPE_HW_CACHE && parsed_config == 1);
+ }
+
+ /* 4. Metric merge and priority across dir1 + dir2. */
+ memset(&metric, 0, sizeof(metric));
+ TEST_ASSERT_VAL("find base_metric",
+ perf_pmu__find_metric(pmu, "base_metric", &metric));
+ TEST_ASSERT_VAL("base_metric expr",
+ metric.metric_expr && !strcmp(metric.metric_expr, "test-event * 2"));
+ TEST_ASSERT_VAL("base_metric threshold from dir1",
+ metric.metric_threshold &&
+ !strcmp(metric.metric_threshold, "base_metric > 10"));
+ TEST_ASSERT_VAL("base_metric scale-unit",
+ metric.unit && !strcmp(metric.unit, "100%"));
+ TEST_ASSERT_VAL("base_metric desc",
+ metric.desc && !strcmp(metric.desc, "Base metric desc"));
+ TEST_ASSERT_VAL("base_metric group",
+ metric.metric_group && !strcmp(metric.metric_group, "TestOverlayGroup"));
+ TEST_ASSERT_VAL("base_metric aggr_mode", metric.aggr_mode == PerCore);
+ TEST_ASSERT_VAL("base_metric constraint",
+ metric.event_grouping == MetricNoGroupEvents);
+ TEST_ASSERT_VAL("perf_pmus__find_metric base_metric",
+ perf_pmus__find_metric("base_metric", &metric));
+ TEST_ASSERT_VAL("default_core metric lookup does not match non-core PMU",
+ !perf_pmus__find_metric_pmu("default_core", "base_metric", NULL));
+ TEST_ASSERT_VAL("full metric scan succeeds despite malformed boolean sidecar",
+ perf_pmu__for_each_metric(pmu, count_metrics_cb, &metric_cnt) == 0 &&
+ metric_cnt == 2);
+ memset(&metric, 0, sizeof(metric));
+ TEST_ASSERT_VAL("dir1 metric beats dir2 metric",
+ perf_pmu__find_metric(pmu, "overridden_metric", &metric) &&
+ metric.metric_expr && !strcmp(metric.metric_expr, "test-event * 4"));
+ TEST_ASSERT_VAL("reject lowercase metric sidecar lookup",
+ !perf_pmu__find_metric(pmu, "base_metric.desc", &metric));
+ TEST_ASSERT_VAL("reject uppercase metric sidecar lookup",
+ !perf_pmu__find_metric(pmu, "base_metric.DESC", &metric));
+
+ /* 5. Metricgroup description precedence. */
+ mg_desc = perf_pmu__describe_metricgroup(pmu, "TestOverlayGroup");
+ TEST_ASSERT_VAL("TestOverlayGroup dir1 precedence",
+ mg_desc && !strcmp(mg_desc, "Dir1 winning group description"));
+ mg_desc = perf_pmus__describe_metricgroup("TestOverlayGroup");
+ TEST_ASSERT_VAL("perf_pmus__describe_metricgroup TestOverlayGroup",
+ mg_desc && !strcmp(mg_desc, "Dir1 winning group description"));
+ mg_desc = perf_pmus__describe_metricgroup("TopdownL1");
+ TEST_ASSERT_VAL("per-PMU user TopdownL1.desc beats compiled-in",
+ mg_desc && !strcmp(mg_desc, "Overridden TopdownL1 on tool PMU"));
+
+ /*
+ * 5b. Sub-metric resolution on heterogeneous/multi-instance PMUs sharing
+ * the same pm->pmu prefix resolves against each originating struct perf_pmu.
+ */
+ {
+ char het_sysfs_path[PATH_MAX];
+ char export_coexist_dir[PATH_MAX];
+ const char *export_argv[5];
+ struct perf_pmu *het0 = NULL, *het1 = NULL;
+ struct perf_pmu *aa_c0 = NULL, *het_c0 = NULL, *het_c1 = NULL;
+ struct pmu_metric found_m;
+ struct evsel *pos;
+ bool saw_duration = false, saw_user = false;
+ bool filt_ev0 = false, filt_ev1 = false;
+ bool saw_core0_expr = false, saw_core1_expr = false;
+ int nr_core0 = 0, nr_core1 = 0;
+ int only_core0 = 0, only_core1 = 0;
+ int het_fd;
+
+ TEST_ASSERT_VAL("perf_pmu__is_core_at matches cpu name without dirfd",
+ perf_pmu__is_core_at(-1, "cpu") &&
+ !perf_pmu__is_core_at(-1, "het_pmu_0"));
+ TEST_ASSERT_VAL("format het_sysfs_path",
+ snprintf(het_sysfs_path, sizeof(het_sysfs_path),
+ "%s/het_sysfs", overlay_root) < (int)sizeof(het_sysfs_path));
+ het_fd = open(het_sysfs_path, O_RDONLY | O_DIRECTORY);
+ TEST_ASSERT_VAL("open het_sysfs", het_fd >= 0);
+ het0 = perf_pmus__add_test_pmu(het_fd, "het_pmu_0");
+ het1 = perf_pmus__add_test_pmu(het_fd, "het_pmu_1");
+ aa_c0 = perf_pmus__add_test_pmu(het_fd, "aa_het_core_0");
+ het_c0 = perf_pmus__add_test_pmu(het_fd, "het_core_0");
+ het_c1 = perf_pmus__add_test_pmu(het_fd, "het_core_1");
+ close(het_fd);
+ TEST_ASSERT_VAL("add het_pmu_0 and het_pmu_1", het0 && het1);
+ TEST_ASSERT_VAL("add aa_het_core_0, het_core_0 and het_core_1",
+ aa_c0 && het_c0 && het_c1 &&
+ aa_c0->is_core && het_c0->is_core && het_c1->is_core);
+ het0->is_uncore = true;
+ free((char *)het0->id);
+ het0->id = strdup("soc_v1");
+ het1->is_uncore = true;
+ free((char *)het1->id);
+ het1->id = strdup("soc_v2");
+ TEST_ASSERT_VAL("set het_pmu ids", het0->id && het1->id);
+
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new het_parent", evlist != NULL);
+ parse_ret = metricgroup__parse_groups(evlist, "all",
+ /*cputype_filter=*/false,
+ "het_parent",
+ /*metric_no_group=*/true,
+ /*metric_no_merge=*/false,
+ /*metric_no_threshold=*/false,
+ /*user_requested_cpu_list=*/NULL,
+ /*system_wide=*/false,
+ /*hardware_aware_grouping=*/false);
+ evlist__for_each_entry(evlist, pos) {
+ const char *ev_name = evsel__name(pos);
+
+ if (ev_name && strstr(ev_name, "duration_time"))
+ saw_duration = true;
+ if (ev_name && strstr(ev_name, "user_time"))
+ saw_user = true;
+ }
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse het_parent across het_pmu_0 and het_pmu_1",
+ parse_ret, 0);
+ TEST_ASSERT_VAL("het_parent resolved sub-metrics per originating PMU",
+ saw_duration && saw_user);
+
+ TEST_ASSERT_VAL("uncore has_metric_or_groups filters by originating PMU",
+ metricgroup__has_metric_or_groups("het_pmu_1",
+ "het_soc2_only") &&
+ !metricgroup__has_metric_or_groups("het_pmu_0",
+ "het_soc2_only") &&
+ !metricgroup__has_metric_or_groups("het_core_0",
+ "het_soc2_only"));
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new filtered het_filt_m", evlist != NULL);
+ parse_ret = metricgroup__parse_groups(evlist, "het_pmu_0",
+ /*cputype_filter=*/false,
+ "het_filt_m",
+ /*metric_no_group=*/true,
+ /*metric_no_merge=*/false,
+ /*metric_no_threshold=*/false,
+ /*user_requested_cpu_list=*/NULL,
+ /*system_wide=*/false,
+ /*hardware_aware_grouping=*/false);
+ evlist__for_each_entry(evlist, pos) {
+ if (!pos->pmu)
+ continue;
+ if (!strcmp(pos->pmu->name, "het_pmu_0"))
+ filt_ev0 = true;
+ if (!strcmp(pos->pmu->name, "het_pmu_1"))
+ filt_ev1 = true;
+ }
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse het_filt_m filtered to het_pmu_0",
+ parse_ret, 0);
+ TEST_ASSERT_VAL("het_filt_m filter excludes het_pmu_1 metric",
+ filt_ev0 && !filt_ev1);
+
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new het_core_m", evlist != NULL);
+ parse_ret = metricgroup__parse_groups(evlist, "all",
+ /*cputype_filter=*/false,
+ "het_core_m",
+ /*metric_no_group=*/true,
+ /*metric_no_merge=*/false,
+ /*metric_no_threshold=*/false,
+ /*user_requested_cpu_list=*/NULL,
+ /*system_wide=*/false,
+ /*hardware_aware_grouping=*/false);
+ evlist__for_each_entry(evlist, pos) {
+ struct metric_event *me;
+ struct metric_expr *mexp;
+
+ if (!pos->pmu)
+ continue;
+ if (!strcmp(pos->pmu->name, "het_core_0"))
+ nr_core0++;
+ if (!strcmp(pos->pmu->name, "het_core_1"))
+ nr_core1++;
+ me = metricgroup__lookup(evlist__metric_events(evlist),
+ pos, /*create=*/false);
+ if (!me)
+ continue;
+ list_for_each_entry(mexp, &me->head, nd) {
+ if (!strcmp(pos->pmu->name, "het_core_0") &&
+ mexp->metric_expr &&
+ !strcmp(mexp->metric_expr, "het_core_ev + 1") &&
+ mexp->metric_events[0] == pos)
+ saw_core0_expr = true;
+ if (!strcmp(pos->pmu->name, "het_core_1") &&
+ mexp->metric_expr &&
+ !strcmp(mexp->metric_expr, "het_core_ev + 2") &&
+ mexp->metric_events[0] == pos)
+ saw_core1_expr = true;
+ }
+ }
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse het_core_m across het_core_0 and het_core_1",
+ parse_ret, 0);
+ TEST_ASSERT_VAL("het_core_m bound default_core metrics per core PMU",
+ saw_core0_expr && saw_core1_expr &&
+ nr_core0 == 1 && nr_core1 == 1);
+
+ TEST_ASSERT_VAL("has_metric_or_groups matches het_core_0 only",
+ metricgroup__has_metric_or_groups("het_core_0",
+ "het_core0_only_m") &&
+ !metricgroup__has_metric_or_groups("het_core_1",
+ "het_core0_only_m"));
+ evlist = evlist__new();
+ TEST_ASSERT_VAL("evlist__new het_core0_only_m", evlist != NULL);
+ parse_ret = metricgroup__parse_groups(evlist, "all",
+ /*cputype_filter=*/false,
+ "het_core0_only_m",
+ /*metric_no_group=*/true,
+ /*metric_no_merge=*/false,
+ /*metric_no_threshold=*/false,
+ /*user_requested_cpu_list=*/NULL,
+ /*system_wide=*/false,
+ /*hardware_aware_grouping=*/false);
+ evlist__for_each_entry(evlist, pos) {
+ if (!pos->pmu)
+ continue;
+ if (!strcmp(pos->pmu->name, "het_core_0"))
+ only_core0++;
+ if (!strcmp(pos->pmu->name, "het_core_1"))
+ only_core1++;
+ }
+ evlist__put(evlist);
+ TEST_ASSERT_EQUAL("parse het_core0_only_m", parse_ret, 0);
+ TEST_ASSERT_VAL("het_core0_only_m does not expand onto het_core_1",
+ only_core0 == 1 && only_core1 == 0);
+
+ TEST_ASSERT_VAL("find_metric_pmu default_core skips per-PMU override",
+ perf_pmus__find_metric_pmu("default_core", "het_coexist_m", &found_m) &&
+ found_m.metric_expr &&
+ !strcmp(found_m.metric_expr, "duration_time + 40"));
+ TEST_ASSERT_VAL("find_metric_pmu wildcard skips per-instance override",
+ perf_pmus__find_metric_pmu("het_pmu", "het_coexist_m", &found_m) &&
+ found_m.metric_expr &&
+ !strcmp(found_m.metric_expr, "duration_time + 20"));
+
+ TEST_ASSERT_VAL("het0 override event preserves pmu_name across layers",
+ overlay_find_event(het0, "het_coexist_ev", &ev_snap) &&
+ !strcmp(ev_snap.str, "config=0x31") &&
+ !strcmp(ev_snap.pmu_name, "het_pmu_0"));
+ TEST_ASSERT_VAL("het1 wildcard event keeps shared pmu_name",
+ overlay_find_event(het1, "het_coexist_ev", &ev_snap) &&
+ !strcmp(ev_snap.str, "config=0x32") &&
+ !strcmp(ev_snap.pmu_name, "het_pmu"));
+
+ TEST_ASSERT_VAL("format export_coexist_dir",
+ snprintf(export_coexist_dir, sizeof(export_coexist_dir),
+ "%s/export_coexist", overlay_root) <
+ (int)sizeof(export_coexist_dir));
+ export_argv[0] = "list";
+ export_argv[1] = "--export-dir";
+ export_argv[2] = export_coexist_dir;
+ export_argv[3] = "metric";
+ export_argv[4] = NULL;
+ TEST_ASSERT_EQUAL("cmd_list --export-dir metric",
+ cmd_list(4, export_argv), 0);
+ TEST_ASSERT_VAL("exported aa_het_core_0 override metric",
+ overlay_expect_file(overlay_root,
+ "export_coexist/aa_het_core_0/metrics/het_coexist_m",
+ "duration_time + 30\n"));
+ TEST_ASSERT_VAL("exported shared default_core metric",
+ overlay_expect_file(overlay_root,
+ "export_coexist/default_core/metrics/het_coexist_m",
+ "duration_time + 40\n"));
+ TEST_ASSERT_VAL("exported het_pmu_0 override metric",
+ overlay_expect_file(overlay_root,
+ "export_coexist/het_pmu_0/metrics/het_coexist_m",
+ "duration_time + 10\n"));
+ TEST_ASSERT_VAL("exported shared het_pmu wildcard metric",
+ overlay_expect_file(overlay_root,
+ "export_coexist/het_pmu/metrics/het_coexist_m",
+ "duration_time + 20\n"));
+ }
+
+ test_pmu_put(sysfs_dir, pmu);
+ *pmu_out = NULL;
+ sysfs_dir[0] = '\0';
+
+ /*
+ * 6. Stepping-specific full CPUID symlink takes precedence over base
+ * CPUID in the same layer without merging the coarser model tree.
+ */
+ TEST_ASSERT_VAL("write stepping-event",
+ overlay_write_file(overlay_root,
+ "dir2/models/stepping1/perf-pmu-test/events/stepping-event",
+ "krava01=2\n") == 0);
+ TEST_ASSERT_VAL("symlink exact stepping CPUID",
+ overlay_symlink(overlay_root,
+ "dir2/cpus/GenuineTest-6-AA-1",
+ "../models/stepping1") == 0);
+
+ perf_config__exit();
+ perf_pmus__destroy();
+
+ pmu = test_pmu_get(sysfs_dir, sysfs_dir_sz);
+ *pmu_out = pmu;
+ TEST_ASSERT_VAL("test_pmu_get stepping", pmu != NULL);
+ TEST_ASSERT_VAL("stepping-event present",
+ perf_pmu__have_event(pmu, "stepping-event"));
+ TEST_ASSERT_VAL("base-cpuid-event not merged when exact stepping matched",
+ !perf_pmu__have_event(pmu, "base-cpuid-event"));
+
+ return TEST_OK;
+}
+
+static int test__pmu_layer_overlay(struct test_suite *test __maybe_unused,
+ int subtest __maybe_unused)
+{
+ char overlay_root[PATH_MAX] = "/tmp/perf-pmu-layer-XXXXXX";
+ char config_path[PATH_MAX] = "";
+ char sysfs_dir[PATH_MAX] = "";
+ char *orig_config = NULL, *orig_cpuid = NULL;
+ const char *orig_exclusive = config_exclusive_filename;
+ const char *env_val;
+ struct perf_pmu *pmu = NULL;
+ int ret = TEST_FAIL;
+
+ env_val = getenv("PERF_CONFIG");
+ if (env_val) {
+ orig_config = strdup(env_val);
+ if (!orig_config)
+ return TEST_FAIL;
+ }
+ env_val = getenv("PERF_CPUID");
+ if (env_val) {
+ orig_cpuid = strdup(env_val);
+ if (!orig_cpuid) {
+ free(orig_config);
+ return TEST_FAIL;
+ }
+ }
+
+ if (mkdtemp(overlay_root)) {
+ ret = run_pmu_layer_overlay_checks(overlay_root,
+ config_path, sizeof(config_path),
+ sysfs_dir, sizeof(sysfs_dir),
+ &pmu);
+ } else {
+ overlay_root[0] = '\0';
+ }
+
+ if (pmu) {
+ test_pmu_put(sysfs_dir, pmu);
+ pmu = NULL;
+ }
+ config_exclusive_filename = orig_exclusive;
+ if (orig_config)
+ setenv("PERF_CONFIG", orig_config, 1);
+ else
+ unsetenv("PERF_CONFIG");
+ if (orig_cpuid)
+ setenv("PERF_CPUID", orig_cpuid, 1);
+ else
+ unsetenv("PERF_CPUID");
+ free(orig_config);
+ free(orig_cpuid);
+ perf_config__exit();
+ perf_pmus__destroy();
+ if (overlay_root[0])
+ overlay_rm_rf_at(AT_FDCWD, overlay_root);
+ return ret;
+}
+
static struct test_case tests__pmu[] = {
TEST_CASE("Parsing with PMU format directory", pmu_format),
TEST_CASE("Parsing with PMU event", pmu_events),
@@ -636,6 +1594,7 @@ static struct test_case tests__pmu[] = {
TEST_CASE("PMU name comparison", name_cmp),
TEST_CASE("PMU cmdline match", pmu_match),
TEST_CASE("PMU config helpers", pmu_config_helpers),
+ TEST_CASE("Layered PMU directory overlays", pmu_layer_overlay),
{ .name = NULL, }
};
diff --git a/tools/perf/tests/shell/pmu_events_dir.sh b/tools/perf/tests/shell/pmu_events_dir.sh
new file mode 100755
index 000000000000..ffca18e674fb
--- /dev/null
+++ b/tools/perf/tests/shell/pmu_events_dir.sh
@@ -0,0 +1,236 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# pmu-events.dir overlay and export tests (no_mlock)
+
+set -e
+
+shelldir=$(dirname "$0")
+tmpdir=$(mktemp -d /tmp/__perf_test.pmu_events_dir.XXXXX)
+
+cleanup() {
+ rm -rf "${tmpdir}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_export_roundtrip() {
+ echo "perf list --export-dir round-trip test"
+ perf list -j -o "${tmpdir}/baseline.json" sw metric metricgroup
+ perf list --export-dir "${tmpdir}/export" sw metric metricgroup
+ [ -d "${tmpdir}/export" ]
+
+ cat > "${tmpdir}/export_config" <<EOF
+[pmu-events]
+ dir = ${tmpdir}/export
+EOF
+
+ PERF_CONFIG="${tmpdir}/export_config" \
+ perf list -j -o "${tmpdir}/roundtrip.json" sw metric metricgroup
+ diff -u "${tmpdir}/baseline.json" "${tmpdir}/roundtrip.json"
+
+ perf list --export-dir "${tmpdir}/tp_export" tracepoint
+ [ -d "${tmpdir}/tp_export" ]
+ [ -z "$(ls -A "${tmpdir}/tp_export")" ]
+ echo "perf list --export-dir round-trip test [Success]"
+}
+
+test_user_overlay() {
+ echo "pmu-events.dir user overlay test"
+ ov_ev="${tmpdir}/overlay/tool/events"
+ ov_met="${tmpdir}/overlay/default_core/metrics"
+ ov_mg="${tmpdir}/overlay/metricgroups"
+ mkdir -p "${ov_ev}" "${ov_met}" "${ov_mg}"
+
+ printf 'config=0x1\n' > "${ov_ev}/overlay_duration"
+ printf 'Overlay custom duration event\n' > "${ov_ev}/overlay_duration.desc"
+ printf 'Overridden duration_time description\n' > "${ov_ev}/duration_time.desc"
+
+ printf 'duration_time * 2\n' > "${ov_met}/overlay_double_duration"
+ printf '1ns\n' > "${ov_met}/overlay_double_duration.scale-unit"
+ printf 'Custom doubled duration metric\n' > "${ov_met}/overlay_double_duration.desc"
+ printf 'OverlayTestGroup\n' > "${ov_met}/overlay_double_duration.group"
+
+ printf 'Custom overlay metric group\n' > "${ov_mg}/OverlayTestGroup.desc"
+
+ cat > "${tmpdir}/overlay_config" <<EOF
+[pmu-events]
+ dir = ${tmpdir}/overlay
+EOF
+
+ PERF_CONFIG="${tmpdir}/overlay_config" perf list > "${tmpdir}/list_all.txt"
+ grep -q "overlay_duration" "${tmpdir}/list_all.txt"
+ grep -q "Overlay custom duration event" "${tmpdir}/list_all.txt"
+ grep -q "Overridden duration_time description" "${tmpdir}/list_all.txt"
+
+ PERF_CONFIG="${tmpdir}/overlay_config" perf list metric > "${tmpdir}/list_metric.txt"
+ grep -q "overlay_double_duration" "${tmpdir}/list_metric.txt"
+ grep -q "Custom doubled duration metric" "${tmpdir}/list_metric.txt"
+
+ PERF_CONFIG="${tmpdir}/overlay_config" perf list metricgroup > "${tmpdir}/list_mg.txt"
+ grep -q "OverlayTestGroup" "${tmpdir}/list_mg.txt"
+ grep -q "Custom overlay metric group" "${tmpdir}/list_mg.txt"
+
+ PERF_CONFIG="${tmpdir}/overlay_config" perf stat -e overlay_duration true >/dev/null 2>&1
+ PERF_CONFIG="${tmpdir}/overlay_config" perf stat -M overlay_double_duration true >/dev/null 2>&1
+
+ rm -f "${ov_ev}/duration_time.desc" \
+ "${ov_ev}/overlay_duration" "${ov_ev}/overlay_duration.desc" \
+ "${ov_met}/overlay_double_duration" "${ov_met}/overlay_double_duration.scale-unit" \
+ "${ov_met}/overlay_double_duration.desc" "${ov_met}/overlay_double_duration.group" \
+ "${ov_mg}/OverlayTestGroup.desc"
+
+ PERF_CONFIG="${tmpdir}/overlay_config" perf list > "${tmpdir}/list_after_rm.txt"
+ if grep -q "overlay_duration" "${tmpdir}/list_after_rm.txt"; then
+ echo "Deleting overlay_duration failed to remove custom event"
+ exit 1
+ fi
+ if grep -q "Overridden duration_time description" "${tmpdir}/list_after_rm.txt"; then
+ echo "Deleting duration_time.desc failed to restore default description"
+ exit 1
+ fi
+ grep -q "duration_time" "${tmpdir}/list_after_rm.txt"
+ echo "pmu-events.dir user overlay test [Success]"
+}
+
+test_jevents_output_dir() {
+ jevents="${shelldir}/../../pmu-events/jevents.py"
+ archdir="${shelldir}/../../pmu-events/arch"
+ if [ ! -f "${jevents}" ] || [ ! -d "${archdir}" ]; then
+ echo "jevents.py --output-dir test [Skipped: source tree not present]"
+ return 0
+ fi
+
+ py="${PYTHON:-}"
+ if [ -z "${py}" ]; then
+ python3 --version >/dev/null 2>&1 && py=python3
+ fi
+ if [ -z "${py}" ]; then
+ python --version >/dev/null 2>&1 && py=python
+ fi
+ if [ -z "${py}" ]; then
+ echo "jevents.py --output-dir test [Skipped: python not found]"
+ return 0
+ fi
+
+ echo "jevents.py --output-dir CPUID symlink test"
+ "${py}" "${jevents}" --output-dir "${tmpdir}/jevents-test" test all "${archdir}"
+ [ -L "${tmpdir}/jevents-test/cpus/testcpu" ]
+ [ -L "${tmpdir}/jevents-test/sys/v8" ]
+ [ -z "$(find "${tmpdir}/jevents-test" -name 'mapfile.csv')" ]
+
+ cat > "${tmpdir}/jevents_config" <<EOF
+[pmu-events]
+ dir = ${tmpdir}/jevents-test
+EOF
+
+ PERF_CPUID=testcpu PERF_CONFIG="${tmpdir}/jevents_config" \
+ perf list > "${tmpdir}/jevents_list.txt" 2>/dev/null
+ grep -q "bp_l1_btb_correct" "${tmpdir}/jevents_list.txt"
+ grep -q "dcache_miss_cpi" "${tmpdir}/jevents_list.txt"
+
+ "${py}" "${jevents}" --output-dir "${tmpdir}/jevents-common" common all "${archdir}"
+ [ -f "${tmpdir}/jevents-common/default_core/events/cycles.legacy-terms" ]
+ [ ! -e "${tmpdir}/jevents-common/default_core/events/cycles" ]
+
+ synth_soc="${tmpdir}/synth_arch/arm64/vendor/soc"
+ mkdir -p "${synth_soc}/sys"
+ cat > "${tmpdir}/synth_arch/arm64/mapfile.csv" <<'EOF'
+Family-model,Version,Filename,EventType
+0x00000000480fd010,v1,vendor/soc,core
+EOF
+ cat > "${synth_soc}/branch.json" <<'EOF'
+[
+ {
+ "EventName": "before_sys_ev",
+ "EventCode": "0x11",
+ "BriefDescription": "Event sorting before sys/"
+ }
+]
+EOF
+ cat > "${synth_soc}/sys/uncore.json" <<'EOF'
+[
+ {
+ "Unit": "hisi_sccl,ddrc",
+ "EventName": "sys_uncore_ev",
+ "EventCode": "0x22",
+ "Compat": "0x00000030",
+ "BriefDescription": "System PMU event inside sys/"
+ }
+]
+EOF
+ cat > "${synth_soc}/virtual-memory.json" <<'EOF'
+[
+ {
+ "EventName": "after_sys_ev",
+ "EventCode": "0x33",
+ "BriefDescription": "Event sorting after sys/"
+ }
+]
+EOF
+
+ "${py}" "${jevents}" --output-dir "${tmpdir}/jevents-synth" arm64 all "${tmpdir}/synth_arch"
+ [ -f "${tmpdir}/jevents-synth/models/arm64/vendor/soc/default_core/events/before_sys_ev" ]
+ [ -f "${tmpdir}/jevents-synth/models/arm64/vendor/soc/default_core/events/after_sys_ev" ]
+ [ ! -e "${tmpdir}/jevents-synth/models/arm64/vendor/soc/sys/default_core/events/after_sys_ev" ]
+ [ -f "${tmpdir}/jevents-synth/models/arm64/vendor/soc/sys/hisi_sccl,ddrc/events/sys_uncore_ev" ]
+ [ -L "${tmpdir}/jevents-synth/cpus/0x00000000480fd010" ]
+ [ -L "${tmpdir}/jevents-synth/sys/0x00000030" ]
+ echo "jevents.py --output-dir CPUID symlink test [Success]"
+}
+
+test_export_cpuid() {
+ echo "perf list --export-cpuid test"
+ if perf list --export-cpuid testcpu >/dev/null 2>&1; then
+ echo "--export-cpuid without --export-dir unexpectedly succeeded"
+ exit 1
+ fi
+ if perf list --export-dir "${tmpdir}/bad_cpuid" \
+ --export-cpuid no_such_cpuid_xyz >/dev/null 2>&1; then
+ echo "--export-cpuid with unknown CPUID unexpectedly succeeded"
+ exit 1
+ fi
+
+ perf list --export-dir "${tmpdir}/cpuid_test" --export-cpuid testarch
+ [ -L "${tmpdir}/cpuid_test/cpus/testcpu" ]
+ [ -L "${tmpdir}/cpuid_test/sys/v8" ]
+ [ -f "${tmpdir}/cpuid_test/models/testarch/test_soc/cpu/default_core/events/bp_l1_btb_correct" ]
+ [ -f "${tmpdir}/cpuid_test/models/testarch/test_soc/cpu/default_core/metrics/dcache_miss_cpi" ]
+
+ printf 'Migrated testcpu metric description\n' \
+ > "${tmpdir}/cpuid_test/models/testarch/test_soc/cpu/default_core/metrics/dcache_miss_cpi.desc"
+ cat > "${tmpdir}/cpuid_test_config" <<EOF
+[pmu-events]
+ dir = ${tmpdir}/cpuid_test
+EOF
+ PERF_CPUID=testcpu PERF_CONFIG="${tmpdir}/cpuid_test_config" \
+ perf list metric > "${tmpdir}/cpuid_test_metric.txt" 2>/dev/null
+ grep -q "Migrated testcpu metric description" "${tmpdir}/cpuid_test_metric.txt"
+
+ perf list --export-dir "${tmpdir}/cpuid_common" --export-cpuid common
+ [ -f "${tmpdir}/cpuid_common/default_core/events/cycles.legacy-terms" ]
+
+ if perf list --export-dir "${tmpdir}/cpuid_n2" \
+ --export-cpuid 0x00000000411fd493 metric metricgroup >/dev/null 2>&1; then
+ [ -L "${tmpdir}/cpuid_n2/cpus/0x00000000410fd490" ]
+ [ -d "${tmpdir}/cpuid_n2/models/arm64/arm/neoverse-n2-v2/default_core/metrics" ]
+ [ ! -d "${tmpdir}/cpuid_n2/models/arm64/arm/neoverse-n2-v2/default_core/events" ]
+ fi
+ if perf list --export-dir "${tmpdir}/cpuid_skl" \
+ --export-cpuid x86/skylake metric >/dev/null 2>&1; then
+ [ -L "${tmpdir}/cpuid_skl/cpus/GenuineIntel-6-4E" ]
+ [ -L "${tmpdir}/cpuid_skl/cpus/GenuineIntel-6-5E" ]
+ fi
+ echo "perf list --export-cpuid test [Success]"
+}
+
+test_export_roundtrip
+test_user_overlay
+test_jevents_output_dir
+test_export_cpuid
+cleanup
+exit 0
--
2.56.1.703.gf848ed807c-goog
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 ` [PATCH 04/11] perf pmu-layer: Add openat-based directory layer with literal CPUID/symlink lookup Ian Rogers
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 ` Ian Rogers [this message]
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-12-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®